diff --git a/build/build.sh b/build/build.sh
index 70a090b5..2547bbae 100755
--- a/build/build.sh
+++ b/build/build.sh
@@ -58,6 +58,10 @@ function build() {
rm -f $(basename $EDGE_API_ZIP_FILE)
cd -
+ # generate files
+ echo "generating files ..."
+ go run -tags $TAG $ROOT/../cmd/edge-admin/main.go generate
+
# build
echo "building "${NAME}" ..."
env GOOS=$OS GOARCH=$ARCH go build -tags $TAG -ldflags="-s -w" -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
diff --git a/cmd/edge-admin/main.go b/cmd/edge-admin/main.go
index 6ec7eca2..df293f98 100644
--- a/cmd/edge-admin/main.go
+++ b/cmd/edge-admin/main.go
@@ -5,6 +5,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/apps"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
+ "github.com/TeaOSLab/EdgeAdmin/internal/gen"
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
_ "github.com/iwind/TeaGo/bootstrap"
@@ -70,6 +71,13 @@ func main() {
}
fmt.Println("change demo mode successfully")
})
+ app.On("generate", func() {
+ err := gen.Generate()
+ if err != nil {
+ fmt.Println("generate failed: " + err.Error())
+ return
+ }
+ })
app.Run(func() {
adminNode := nodes.NewAdminNode()
adminNode.Run()
diff --git a/internal/gen/generate.go b/internal/gen/generate.go
new file mode 100644
index 00000000..54a5476f
--- /dev/null
+++ b/internal/gen/generate.go
@@ -0,0 +1,129 @@
+// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
+
+package gen
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
+ "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
+ "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
+ "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
+ "github.com/iwind/TeaGo/Tea"
+ "github.com/iwind/TeaGo/files"
+ "github.com/iwind/TeaGo/logs"
+ "io"
+ "os"
+ "path/filepath"
+)
+
+func Generate() error {
+ err := generateComponentsJSFile()
+ if err != nil {
+ return errors.New("generate 'components.js' failed: " + err.Error())
+ }
+
+ return nil
+}
+
+// 生成Javascript文件
+func generateComponentsJSFile() error {
+ var buffer = bytes.NewBuffer([]byte{})
+
+ var webRoot string
+ if Tea.IsTesting() {
+ webRoot = Tea.Root + "/../web/public/js/components/"
+ } else {
+ webRoot = Tea.Root + "/web/public/js/components/"
+ }
+ f := files.NewFile(webRoot)
+
+ f.Range(func(file *files.File) {
+ if !file.IsFile() {
+ return
+ }
+ if file.Ext() != ".js" {
+ return
+ }
+ data, err := file.ReadAll()
+ if err != nil {
+ logs.Error(err)
+ return
+ }
+ buffer.Write(data)
+ buffer.Write([]byte{'\n', '\n'})
+ })
+
+ // 条件组件
+ typesJSON, err := json.Marshal(condutils.ReadAllAvailableCondTypes())
+ if err != nil {
+ logs.Println("ComponentsAction marshal request cond types failed: " + err.Error())
+ } else {
+ buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
+ buffer.Write(typesJSON)
+ buffer.Write([]byte{'\n', '\n'})
+ }
+
+ // 条件操作符
+ requestOperatorsJSON, err := json.Marshal(shared.AllRequestOperators())
+ if err != nil {
+ logs.Println("ComponentsAction marshal request operators failed: " + err.Error())
+ } else {
+ buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
+ buffer.Write(requestOperatorsJSON)
+ buffer.Write([]byte{'\n', '\n'})
+ }
+
+ // 请求变量
+ requestVariablesJSON, err := json.Marshal(shared.DefaultRequestVariables())
+ if err != nil {
+ logs.Println("ComponentsAction marshal request variables failed: " + err.Error())
+ } else {
+ buffer.WriteString("window.REQUEST_VARIABLES = ")
+ buffer.Write(requestVariablesJSON)
+ buffer.Write([]byte{'\n', '\n'})
+ }
+
+ // 指标
+ metricHTTPKeysJSON, err := json.Marshal(serverconfigs.FindAllMetricKeyDefinitions(serverconfigs.MetricItemCategoryHTTP))
+ if err != nil {
+ logs.Println("ComponentsAction marshal metric http keys failed: " + err.Error())
+ } else {
+ buffer.WriteString("window.METRIC_HTTP_KEYS = ")
+ buffer.Write(metricHTTPKeysJSON)
+ buffer.Write([]byte{'\n', '\n'})
+ }
+
+ // IP地址阈值项目
+ ipAddrThresholdItemsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdItems())
+ if err != nil {
+ logs.Println("ComponentsAction marshal ip addr threshold items failed: " + err.Error())
+ } else {
+ buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
+ buffer.Write(ipAddrThresholdItemsJSON)
+ buffer.Write([]byte{'\n', '\n'})
+ }
+
+ // IP地址阈值动作
+ ipAddrThresholdActionsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdActions())
+ if err != nil {
+ logs.Println("ComponentsAction marshal ip addr threshold actions failed: " + err.Error())
+ } else {
+ buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
+ buffer.Write(ipAddrThresholdActionsJSON)
+ buffer.Write([]byte{'\n', '\n'})
+ }
+
+ fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
+ if err != nil {
+ return err
+ }
+
+ _, err = io.Copy(fp, buffer)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/internal/gen/generate_test.go b/internal/gen/generate_test.go
new file mode 100644
index 00000000..8092964d
--- /dev/null
+++ b/internal/gen/generate_test.go
@@ -0,0 +1,13 @@
+// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
+
+package gen
+
+import "testing"
+
+func TestGenerate(t *testing.T) {
+ err := Generate()
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Log("ok")
+}
diff --git a/internal/web/actions/default/ui/init.go b/internal/web/actions/default/ui/init.go
index fc0543f6..390e9f8b 100644
--- a/internal/web/actions/default/ui/init.go
+++ b/internal/web/actions/default/ui/init.go
@@ -1,11 +1,10 @@
package ui
import (
- "compress/gzip"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
- "github.com/iwind/TeaGo/actions"
+ "github.com/iwind/TeaGo/Tea"
)
func init() {
@@ -16,11 +15,6 @@ func init() {
// 公共可以访问的链接
Get("/image/:fileId", new(ImageAction)).
- // 以下的需要压缩
- Helper(&actions.Gzip{Level: gzip.BestCompression}).
- Get("/components.js", new(ComponentsAction)).
- EndHelpers().
-
// 以下需要登录
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
Get("/download", new(DownloadAction)).
@@ -31,7 +25,13 @@ func init() {
Post("/hideTip", new(HideTipAction)).
Post("/theme", new(ThemeAction)).
Post("/validateIPs", new(ValidateIPsAction)).
-
EndAll()
+
+ // 开发环境下总是动态加载,以便于调试
+ if Tea.IsTesting() {
+ server.
+ Get("/js/components.js", new(ComponentsAction)).
+ EndAll()
+ }
})
}
diff --git a/web/public/js/components.js b/web/public/js/components.js
new file mode 100755
index 00000000..ad11774f
--- /dev/null
+++ b/web/public/js/components.js
@@ -0,0 +1,12042 @@
+// 显示节点的多个集群
+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-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-routes-selector", {
+ props: ["v-routes"],
+ mounted: function () {
+ let that = this
+ Tea.action("/ns/routes/options")
+ .post()
+ .success(function (resp) {
+ that.routes = resp.data.routes
+ })
+ },
+ data: function () {
+ let selectedRoutes = this.vRoutes
+ if (selectedRoutes == null) {
+ selectedRoutes = []
+ }
+
+ return {
+ routeCode: "default",
+ routes: [],
+ isAdding: false,
+ routeType: "default",
+ selectedRoutes: selectedRoutes
+ }
+ },
+ watch: {
+ routeType: function (v) {
+ this.routeCode = ""
+ let that = this
+ this.routes.forEach(function (route) {
+ if (route.type == v && that.routeCode.length == 0) {
+ that.routeCode = route.code
+ }
+ })
+ }
+ },
+ methods: {
+ add: function () {
+ this.isAdding = true
+ this.routeType = "default"
+ this.routeCode = "default"
+ },
+ cancel: function () {
+ this.isAdding = false
+ },
+ confirm: function () {
+ if (this.routeCode.length == 0) {
+ return
+ }
+
+ let that = this
+ this.routes.forEach(function (v) {
+ if (v.code == that.routeCode) {
+ that.selectedRoutes.push(v)
+ }
+ })
+ this.cancel()
+ },
+ remove: function (index) {
+ this.selectedRoutes.$remove(index)
+ }
+ }
+ ,
+ template: `
+
+
+
+
+
+ [默认线路]
+ 自定义线路
+ 运营商
+ 中国省市
+ 全球国家地区
+
+
+
+
+
+ {{route.name}}
+
+
+
+
+
+
+
+
+
`
+})
+
+// 递归DNS设置
+Vue.component("ns-recursion-config-box", {
+ props: ["v-recursion-config"],
+ data: function () {
+ let recursion = this.vRecursionConfig
+ if (recursion == null) {
+ recursion = {
+ isOn: false,
+ hosts: [],
+ allowDomains: [],
+ denyDomains: [],
+ useLocalHosts: false
+ }
+ }
+ if (recursion.hosts == null) {
+ recursion.hosts = []
+ }
+ if (recursion.allowDomains == null) {
+ recursion.allowDomains = []
+ }
+ if (recursion.denyDomains == null) {
+ recursion.denyDomains = []
+ }
+ return {
+ config: recursion,
+ hostIsAdding: false,
+ host: "",
+ updatingHost: null
+ }
+ },
+ methods: {
+ changeHosts: function (hosts) {
+ this.config.hosts = hosts
+ },
+ changeAllowDomains: function (domains) {
+ this.config.allowDomains = domains
+ },
+ changeDenyDomains: function (domains) {
+ this.config.denyDomains = domains
+ },
+ removeHost: function (index) {
+ this.config.hosts.$remove(index)
+ },
+ addHost: function () {
+ this.updatingHost = null
+ this.host = ""
+ this.hostIsAdding = !this.hostIsAdding
+ if (this.hostIsAdding) {
+ var that = this
+ setTimeout(function () {
+ let hostRef = that.$refs.hostRef
+ if (hostRef != null) {
+ hostRef.focus()
+ }
+ }, 200)
+ }
+ },
+ updateHost: function (host) {
+ this.updatingHost = host
+ this.host = host.host
+ this.hostIsAdding = !this.hostIsAdding
+
+ if (this.hostIsAdding) {
+ var that = this
+ setTimeout(function () {
+ let hostRef = that.$refs.hostRef
+ if (hostRef != null) {
+ hostRef.focus()
+ }
+ }, 200)
+ }
+ },
+ confirmHost: function () {
+ if (this.host.length == 0) {
+ teaweb.warn("请输入DNS地址")
+ return
+ }
+
+ // TODO 校验Host
+ // TODO 可以输入端口号
+ // TODO 可以选择协议
+
+ this.hostIsAdding = false
+ if (this.updatingHost == null) {
+ this.config.hosts.push({
+ host: this.host
+ })
+ } else {
+ this.updatingHost.host = this.host
+ }
+ },
+ cancelHost: function () {
+ this.hostIsAdding = false
+ }
+ },
+ template: ``
+})
+
+Vue.component("ns-access-log-ref-box", {
+ props: ["v-access-log-ref", "v-is-parent"],
+ data: function () {
+ let config = this.vAccessLogRef
+ if (config == null) {
+ config = {
+ isOn: false,
+ isPrior: false,
+ logMissingDomains: false
+ }
+ }
+ if (typeof (config.logMissingDomains) == "undefined") {
+ config.logMissingDomains = false
+ }
+ return {
+ config: config
+ }
+ },
+ template: `
+
+
+
+
+
+ 是否启用
+
+
+
+
+
+ 记录所有访问
+
+
+
+
+
+
+
+
+
`
+})
+
+Vue.component("ns-route-ranges-box", {
+ props: ["v-ranges"],
+ data: function () {
+ let ranges = this.vRanges
+ if (ranges == null) {
+ ranges = []
+ }
+ return {
+ ranges: ranges,
+ isAdding: false,
+
+ // IP范围
+ ipRangeFrom: "",
+ ipRangeTo: ""
+ }
+ },
+ methods: {
+ add: function () {
+ this.isAdding = true
+ let that = this
+ setTimeout(function () {
+ that.$refs.ipRangeFrom.focus()
+ }, 100)
+ },
+ remove: function (index) {
+ this.ranges.$remove(index)
+ },
+ cancelIPRange: function () {
+ this.isAdding = false
+ this.ipRangeFrom = ""
+ this.ipRangeTo = ""
+ },
+ confirmIPRange: function () {
+ // 校验IP
+ let that = this
+ this.ipRangeFrom = this.ipRangeFrom.trim()
+ if (!this.validateIP(this.ipRangeFrom)) {
+ teaweb.warn("开始IP填写错误", function () {
+ that.$refs.ipRangeFrom.focus()
+ })
+ return
+ }
+
+ this.ipRangeTo = this.ipRangeTo.trim()
+ if (!this.validateIP(this.ipRangeTo)) {
+ teaweb.warn("结束IP填写错误", function () {
+ that.$refs.ipRangeTo.focus()
+ })
+ return
+ }
+
+ this.ranges.push({
+ type: "ipRange",
+ params: {
+ ipFrom: this.ipRangeFrom,
+ ipTo: this.ipRangeTo
+ }
+ })
+ this.cancelIPRange()
+ },
+ validateIP: function (ip) {
+ if (!ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
+ return false
+ }
+ let pieces = ip.split(".")
+ let isOk = true
+ pieces.forEach(function (v) {
+ let v1 = parseInt(v)
+ if (v1 > 255) {
+ isOk = false
+ }
+ })
+ return isOk
+ }
+ },
+ template: `
+
+
+
+
IP范围:
+ {{range.params.ipFrom}} - {{range.params.ipTo}}
+
+
+
+
+
+
+
+
+
+
`
+})
+
+// 选择单一线路
+Vue.component("ns-route-selector", {
+ props: ["v-route-code"],
+ mounted: function () {
+ let that = this
+ Tea.action("/ns/routes/options")
+ .post()
+ .success(function (resp) {
+ that.routes = resp.data.routes
+ })
+ },
+ data: function () {
+ let routeCode = this.vRouteCode
+ if (routeCode == null) {
+ routeCode = ""
+ }
+ return {
+ routeCode: routeCode,
+ routes: []
+ }
+ },
+ template: `
+
+
+ [线路]
+ {{route.name}}
+
+
+
`
+})
+
+Vue.component("ns-user-selector", {
+ mounted: function () {
+ let that = this
+
+ Tea.action("/ns/users/options")
+ .post()
+ .success(function (resp) {
+ that.users = resp.data.users
+ })
+ },
+ props: ["v-user-id"],
+ data: function () {
+ let userId = this.vUserId
+ if (userId == null) {
+ userId = 0
+ }
+ return {
+ users: [],
+ userId: userId
+ }
+ },
+ template: `
+
+ [选择用户]
+ {{user.fullname}} ({{user.username}})
+
+
`
+})
+
+Vue.component("ns-access-log-box", {
+ props: ["v-access-log", "v-keyword"],
+ data: function () {
+ let accessLog = this.vAccessLog
+ return {
+ accessLog: accessLog
+ }
+ },
+ methods: {
+ showLog: function () {
+ let that = this
+ let requestId = this.accessLog.requestId
+ this.$parent.$children.forEach(function (v) {
+ if (v.deselect != null) {
+ v.deselect()
+ }
+ })
+ this.select()
+
+ teaweb.popup("/ns/clusters/accessLogs/viewPopup?requestId=" + requestId, {
+ width: "50em",
+ height: "24em",
+ onClose: function () {
+ that.deselect()
+ }
+ })
+ },
+ select: function () {
+ this.$refs.box.parentNode.style.cssText = "background: rgba(0, 0, 0, 0.1)"
+ },
+ deselect: function () {
+ this.$refs.box.parentNode.style.cssText = ""
+ }
+ },
+ template: `
+
[{{accessLog.region}}] {{accessLog.remoteAddr}} [{{accessLog.timeLocal}}] [{{accessLog.networking}}]
{{accessLog.questionType}} {{accessLog.questionName}} ->
{{accessLog.recordType}} {{accessLog.recordValue}}
+
+ 线路: {{route.name}}
+ 递归DNS
+
+
+ 错误:[{{accessLog.error}}]
+
+
`
+})
+
+Vue.component("ns-cluster-selector", {
+ props: ["v-cluster-id"],
+ mounted: function () {
+ let that = this
+
+ Tea.action("/ns/clusters/options")
+ .post()
+ .success(function (resp) {
+ that.clusters = resp.data.clusters
+ })
+ },
+ data: function () {
+ let clusterId = this.vClusterId
+ if (clusterId == null) {
+ clusterId = 0
+ }
+ return {
+ clusters: [],
+ clusterId: clusterId
+ }
+ },
+ template: `
+
+ [选择集群]
+ {{cluster.name}}
+
+
`
+})
+
+Vue.component("plan-user-selector", {
+ mounted: function () {
+ let that = this
+
+ Tea.action("/plans/users/options")
+ .post()
+ .success(function (resp) {
+ that.users = resp.data.users
+ })
+ },
+ props: ["v-user-id"],
+ data: function () {
+ let userId = this.vUserId
+ if (userId == null) {
+ userId = 0
+ }
+ return {
+ users: [],
+ userId: userId
+ }
+ },
+ watch: {
+ userId: function (v) {
+ this.$emit("change", v)
+ }
+ },
+ template: `
+
+ [选择用户]
+ {{user.fullname}} ({{user.username}})
+
+
`
+})
+
+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
+
+
`
+})
+
+// 套餐价格配置
+Vue.component("plan-price-config-box", {
+ props: ["v-price-type", "v-monthly-price", "v-seasonally-price", "v-yearly-price", "v-traffic-price"],
+ data: function () {
+ let priceType = this.vPriceType
+ if (priceType == null) {
+ priceType = "period"
+ }
+
+ 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()
+ }
+
+ return {
+ priceType: priceType,
+ monthlyPrice: monthlyPrice,
+ seasonallyPrice: seasonallyPrice,
+ yearlyPrice: yearlyPrice,
+
+ monthlyPriceNumber: monthlyPriceNumber,
+ seasonallyPriceNumber: seasonallyPriceNumber,
+ yearlyPriceNumber: yearlyPriceNumber,
+
+ trafficPriceBase: trafficPriceBase,
+ trafficPrice: trafficPrice
+ }
+ },
+ 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
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+ 按时间周期
+ 按流量
+
+
+
+
+
+
+
+
`
+})
+
+Vue.component("http-stat-config-box", {
+ props: ["v-stat-config", "v-is-location", "v-is-group"],
+ data: function () {
+ let stat = this.vStatConfig
+ if (stat == null) {
+ stat = {
+ isPrior: false,
+ isOn: false
+ }
+ }
+ return {
+ stat: stat
+ }
+ },
+ template: ``
+})
+
+Vue.component("http-request-conds-box", {
+ props: ["v-conds"],
+ data: function () {
+ let conds = this.vConds
+ if (conds == null) {
+ conds = {
+ isOn: true,
+ connector: "or",
+ groups: []
+ }
+ }
+ return {
+ conds: conds,
+ components: window.REQUEST_COND_COMPONENTS
+ }
+ },
+ methods: {
+ change: function () {
+ this.$emit("change", this.conds)
+ },
+ addGroup: function () {
+ window.UPDATING_COND_GROUP = null
+
+ let that = this
+ teaweb.popup("/servers/server/settings/conds/addGroupPopup", {
+ height: "30em",
+ callback: function (resp) {
+ that.conds.groups.push(resp.data.group)
+ that.change()
+ }
+ })
+ },
+ updateGroup: function (groupIndex, group) {
+ window.UPDATING_COND_GROUP = group
+ let that = this
+ teaweb.popup("/servers/server/settings/conds/addGroupPopup", {
+ height: "30em",
+ callback: function (resp) {
+ Vue.set(that.conds.groups, groupIndex, resp.data.group)
+ that.change()
+ }
+ })
+ },
+ removeGroup: function (groupIndex) {
+ let that = this
+ teaweb.confirm("确定要删除这一组条件吗?", function () {
+ that.conds.groups.$remove(groupIndex)
+ that.change()
+ })
+ },
+ typeName: function (cond) {
+ let c = this.components.$find(function (k, v) {
+ return v.type == cond.type
+ })
+ if (c != null) {
+ return c.name;
+ }
+ return cond.param + " " + cond.operator
+ }
+ },
+ template: `
+
+
+
+
+ 分组{{groupIndex+1}}
+
+
+
+ {{cond.param}} {{cond.operator}}
+ {{typeName(cond)}}:
+ {{cond.value}}
+
+
+ {{group.connector}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分组之间关系
+
+
+ 和
+ 或
+
+
+
+
+
+
+
+ +添加分组
+
+
+`
+})
+
+Vue.component("ssl-config-box", {
+ props: ["v-ssl-policy", "v-protocol", "v-server-id"],
+ created: function () {
+ let that = this
+ setTimeout(function () {
+ that.sortableCipherSuites()
+ }, 100)
+ },
+ data: function () {
+ let policy = this.vSslPolicy
+ if (policy == null) {
+ policy = {
+ id: 0,
+ isOn: true,
+ certRefs: [],
+ certs: [],
+ clientCARefs: [],
+ clientCACerts: [],
+ clientAuthType: 0,
+ minVersion: "TLS 1.1",
+ hsts: null,
+ cipherSuitesIsOn: false,
+ cipherSuites: [],
+ http2Enabled: true
+ }
+ } else {
+ if (policy.certRefs == null) {
+ policy.certRefs = []
+ }
+ if (policy.certs == null) {
+ policy.certs = []
+ }
+ if (policy.clientCARefs == null) {
+ policy.clientCARefs = []
+ }
+ if (policy.clientCACerts == null) {
+ policy.clientCACerts = []
+ }
+ if (policy.cipherSuites == null) {
+ policy.cipherSuites = []
+ }
+ }
+
+ let hsts = policy.hsts
+ if (hsts == null) {
+ hsts = {
+ isOn: false,
+ maxAge: 0,
+ includeSubDomains: false,
+ preload: false,
+ domains: []
+ }
+ }
+
+ return {
+ policy: policy,
+
+ // hsts
+ hsts: hsts,
+ hstsOptionsVisible: false,
+ hstsDomainAdding: false,
+ addingHstsDomain: "",
+ hstsDomainEditingIndex: -1,
+
+ // 相关数据
+ allVersions: window.SSL_ALL_VERSIONS,
+ allCipherSuites: window.SSL_ALL_CIPHER_SUITES.$copy(),
+ modernCipherSuites: window.SSL_MODERN_CIPHER_SUITES,
+ intermediateCipherSuites: window.SSL_INTERMEDIATE_CIPHER_SUITES,
+ allClientAuthTypes: window.SSL_ALL_CLIENT_AUTH_TYPES,
+ cipherSuitesVisible: false,
+
+ // 高级选项
+ moreOptionsVisible: false
+ }
+ },
+ watch: {
+ hsts: {
+ deep: true,
+ handler: function () {
+ this.policy.hsts = this.hsts
+ }
+ }
+ },
+ methods: {
+ // 删除证书
+ removeCert: function (index) {
+ let that = this
+ teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () {
+ that.policy.certRefs.$remove(index)
+ that.policy.certs.$remove(index)
+ })
+ },
+
+ // 选择证书
+ selectCert: function () {
+ let that = this
+ let selectedCertIds = []
+ if (this.policy != null && this.policy.certs.length > 0) {
+ this.policy.certs.forEach(function (cert) {
+ selectedCertIds.push(cert.id.toString())
+ })
+ }
+ teaweb.popup("/servers/certs/selectPopup?selectedCertIds=" + selectedCertIds, {
+ width: "50em",
+ height: "30em",
+ callback: function (resp) {
+ that.policy.certRefs.push(resp.data.certRef)
+ that.policy.certs.push(resp.data.cert)
+ }
+ })
+ },
+
+ // 上传证书
+ uploadCert: function () {
+ let that = this
+ teaweb.popup("/servers/certs/uploadPopup", {
+ height: "28em",
+ callback: function (resp) {
+ teaweb.success("上传成功", function () {
+ that.policy.certRefs.push(resp.data.certRef)
+ that.policy.certs.push(resp.data.cert)
+ })
+ }
+ })
+ },
+
+ // 申请证书
+ requestCert: function () {
+ // 已经在证书中的域名
+ let excludeServerNames = []
+ if (this.policy != null && this.policy.certs.length > 0) {
+ this.policy.certs.forEach(function (cert) {
+ excludeServerNames.$pushAll(cert.dnsNames)
+ })
+ }
+
+ let that = this
+ teaweb.popup("/servers/server/settings/https/requestCertPopup?serverId=" + this.vServerId + "&excludeServerNames=" + excludeServerNames.join(","), {
+ callback: function () {
+ that.policy.certRefs.push(resp.data.certRef)
+ that.policy.certs.push(resp.data.cert)
+ }
+ })
+ },
+
+ // 更多选项
+ changeOptionsVisible: function () {
+ this.moreOptionsVisible = !this.moreOptionsVisible
+ },
+
+ // 格式化时间
+ formatTime: function (timestamp) {
+ return new Date(timestamp * 1000).format("Y-m-d")
+ },
+
+ // 格式化加密套件
+ formatCipherSuite: function (cipherSuite) {
+ return cipherSuite.replace(/(AES|3DES)/, "$1 ")
+ },
+
+ // 添加单个套件
+ addCipherSuite: function (cipherSuite) {
+ if (!this.policy.cipherSuites.$contains(cipherSuite)) {
+ this.policy.cipherSuites.push(cipherSuite)
+ }
+ this.allCipherSuites.$removeValue(cipherSuite)
+ },
+
+ // 删除单个套件
+ removeCipherSuite: function (cipherSuite) {
+ let that = this
+ teaweb.confirm("确定要删除此套件吗?", function () {
+ that.policy.cipherSuites.$removeValue(cipherSuite)
+ that.allCipherSuites = window.SSL_ALL_CIPHER_SUITES.$findAll(function (k, v) {
+ return !that.policy.cipherSuites.$contains(v)
+ })
+ })
+ },
+
+ // 清除所选套件
+ clearCipherSuites: function () {
+ let that = this
+ teaweb.confirm("确定要清除所有已选套件吗?", function () {
+ that.policy.cipherSuites = []
+ that.allCipherSuites = window.SSL_ALL_CIPHER_SUITES.$copy()
+ })
+ },
+
+ // 批量添加套件
+ addBatchCipherSuites: function (suites) {
+ var that = this
+ teaweb.confirm("确定要批量添加套件?", function () {
+ suites.$each(function (k, v) {
+ if (that.policy.cipherSuites.$contains(v)) {
+ return
+ }
+ that.policy.cipherSuites.push(v)
+ })
+ })
+ },
+
+ /**
+ * 套件拖动排序
+ */
+ sortableCipherSuites: function () {
+ var box = document.querySelector(".cipher-suites-box")
+ Sortable.create(box, {
+ draggable: ".label",
+ handle: ".icon.handle",
+ onStart: function () {
+
+ },
+ onUpdate: function (event) {
+
+ }
+ })
+ },
+
+ // 显示所有套件
+ showAllCipherSuites: function () {
+ this.cipherSuitesVisible = !this.cipherSuitesVisible
+ },
+
+ // 显示HSTS更多选项
+ showMoreHSTS: function () {
+ this.hstsOptionsVisible = !this.hstsOptionsVisible;
+ if (this.hstsOptionsVisible) {
+ this.changeHSTSMaxAge()
+ }
+ },
+
+ // 监控HSTS有效期修改
+ changeHSTSMaxAge: function () {
+ var v = this.hsts.maxAge
+ if (isNaN(v)) {
+ this.hsts.days = "-"
+ return
+ }
+ this.hsts.days = parseInt(v / 86400)
+ if (isNaN(this.hsts.days)) {
+ this.hsts.days = "-"
+ } else if (this.hsts.days < 0) {
+ this.hsts.days = "-"
+ }
+ },
+
+ // 设置HSTS有效期
+ setHSTSMaxAge: function (maxAge) {
+ this.hsts.maxAge = maxAge
+ this.changeHSTSMaxAge()
+ },
+
+ // 添加HSTS域名
+ addHstsDomain: function () {
+ this.hstsDomainAdding = true
+ this.hstsDomainEditingIndex = -1
+ let that = this
+ setTimeout(function () {
+ that.$refs.addingHstsDomain.focus()
+ }, 100)
+ },
+
+ // 修改HSTS域名
+ editHstsDomain: function (index) {
+ this.hstsDomainEditingIndex = index
+ this.addingHstsDomain = this.hsts.domains[index]
+ this.hstsDomainAdding = true
+ let that = this
+ setTimeout(function () {
+ that.$refs.addingHstsDomain.focus()
+ }, 100)
+ },
+
+ // 确认HSTS域名添加
+ confirmAddHstsDomain: function () {
+ this.addingHstsDomain = this.addingHstsDomain.trim()
+ if (this.addingHstsDomain.length == 0) {
+ return;
+ }
+ if (this.hstsDomainEditingIndex > -1) {
+ this.hsts.domains[this.hstsDomainEditingIndex] = this.addingHstsDomain
+ } else {
+ this.hsts.domains.push(this.addingHstsDomain)
+ }
+ this.cancelHstsDomainAdding()
+ },
+
+ // 取消HSTS域名添加
+ cancelHstsDomainAdding: function () {
+ this.hstsDomainAdding = false
+ this.addingHstsDomain = ""
+ this.hstsDomainEditingIndex = -1
+ },
+
+ // 删除HSTS域名
+ removeHstsDomain: function (index) {
+ this.cancelHstsDomainAdding()
+ this.hsts.domains.$remove(index)
+ },
+
+ // 选择客户端CA证书
+ selectClientCACert: function () {
+ let that = this
+ teaweb.popup("/servers/certs/selectPopup?isCA=1", {
+ width: "50em",
+ height: "30em",
+ callback: function (resp) {
+ that.policy.clientCARefs.push(resp.data.certRef)
+ that.policy.clientCACerts.push(resp.data.cert)
+ }
+ })
+ },
+
+ // 上传CA证书
+ uploadClientCACert: function () {
+ let that = this
+ teaweb.popup("/servers/certs/uploadPopup?isCA=1", {
+ height: "28em",
+ callback: function (resp) {
+ teaweb.success("上传成功", function () {
+ that.policy.clientCARefs.push(resp.data.certRef)
+ that.policy.clientCACerts.push(resp.data.cert)
+ })
+ }
+ })
+ },
+
+ // 删除客户端CA证书
+ removeClientCACert: function (index) {
+ let that = this
+ teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () {
+ that.policy.clientCARefs.$remove(index)
+ that.policy.clientCACerts.$remove(index)
+ })
+ }
+ },
+ template: `
+
SSL/TLS相关配置
+
+
+
+
+ 启用HTTP/2
+
+
+
+
+
+
+
+
+ 选择证书
+
+
+
+ {{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}}
+
+
+
+
+
选择或上传证书后HTTPS TLS 服务才能生效。
+
+
+ 选择已有证书
+ 上传新证书
+ 申请免费证书
+
+
+
+ TLS最低版本
+
+
+ {{version}}
+
+
+
+
+
+
+
+
+ 加密算法套件(CipherSuites)
+
+
+
+ 是否要自定义
+
+
+
+
+ 已添加套件({{policy.cipherSuites.length}}):
+
+
+
+
+
+
+
+
+
+
+
+
+ 是否开启HSTS
+
+
+
+
+
+
+
+
+
+ HSTS包含子域名(includeSubDomains)
+
+
+
+
+
+
+
+
+ HSTS预加载(preload)
+
+
+
+
+
+
+
+
+ HSTS生效的域名
+
+
+
+
+ +
+
+
+
+
+
+
+
+ 客户端认证方式
+
+
+ {{authType.name}}
+
+
+
+
+ 客户端认证CA证书
+
+
+
+ {{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}}
+
+
+
+ 选择已有证书
+ 上传新证书
+
+
+
+
+
+
+
`
+})
+
+// Action列表
+Vue.component("http-firewall-actions-view", {
+ props: ["v-actions"],
+ template: `
+
+ {{action.name}} ({{action.code.toUpperCase()}})
+
+
`
+})
+
+// 显示WAF规则的标签
+Vue.component("http-firewall-rule-label", {
+ props: ["v-rule"],
+ data: function () {
+ return {
+ rule: this.vRule
+ }
+ },
+ methods: {
+ showErr: function (err) {
+
+ teaweb.popupTip("规则校验错误,请修正:" + teaweb.encodeHTML(err) + " ")
+ },
+
+ },
+ template: `
+
+ {{rule.name}}[{{rule.param}}]
+
+
+
+ {{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求
+
+
+
+
+ {{rule.checkpointOptions.allowDomains}}
+
+
+
+ | {{paramFilter.code}}
+ {{rule.operator}}
+ {{rule.value}}
+
+
+
规则错误
+
+
`
+})
+
+// 缓存条件列表
+Vue.component("http-cache-refs-box", {
+ props: ["v-cache-refs"],
+ data: function () {
+ let refs = this.vCacheRefs
+ if (refs == null) {
+ refs = []
+ }
+ return {
+ refs: refs
+ }
+ },
+ methods: {
+ timeUnitName: function (unit) {
+ switch (unit) {
+ case "ms":
+ return "毫秒"
+ case "second":
+ return "秒"
+ case "minute":
+ return "分钟"
+ case "hour":
+ return "小时"
+ case "day":
+ return "天"
+ case "week":
+ return "周 "
+ }
+ return unit
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+ 缓存条件
+ 分组关系
+ 缓存时间
+
+
+
+
+
+ {{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
+ - {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}
+
+ 0 - {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}
+ 状态码:{{cacheRef.status.map(function(v) {return v.toString()}).join(", ")}}
+
+
+ 和
+ 或
+
+
+ {{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}
+ 不缓存
+
+
+
+
+
+
+
`
+})
+
+Vue.component("ssl-certs-box", {
+ props: [
+ "v-certs", // 证书列表
+ "v-protocol", // 协议:https|tls
+ "v-view-size", // 弹窗尺寸
+ "v-single-mode" // 单证书模式
+ ],
+ data: function () {
+ let certs = this.vCerts
+ if (certs == null) {
+ certs = []
+ }
+
+ return {
+ certs: certs
+ }
+ },
+ methods: {
+ certIds: function () {
+ return this.certs.map(function (v) {
+ return v.id
+ })
+ },
+ // 删除证书
+ removeCert: function (index) {
+ let that = this
+ teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () {
+ that.certs.$remove(index)
+ })
+ },
+
+ // 选择证书
+ selectCert: function () {
+ let that = this
+ let width = "50em"
+ let height = "30em"
+ let viewSize = this.vViewSize
+ if (viewSize == null) {
+ viewSize = "normal"
+ }
+ if (viewSize == "mini") {
+ width = "35em"
+ height = "20em"
+ }
+ teaweb.popup("/servers/certs/selectPopup?viewSize=" + viewSize, {
+ width: width,
+ height: height,
+ callback: function (resp) {
+ that.certs.push(resp.data.cert)
+ }
+ })
+ },
+
+ // 上传证书
+ uploadCert: function () {
+ let that = this
+ teaweb.popup("/servers/certs/uploadPopup", {
+ height: "28em",
+ callback: function (resp) {
+ teaweb.success("上传成功", function () {
+ that.certs.push(resp.data.cert)
+ })
+ }
+ })
+ },
+
+ // 格式化时间
+ formatTime: function (timestamp) {
+ return new Date(timestamp * 1000).format("Y-m-d")
+ },
+
+ // 判断是否显示选择|上传按钮
+ buttonsVisible: function () {
+ return this.vSingleMode == null || !this.vSingleMode || this.certs == null || this.certs.length == 0
+ }
+ },
+ template: `
+
+
+
+ {{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}}
+
+
+
+
+
选择或上传证书后HTTPS TLS 服务才能生效。
+
+
+
+ 选择已有证书
+ 上传新证书
+
+
`
+})
+
+Vue.component("http-host-redirect-box", {
+ props: ["v-redirects"],
+ mounted: function () {
+ let that = this
+ sortTable(function (ids) {
+ let newRedirects = []
+ ids.forEach(function (id) {
+ that.redirects.forEach(function (redirect) {
+ if (redirect.id == id) {
+ newRedirects.push(redirect)
+ }
+ })
+ })
+ that.updateRedirects(newRedirects)
+ })
+ },
+ data: function () {
+ let redirects = this.vRedirects
+ if (redirects == null) {
+ redirects = []
+ }
+
+ let id = 0
+ redirects.forEach(function (v) {
+ id++
+ v.id = id
+ })
+
+ return {
+ redirects: redirects,
+ statusOptions: [
+ {"code": 301, "text": "Moved Permanently"},
+ {"code": 308, "text": "Permanent Redirect"},
+ {"code": 302, "text": "Found"},
+ {"code": 303, "text": "See Other"},
+ {"code": 307, "text": "Temporary Redirect"}
+ ],
+ id: id
+ }
+ },
+ methods: {
+ add: function () {
+ let that = this
+ window.UPDATING_REDIRECT = null
+
+ teaweb.popup("/servers/server/settings/redirects/createPopup", {
+ width: "50em",
+ height: "30em",
+ callback: function (resp) {
+ that.id++
+ resp.data.redirect.id = that.id
+ that.redirects.push(resp.data.redirect)
+ that.change()
+ }
+ })
+ },
+ update: function (index, redirect) {
+ let that = this
+ window.UPDATING_REDIRECT = redirect
+
+ teaweb.popup("/servers/server/settings/redirects/createPopup", {
+ width: "50em",
+ height: "30em",
+ callback: function (resp) {
+ resp.data.redirect.id = redirect.id
+ Vue.set(that.redirects, index, resp.data.redirect)
+ that.change()
+ }
+ })
+ },
+ remove: function (index) {
+ let that = this
+ teaweb.confirm("确定要删除这条跳转规则吗?", function () {
+ that.redirects.$remove(index)
+ that.change()
+ })
+ },
+ change: function () {
+ let that = this
+ setTimeout(function (){
+ that.$emit("change", that.redirects)
+ }, 100)
+ },
+ updateRedirects: function (newRedirects) {
+ this.redirects = newRedirects
+ this.change()
+ }
+ },
+ template: `
+
+
+
+ [创建]
+
+
+
+
+
+
+
+
+
+ 跳转前URL
+
+ 跳转后URL
+ 匹配模式
+ HTTP状态码
+ 状态
+ 操作
+
+
+
+
+
+
+ {{redirect.beforeURL}}
+
+ 匹配条件
+
+
+ ->
+ {{redirect.afterURL}}
+
+ 匹配前缀
+ 正则匹配
+ 精准匹配
+
+
+ {{redirect.status}}
+ 默认
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
`
+})
+
+// 单个缓存条件设置
+Vue.component("http-cache-ref-box", {
+ props: ["v-cache-ref", "v-is-reverse"],
+ data: function () {
+ let ref = this.vCacheRef
+ if (ref == null) {
+ ref = {
+ isOn: true,
+ cachePolicyId: 0,
+ key: "${scheme}://${host}${requestURI}",
+ life: {count: 2, unit: "hour"},
+ status: [200],
+ maxSize: {count: 32, unit: "mb"},
+ minSize: {count: 0, unit: "kb"},
+ skipCacheControlValues: ["private", "no-cache", "no-store"],
+ skipSetCookie: true,
+ enableRequestCachePragma: false,
+ conds: null,
+ allowChunkedEncoding: true,
+ isReverse: this.vIsReverse
+ }
+ }
+ if (ref.life == null) {
+ ref.life = {count: 2, unit: "hour"}
+ }
+ if (ref.maxSize == null) {
+ ref.maxSize = {count: 32, unit: "mb"}
+ }
+ if (ref.minSize == null) {
+ ref.minSize = {count: 0, unit: "kb"}
+ }
+ return {
+ ref: ref,
+ moreOptionsVisible: false
+ }
+ },
+ methods: {
+ changeOptionsVisible: function (v) {
+ this.moreOptionsVisible = v
+ },
+ changeLife: function (v) {
+ this.ref.life = v
+ },
+ changeMaxSize: function (v) {
+ this.ref.maxSize = v
+ },
+ changeMinSize: function (v) {
+ this.ref.minSize = v
+ },
+ changeConds: function (v) {
+ this.ref.conds = v
+ },
+ changeStatusList: function (list) {
+ let result = []
+ list.forEach(function (status) {
+ let statusNumber = parseInt(status)
+ if (isNaN(statusNumber) || statusNumber < 100 || statusNumber > 999) {
+ return
+ }
+ result.push(statusNumber)
+ })
+ this.ref.status = result
+ }
+ },
+ template: `
+
+ 匹配条件分组 *
+
+
+
+
+
+
+
+ 缓存有效期 *
+
+
+
+
+
+ 缓存Key *
+
+
+
+
+
+
+
+
+
+ 可缓存的最大内容尺寸
+
+
+
+
+
+
+ 可缓存的最小内容尺寸
+
+
+
+
+
+
+ 支持分片内容
+
+
+
+
+
+
+ 状态码列表
+
+
+
+
+
+
+ 跳过的Cache-Control值
+
+
+
+
+
+
+ 跳过Set-Cookie
+
+
+
+
+
+
+
+
+
+ 支持请求no-cache刷新
+
+
+
+
+
+
+
+
+ `
+})
+
+// 浏览条件列表
+Vue.component("http-request-conds-view", {
+ props: ["v-conds"],
+ data: function () {
+ let conds = this.vConds
+ if (conds == null) {
+ conds = {
+ isOn: true,
+ connector: "or",
+ groups: []
+ }
+ }
+
+ let that = this
+ conds.groups.forEach(function (group) {
+ group.conds.forEach(function (cond) {
+ cond.typeName = that.typeName(cond)
+ })
+ })
+
+ return {
+ initConds: conds,
+ version: 0 // 为了让组件能及时更新加入此变量
+ }
+ },
+ computed: {
+ // 之所以使用computed,是因为需要动态更新
+ conds: function () {
+ return this.initConds
+ }
+ },
+ methods: {
+ typeName: function (cond) {
+ let c = window.REQUEST_COND_COMPONENTS.$find(function (k, v) {
+ return v.type == cond.type
+ })
+ if (c != null) {
+ return c.name;
+ }
+ return cond.param + " " + cond.operator
+ },
+ notifyChange: function () {
+ this.version++
+ let that = this
+ this.initConds.groups.forEach(function (group) {
+ group.conds.forEach(function (cond) {
+ cond.typeName = that.typeName(cond)
+ })
+ })
+ }
+ },
+ template: `
+
{{version}}
+
+
+
+
+ {{cond.param}} {{cond.operator}}
+ {{cond.typeName}}:
+ {{cond.value}}
+
+
+ {{group.connector}}
+
+
+
+ {{group.description}}
+
+
+
+
+`
+})
+
+Vue.component("http-firewall-config-box", {
+ props: ["v-firewall-config", "v-is-location", "v-is-group", "v-firewall-policy"],
+ data: function () {
+ let firewall = this.vFirewallConfig
+ if (firewall == null) {
+ firewall = {
+ isPrior: false,
+ isOn: false,
+ firewallPolicyId: 0
+ }
+ }
+
+ return {
+ firewall: firewall
+ }
+ },
+ template: ``
+})
+
+// 指标图表
+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
+ }
+ return stat.keys[0] + ": " + value + ",占比:" + percent + "%"
+ }
+ },
+ series: [
+ {
+ name: name,
+ type: "pie",
+ data: values,
+ areaStyle: {},
+ color: ["#9DD3E8", "#B2DB9E", "#F39494", "#FBD88A", "#879BD7"]
+ }
+ ]
+ })
+ },
+ renderTimeBar: function (chart) {
+ this.stats.$sort(function (v1, v2) {
+ return (v1.time < v2.time) ? -1 : 1
+ })
+ let values = this.stats.map(function (v) {
+ return v.value
+ })
+
+ let axis = {unit: "", divider: 1}
+ switch (this.item.valueType) {
+ case "count":
+ axis = teaweb.countAxis(values, function (v) {
+ return v
+ })
+ break
+ case "byte":
+ axis = teaweb.bytesAxis(values, function (v) {
+ return v
+ })
+ break
+ }
+
+ let that = this
+ chart.setOption({
+ xAxis: {
+ data: this.stats.map(function (v) {
+ return that.formatTime(v.time)
+ })
+ },
+ yAxis: {
+ axisLabel: {
+ formatter: function (value) {
+ return value + axis.unit
+ }
+ }
+ },
+ tooltip: {
+ show: true,
+ trigger: "item",
+ formatter: function (data) {
+ let stat = that.stats[data.dataIndex]
+ let value = stat.value
+ switch (that.item.valueType) {
+ case "byte":
+ value = teaweb.formatBytes(value)
+ break
+ }
+ return that.formatTime(stat.time) + ": " + value
+ }
+ },
+ grid: {
+ left: 50,
+ top: 10,
+ right: 20,
+ bottom: 25
+ },
+ series: [
+ {
+ name: name,
+ type: "bar",
+ data: values.map(function (v) {
+ return v / axis.divider
+ }),
+ itemStyle: {
+ color: "#9DD3E8"
+ },
+ areaStyle: {},
+ barWidth: "20em"
+ }
+ ]
+ })
+ },
+ renderTimeLine: function (chart) {
+ this.stats.$sort(function (v1, v2) {
+ return (v1.time < v2.time) ? -1 : 1
+ })
+ let values = this.stats.map(function (v) {
+ return v.value
+ })
+
+ let axis = {unit: "", divider: 1}
+ switch (this.item.valueType) {
+ case "count":
+ axis = teaweb.countAxis(values, function (v) {
+ return v
+ })
+ break
+ case "byte":
+ axis = teaweb.bytesAxis(values, function (v) {
+ return v
+ })
+ break
+ }
+
+ let that = this
+ chart.setOption({
+ xAxis: {
+ data: this.stats.map(function (v) {
+ return that.formatTime(v.time)
+ })
+ },
+ yAxis: {
+ axisLabel: {
+ formatter: function (value) {
+ return value + axis.unit
+ }
+ }
+ },
+ tooltip: {
+ show: true,
+ trigger: "item",
+ formatter: function (data) {
+ let stat = that.stats[data.dataIndex]
+ let value = stat.value
+ switch (that.item.valueType) {
+ case "byte":
+ value = teaweb.formatBytes(value)
+ break
+ }
+ return that.formatTime(stat.time) + ": " + value
+ }
+ },
+ grid: {
+ left: 50,
+ top: 10,
+ right: 20,
+ bottom: 25
+ },
+ series: [
+ {
+ name: name,
+ type: "line",
+ data: values.map(function (v) {
+ return v / axis.divider
+ }),
+ itemStyle: {
+ color: "#9DD3E8"
+ },
+ areaStyle: {}
+ }
+ ]
+ })
+ },
+ renderBar: function (chart) {
+ let values = this.stats.map(function (v) {
+ return v.value
+ })
+ let axis = {unit: "", divider: 1}
+ switch (this.item.valueType) {
+ case "count":
+ axis = teaweb.countAxis(values, function (v) {
+ return v
+ })
+ break
+ case "byte":
+ axis = teaweb.bytesAxis(values, function (v) {
+ return v
+ })
+ break
+ }
+ let bottom = 24
+ let rotate = 0
+ let result = teaweb.xRotation(chart, this.stats.map(function (v) {
+ return v.keys[0]
+ }))
+ if (result != null) {
+ bottom = result[0]
+ rotate = result[1]
+ }
+ let that = this
+ chart.setOption({
+ xAxis: {
+ data: this.stats.map(function (v) {
+ return v.keys[0]
+ }),
+ axisLabel: {
+ interval: 0,
+ rotate: rotate
+ }
+ },
+ tooltip: {
+ show: true,
+ trigger: "item",
+ formatter: function (data) {
+ let stat = that.stats[data.dataIndex]
+ let percent = 0
+ if (stat.total > 0) {
+ percent = Math.round((stat.value * 100 / stat.total) * 100) / 100
+ }
+ let value = stat.value
+ switch (that.item.valueType) {
+ case "byte":
+ value = teaweb.formatBytes(value)
+ break
+ }
+ return stat.keys[0] + ": " + value + ",占比:" + percent + "%"
+ }
+ },
+ yAxis: {
+ axisLabel: {
+ formatter: function (value) {
+ return value + axis.unit
+ }
+ }
+ },
+ grid: {
+ left: 40,
+ top: 10,
+ right: 20,
+ bottom: bottom
+ },
+ series: [
+ {
+ name: name,
+ type: "bar",
+ data: values.map(function (v) {
+ return v / axis.divider
+ }),
+ itemStyle: {
+ color: "#9DD3E8"
+ },
+ areaStyle: {},
+ barWidth: "20em"
+ }
+ ]
+ })
+
+ if (this.item.keys != null) {
+ // IP相关操作
+ if (this.item.keys.$contains("${remoteAddr}")) {
+ let that = this
+ chart.on("click", function (args) {
+ let index = that.item.keys.$indexesOf("${remoteAddr}")[0]
+ let value = that.stats[args.dataIndex].keys[index]
+ teaweb.popup("/servers/ipbox?ip=" + value, {
+ width: "50em",
+ height: "30em"
+ })
+ })
+ }
+ }
+ },
+ renderTable: function (chart) {
+ let table = `
+
+
+ 对象
+ 数值
+ 占比
+
+ `
+ let that = this
+ this.stats.forEach(function (v) {
+ let value = v.value
+ switch (that.item.valueType) {
+ case "byte":
+ value = teaweb.formatBytes(value)
+ break
+ }
+ table += "" + 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"],
+ data: function () {
+ let cacheConfig = this.vCacheConfig
+ if (cacheConfig == null) {
+ cacheConfig = {
+ isPrior: false,
+ isOn: false,
+ addStatusHeader: true,
+ cacheRefs: [],
+ purgeIsOn: false,
+ purgeKey: ""
+ }
+ }
+ if (cacheConfig.cacheRefs == null) {
+ cacheConfig.cacheRefs = []
+ }
+ return {
+ cacheConfig: cacheConfig,
+ moreOptionsVisible: false
+ }
+ },
+ methods: {
+ isOn: function () {
+ return ((!this.vIsLocation && !this.vIsGroup) || this.cacheConfig.isPrior) && this.cacheConfig.isOn
+ },
+ 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
+ }
+ },
+ template: ``
+})
+
+// 通用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 options = {}
+ if (window.parent.UPDATING_RULE != null) {
+ options = window.parent.UPDATING_RULE.checkpointOptions
+ }
+
+ if (options == null) {
+ options = {}
+ }
+ if (options.keys != null) {
+ keys = options.keys
+ }
+ if (keys.length == 0) {
+ keys = ["${remoteAddr}", "${requestPath}"]
+ }
+ if (options.period != null) {
+ period = options.period
+ }
+ if (options.threshold != null) {
+ threshold = options.threshold
+ }
+
+ let that = this
+ setTimeout(function () {
+ that.change()
+ }, 100)
+
+ return {
+ keys: keys,
+ period: period,
+ threshold: threshold,
+ options: {},
+ value: threshold
+ }
+ },
+ watch: {
+ period: function () {
+ this.change()
+ },
+ threshold: function () {
+ this.change()
+ }
+ },
+ methods: {
+ changeKeys: function (keys) {
+ this.keys = keys
+ this.change()
+ },
+ change: function () {
+ let period = parseInt(this.period.toString())
+ if (isNaN(period) || period <= 0) {
+ period = 60
+ }
+
+ let threshold = parseInt(this.threshold.toString())
+ if (isNaN(threshold) || threshold <= 0) {
+ threshold = 1000
+ }
+ this.value = threshold
+
+ this.vCheckpoint.options = [
+ {
+ code: "keys",
+ value: this.keys
+ },
+ {
+ code: "period",
+ value: period,
+ },
+ {
+ code: "threshold",
+ value: threshold
+ }
+ ]
+ }
+ },
+ template: ``
+})
+
+// 防盗链
+Vue.component("http-firewall-checkpoint-referer-block", {
+ props: ["v-checkpoint"],
+ data: function () {
+ let allowEmpty = true
+ let allowSameDomain = true
+ let allowDomains = []
+
+ let options = {}
+ if (window.parent.UPDATING_RULE != null) {
+ options = window.parent.UPDATING_RULE.checkpointOptions
+ }
+
+ if (options == null) {
+ options = {}
+ }
+ if (typeof (options.allowEmpty) == "boolean") {
+ allowEmpty = options.allowEmpty
+ }
+ if (typeof (options.allowSameDomain) == "boolean") {
+ allowSameDomain = options.allowSameDomain
+ }
+ if (options.allowDomains != null && typeof (options.allowDomains) == "object") {
+ allowDomains = options.allowDomains
+ }
+
+ let that = this
+ setTimeout(function () {
+ that.change()
+ }, 100)
+
+ return {
+ allowEmpty: allowEmpty,
+ allowSameDomain: allowSameDomain,
+ allowDomains: allowDomains,
+ options: {},
+ value: 0
+ }
+ },
+ watch: {
+ allowEmpty: function () {
+ this.change()
+ },
+ allowSameDomain: function () {
+ this.change()
+ }
+ },
+ methods: {
+ changeAllowDomains: function (values) {
+ this.allowDomains = values
+ this.change()
+ },
+ change: function () {
+ this.vCheckpoint.options = [
+ {
+ code: "allowEmpty",
+ value: this.allowEmpty
+ },
+ {
+ code: "allowSameDomain",
+ value: this.allowSameDomain,
+ },
+ {
+ code: "allowDomains",
+ value: this.allowDomains
+ }
+ ]
+ }
+ },
+ template: `
+
+
+
+
+ 来源域名允许为空
+
+
+
+
+
+
+ 来源域名允许一致
+
+
+
+
+
+
+ 允许的来源域名
+
+
+
+
+
+
+
`
+})
+
+Vue.component("http-cache-refs-config-box", {
+ props: ["v-cache-refs", "v-cache-config", "v-cache-policy-id"],
+ mounted: function () {
+ let that = this
+ sortTable(function (ids) {
+ let newRefs = []
+ ids.forEach(function (id) {
+ that.refs.forEach(function (ref) {
+ if (ref.id == id) {
+ newRefs.push(ref)
+ }
+ })
+ })
+ that.updateRefs(newRefs)
+ that.change()
+ })
+ },
+ data: function () {
+ let refs = this.vCacheRefs
+ if (refs == null) {
+ refs = []
+ }
+
+ let id = 0
+ refs.forEach(function (ref) {
+ id++
+ ref.id = id
+ })
+ return {
+ refs: refs,
+ id: id // 用来对条件进行排序
+ }
+ },
+ methods: {
+ addRef: function (isReverse) {
+ window.UPDATING_CACHE_REF = null
+
+ let width = window.innerWidth
+ if (width > 1024) {
+ width = 1024
+ }
+ let height = window.innerHeight
+ if (height > 500) {
+ height = 500
+ }
+ let that = this
+ teaweb.popup("/servers/server/settings/cache/createPopup?isReverse=" + (isReverse ? 1 : 0), {
+ width: width + "px",
+ height: height + "px",
+ callback: function (resp) {
+ let newRef = resp.data.cacheRef
+ if (newRef.conds == null) {
+ return
+ }
+
+ that.id++
+ newRef.id = that.id
+
+ if (newRef.isReverse) {
+ let newRefs = []
+ let isAdded = false
+ that.refs.forEach(function (v) {
+ if (!v.isReverse && !isAdded) {
+ newRefs.push(newRef)
+ isAdded = true
+ }
+ newRefs.push(v)
+ })
+ if (!isAdded) {
+ newRefs.push(newRef)
+ }
+
+ that.updateRefs(newRefs)
+ } else {
+ that.refs.push(newRef)
+ }
+
+ that.change()
+ }
+ })
+ },
+ updateRef: function (index, cacheRef) {
+ window.UPDATING_CACHE_REF = cacheRef
+
+ let width = window.innerWidth
+ if (width > 1024) {
+ width = 1024
+ }
+ let height = window.innerHeight
+ if (height > 500) {
+ height = 500
+ }
+ let that = this
+ teaweb.popup("/servers/server/settings/cache/createPopup", {
+ width: width + "px",
+ height: height + "px",
+ callback: function (resp) {
+ resp.data.cacheRef.id = that.refs[index].id
+ Vue.set(that.refs, index, resp.data.cacheRef)
+
+ // 通知子组件更新
+ that.$refs.cacheRef[index].notifyChange()
+
+ that.change()
+ }
+ })
+ },
+ removeRef: function (index) {
+ let that = this
+ teaweb.confirm("确定要删除此缓存设置吗?", function () {
+ that.refs.$remove(index)
+ that.change()
+ })
+ },
+ updateRefs: function (newRefs) {
+ this.refs = newRefs
+ if (this.vCacheConfig != null) {
+ this.vCacheConfig.cacheRefs = newRefs
+ }
+ },
+ timeUnitName: function (unit) {
+ switch (unit) {
+ case "ms":
+ return "毫秒"
+ case "second":
+ return "秒"
+ case "minute":
+ return "分钟"
+ case "hour":
+ return "小时"
+ case "day":
+ return "天"
+ case "week":
+ return "周 "
+ }
+ return unit
+ },
+ change: function () {
+ // 自动保存
+ if (this.vCachePolicyId != null && this.vCachePolicyId > 0) {
+ Tea.action("/servers/components/cache/updateRefs")
+ .params({
+ cachePolicyId: this.vCachePolicyId,
+ refsJSON: JSON.stringify(this.refs)
+ })
+ .post()
+ }
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+ 缓存条件
+ 分组关系
+ 缓存时间
+ 操作
+
+
+
+
+
+
+
+
+ {{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
+ - {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}
+
+ 0 - {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}
+ 状态码:{{cacheRef.status.map(function(v) {return v.toString()}).join(", ")}}
+
+
+ 和
+ 或
+
+
+ {{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}
+ 不缓存
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+添加缓存设置 +添加不缓存设置
+
+
+
`
+})
+
+Vue.component("origin-list-box", {
+ props: ["v-primary-origins", "v-backup-origins", "v-server-type", "v-params"],
+ data: function () {
+ return {
+ primaryOrigins: this.vPrimaryOrigins,
+ backupOrigins: this.vBackupOrigins
+ }
+ },
+ methods: {
+ createPrimaryOrigin: function () {
+ teaweb.popup("/servers/server/settings/origins/addPopup?originType=primary&" + this.vParams, {
+ height: "27em",
+ callback: function (resp) {
+ teaweb.success("保存成功", function () {
+ window.location.reload()
+ })
+ }
+ })
+ },
+ createBackupOrigin: function () {
+ teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&" + this.vParams, {
+ height: "27em",
+ callback: function (resp) {
+ teaweb.success("保存成功", function () {
+ window.location.reload()
+ })
+ }
+ })
+ },
+ updateOrigin: function (originId, originType) {
+ teaweb.popup("/servers/server/settings/origins/updatePopup?originType=" + originType + "&" + this.vParams + "&originId=" + originId, {
+ height: "27em",
+ callback: function (resp) {
+ teaweb.success("保存成功", function () {
+ window.location.reload()
+ })
+ }
+ })
+ },
+ deleteOrigin: function (originId, originType) {
+ let that = this
+ teaweb.confirm("确定要删除此源站吗?", function () {
+ Tea.action("/servers/server/settings/origins/delete?" + that.vParams + "&originId=" + originId + "&originType=" + originType)
+ .post()
+ .success(function () {
+ teaweb.success("保存成功", function () {
+ window.location.reload()
+ })
+ })
+ })
+ }
+ },
+ template: ``
+})
+
+Vue.component("origin-list-table", {
+ props: ["v-origins", "v-origin-type"],
+ data: function () {
+ return {}
+ },
+ methods: {
+ deleteOrigin: function (originId) {
+ this.$emit("deleteOrigin", originId, this.vOriginType)
+ },
+ updateOrigin: function (originId) {
+ this.$emit("updateOrigin", originId, this.vOriginType)
+ }
+ },
+ template: `
+
+
+
+ 源站地址
+ 权重
+ 状态
+ 操作
+
+
+
+ {{origin.addr}}
+
+ {{origin.name}}
+
+
+ {{domain}}
+
+
+ {{origin.weight}}
+
+
+
+
+ 修改
+ 删除
+
+
+
`
+})
+
+Vue.component("http-firewall-policy-selector", {
+ props: ["v-http-firewall-policy"],
+ mounted: function () {
+ let that = this
+ Tea.action("/servers/components/waf/count")
+ .post()
+ .success(function (resp) {
+ that.count = resp.data.count
+ })
+ },
+ data: function () {
+ let firewallPolicy = this.vHttpFirewallPolicy
+ return {
+ count: 0,
+ firewallPolicy: firewallPolicy
+ }
+ },
+ methods: {
+ remove: function () {
+ this.firewallPolicy = null
+ },
+ select: function () {
+ let that = this
+ teaweb.popup("/servers/components/waf/selectPopup", {
+ callback: function (resp) {
+ that.firewallPolicy = resp.data.firewallPolicy
+ }
+ })
+ },
+ create: function () {
+ let that = this
+ teaweb.popup("/servers/components/waf/createPopup", {
+ height: "26em",
+ callback: function (resp) {
+ that.firewallPolicy = resp.data.firewallPolicy
+ }
+ })
+ }
+ },
+ template: `
+
+
+ {{firewallPolicy.name}}
+
+
+
`
+})
+
+Vue.component("http-websocket-box", {
+ props: ["v-websocket-ref", "v-websocket-config", "v-is-location", "v-is-group"],
+ data: function () {
+ let websocketRef = this.vWebsocketRef
+ if (websocketRef == null) {
+ websocketRef = {
+ isPrior: false,
+ isOn: false,
+ websocketId: 0
+ }
+ }
+
+ let websocketConfig = this.vWebsocketConfig
+ if (websocketConfig == null) {
+ websocketConfig = {
+ id: 0,
+ isOn: false,
+ handshakeTimeout: {
+ count: 30,
+ unit: "second"
+ },
+ allowAllOrigins: true,
+ allowedOrigins: [],
+ requestSameOrigin: true,
+ requestOrigin: ""
+ }
+ } else {
+ if (websocketConfig.handshakeTimeout == null) {
+ websocketConfig.handshakeTimeout = {
+ count: 30,
+ unit: "second",
+ }
+ }
+ if (websocketConfig.allowedOrigins == null) {
+ websocketConfig.allowedOrigins = []
+ }
+ }
+
+ return {
+ websocketRef: websocketRef,
+ websocketConfig: websocketConfig,
+ handshakeTimeoutCountString: websocketConfig.handshakeTimeout.count.toString(),
+ advancedVisible: false
+ }
+ },
+ watch: {
+ handshakeTimeoutCountString: function (v) {
+ let count = parseInt(v)
+ if (!isNaN(count) && count >= 0) {
+ this.websocketConfig.handshakeTimeout.count = count
+ } else {
+ this.websocketConfig.handshakeTimeout.count = 0
+ }
+ }
+ },
+ methods: {
+ isOn: function () {
+ return ((!this.vIsLocation && !this.vIsGroup) || this.websocketRef.isPrior) && this.websocketRef.isOn
+ },
+ changeAdvancedVisible: function (v) {
+ this.advancedVisible = v
+ },
+ createOrigin: function () {
+ let that = this
+ teaweb.popup("/servers/server/settings/websocket/createOrigin", {
+ height: "12.5em",
+ callback: function (resp) {
+ that.websocketConfig.allowedOrigins.push(resp.data.origin)
+ }
+ })
+ },
+ removeOrigin: function (index) {
+ this.websocketConfig.allowedOrigins.$remove(index)
+ }
+ },
+ template: ``
+})
+
+Vue.component("http-rewrite-rule-list", {
+ props: ["v-web-id", "v-rewrite-rules"],
+ mounted: function () {
+ setTimeout(this.sort, 1000)
+ },
+ data: function () {
+ let rewriteRules = this.vRewriteRules
+ if (rewriteRules == null) {
+ rewriteRules = []
+ }
+ return {
+ rewriteRules: rewriteRules
+ }
+ },
+ methods: {
+ updateRewriteRule: function (rewriteRuleId) {
+ teaweb.popup("/servers/server/settings/rewrite/updatePopup?webId=" + this.vWebId + "&rewriteRuleId=" + rewriteRuleId, {
+ height: "26em",
+ callback: function () {
+ window.location.reload()
+ }
+ })
+ },
+ deleteRewriteRule: function (rewriteRuleId) {
+ let that = this
+ teaweb.confirm("确定要删除此重写规则吗?", function () {
+ Tea.action("/servers/server/settings/rewrite/delete")
+ .params({
+ webId: that.vWebId,
+ rewriteRuleId: rewriteRuleId
+ })
+ .post()
+ .refresh()
+ })
+ },
+ // 排序
+ sort: function () {
+ if (this.rewriteRules.length == 0) {
+ return
+ }
+ let that = this
+ sortTable(function (rowIds) {
+ Tea.action("/servers/server/settings/rewrite/sort")
+ .post()
+ .params({
+ webId: that.vWebId,
+ rewriteRuleIds: rowIds
+ })
+ .success(function () {
+ teaweb.success("保存成功")
+ })
+ })
+ }
+ },
+ template: `
+
+
+
+
+
+
+ 匹配规则
+ 转发目标
+ 转发方式
+ 状态
+ 操作
+
+
+
+
+
+ {{rule.pattern}}
+
+ BREAK
+ {{rule.redirectStatus}}
+ Host: {{rule.proxyHost}}
+
+ {{rule.replace}}
+
+ 隐式
+ 显示
+
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
`
+})
+
+Vue.component("http-rewrite-labels-label", {
+ props: ["v-class"],
+ template: ` `
+})
+
+Vue.component("server-name-box", {
+ props: ["v-server-names"],
+ data: function () {
+ let serverNames = this.vServerNames;
+ if (serverNames == null) {
+ serverNames = []
+ }
+ return {
+ serverNames: serverNames,
+ isSearching: false,
+ keyword: ""
+ }
+ },
+ methods: {
+ addServerName: function () {
+ window.UPDATING_SERVER_NAME = null
+ let that = this
+ teaweb.popup("/servers/addServerNamePopup", {
+ callback: function (resp) {
+ var serverName = resp.data.serverName
+ that.serverNames.push(serverName)
+ }
+ });
+ },
+
+ removeServerName: function (index) {
+ this.serverNames.$remove(index)
+ },
+
+ updateServerName: function (index, serverName) {
+ window.UPDATING_SERVER_NAME = serverName
+ let that = this
+ teaweb.popup("/servers/addServerNamePopup", {
+ callback: function (resp) {
+ var serverName = resp.data.serverName
+ Vue.set(that.serverNames, index, serverName)
+ }
+ });
+ },
+ showSearchBox: function () {
+ this.isSearching = !this.isSearching
+ if (this.isSearching) {
+ let that = this
+ setTimeout(function () {
+ that.$refs.keywordRef.focus()
+ }, 200)
+ } else {
+ this.keyword = ""
+ }
+ },
+ },
+ watch: {
+ keyword: function (v) {
+ this.serverNames.forEach(function (serverName) {
+ if (v.length == 0) {
+ serverName.isShowing = true
+ return
+ }
+ if (serverName.subNames == null || serverName.subNames.length == 0) {
+ if (!teaweb.match(serverName.name, v)) {
+ serverName.isShowing = false
+ }
+ } else {
+ let found = false
+ serverName.subNames.forEach(function (subName) {
+ if (teaweb.match(subName, v)) {
+ found = true
+ }
+ })
+ serverName.isShowing = found
+ }
+ })
+ }
+ },
+ template: `
+
+
+
+
{{serverName.type}}
+
{{serverName.name}}
+
{{serverName.subNames[0]}}等{{serverName.subNames.length}}个域名
+
+
+
+
+
+
`
+})
+
+// 域名列表
+Vue.component("domains-box", {
+ props: ["v-domains"],
+ data: function () {
+ let domains = this.vDomains
+ if (domains == null) {
+ domains = []
+ }
+ return {
+ domains: domains,
+ isAdding: false,
+ addingDomain: ""
+ }
+ },
+ methods: {
+ add: function () {
+ this.isAdding = true
+ let that = this
+ setTimeout(function () {
+ that.$refs.addingDomain.focus()
+ }, 100)
+ },
+ confirm: function () {
+ let that = this
+
+ // 删除其中的空格
+ this.addingDomain = this.addingDomain.replace(/\s/g, "")
+
+ if (this.addingDomain.length == 0) {
+ teaweb.warn("请输入要添加的域名", function () {
+ that.$refs.addingDomain.focus()
+ })
+ return
+ }
+
+
+ // 基本校验
+ if (this.addingDomain[0] == "~") {
+ let expr = this.addingDomain.substring(1)
+ try {
+ new RegExp(expr)
+ } catch (e) {
+ teaweb.warn("正则表达式错误:" + e.message, function () {
+ that.$refs.addingDomain.focus()
+ })
+ return
+ }
+ }
+
+ this.domains.push(this.addingDomain)
+ this.cancel()
+ },
+ remove: function (index) {
+ this.domains.$remove(index)
+ },
+ cancel: function () {
+ this.isAdding = false
+ this.addingDomain = ""
+ }
+ },
+ template: `
+
+
+
+ [正则]
+ [后缀]
+ [泛域名]
+ {{domain}}
+
+
+
+
+
+
+ +
+
+
`
+})
+
+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"],
+ mounted: function () {
+ let that = this
+ Tea.action("/servers/iplists/levelOptions")
+ .success(function (resp) {
+ that.ipListLevels = resp.data.levels
+ })
+ .post()
+
+ this.loadJS(function () {
+ let box = document.getElementById("actions-box")
+ Sortable.create(box, {
+ draggable: ".label",
+ handle: ".icon.handle",
+ onStart: function () {
+ that.cancel()
+ },
+ onUpdate: function (event) {
+ let labels = box.getElementsByClassName("label")
+ let newConfigs = []
+ for (let i = 0; i < labels.length; i++) {
+ let index = parseInt(labels[i].getAttribute("data-index"))
+ newConfigs.push(that.configs[index])
+ }
+ that.configs = newConfigs
+ }
+ })
+ })
+ },
+ data: function () {
+ if (this.vFirewallPolicy.inbound == null) {
+ this.vFirewallPolicy.inbound = {}
+ }
+ if (this.vFirewallPolicy.inbound.groups == null) {
+ this.vFirewallPolicy.inbound.groups = []
+ }
+
+ let id = 0
+ let configs = []
+ if (this.vActionConfigs != null) {
+ configs = this.vActionConfigs
+ configs.forEach(function (v) {
+ v.id = (id++)
+ })
+ }
+
+ var defaultPageBody = `
+
+
+403 Forbidden
+
+`
+
+
+ return {
+ id: id,
+
+ actions: this.vActions,
+ configs: configs,
+ isAdding: false,
+ editingIndex: -1,
+
+ action: null,
+ actionCode: "",
+ actionOptions: {},
+
+ // IPList相关
+ ipListLevels: [],
+
+ // 动作参数
+ blockTimeout: "",
+ blockScope: "global",
+
+ captchaLife: "",
+ get302Life: "",
+ post307Life: "",
+ recordIPType: "black",
+ recordIPLevel: "critical",
+ recordIPTimeout: "",
+ recordIPListId: 0,
+ recordIPListName: "",
+
+ tagTags: [],
+
+ pageStatus: 403,
+ pageBody: defaultPageBody,
+ defaultPageBody: defaultPageBody,
+
+ goGroupName: "",
+ goGroupId: 0,
+ goGroup: null,
+
+ goSetId: 0,
+ goSetName: ""
+ }
+ },
+ watch: {
+ actionCode: function (code) {
+ this.action = this.actions.$find(function (k, v) {
+ return v.code == code
+ })
+ this.actionOptions = {}
+ },
+ blockTimeout: function (v) {
+ v = parseInt(v)
+ if (isNaN(v)) {
+ this.actionOptions["timeout"] = 0
+ } else {
+ this.actionOptions["timeout"] = v
+ }
+ },
+ blockScope: function (v) {
+ this.actionOptions["scope"] = v
+ },
+ captchaLife: function (v) {
+ v = parseInt(v)
+ if (isNaN(v)) {
+ this.actionOptions["life"] = 0
+ } else {
+ this.actionOptions["life"] = v
+ }
+ },
+ get302Life: function (v) {
+ v = parseInt(v)
+ if (isNaN(v)) {
+ this.actionOptions["life"] = 0
+ } else {
+ this.actionOptions["life"] = v
+ }
+ },
+ post307Life: function (v) {
+ v = parseInt(v)
+ if (isNaN(v)) {
+ this.actionOptions["life"] = 0
+ } else {
+ this.actionOptions["life"] = v
+ }
+ },
+ recordIPType: function (v) {
+ this.recordIPListId = 0
+ },
+ recordIPTimeout: function (v) {
+ v = parseInt(v)
+ if (isNaN(v)) {
+ this.actionOptions["timeout"] = 0
+ } else {
+ this.actionOptions["timeout"] = v
+ }
+ },
+ goGroupId: function (groupId) {
+ let group = this.vFirewallPolicy.inbound.groups.$find(function (k, v) {
+ return v.id == groupId
+ })
+ this.goGroup = group
+ if (group == null) {
+ this.goGroupName = ""
+ } else {
+ this.goGroupName = group.name
+ }
+ this.goSetId = 0
+ this.goSetName = ""
+ },
+ goSetId: function (setId) {
+ if (this.goGroup == null) {
+ return
+ }
+ let set = this.goGroup.sets.$find(function (k, v) {
+ return v.id == setId
+ })
+ if (set == null) {
+ this.goSetId = 0
+ this.goSetName = ""
+ } else {
+ this.goSetName = set.name
+ }
+ }
+ },
+ methods: {
+ add: function () {
+ this.action = null
+ this.actionCode = "block"
+ this.isAdding = true
+ this.actionOptions = {}
+
+ // 动作参数
+ this.blockTimeout = ""
+ this.blockScope = "global"
+ this.captchaLife = ""
+ this.get302Life = ""
+ this.post307Life = ""
+
+ this.recordIPLevel = "critical"
+ this.recordIPType = "black"
+ this.recordIPTimeout = ""
+ this.recordIPListId = 0
+ this.recordIPListName = ""
+
+ this.tagTags = []
+
+ this.pageStatus = 403
+ this.pageBody = this.defaultPageBody
+
+ this.goGroupName = ""
+ this.goGroupId = 0
+ this.goGroup = null
+
+ this.goSetId = 0
+ this.goSetName = ""
+
+ let that = this
+ this.action = this.vActions.$find(function (k, v) {
+ return v.code == that.actionCode
+ })
+
+ // 滚到界面底部
+ this.scroll()
+ },
+ remove: function (index) {
+ this.isAdding = false
+ this.editingIndex = -1
+ this.configs.$remove(index)
+ },
+ update: function (index, config) {
+ if (this.isAdding && this.editingIndex == index) {
+ this.cancel()
+ return
+ }
+
+ this.add()
+
+ this.isAdding = true
+ this.editingIndex = index
+
+ this.actionCode = config.code
+
+ switch (config.code) {
+ case "block":
+ this.blockTimeout = ""
+ if (config.options.timeout != null || config.options.timeout > 0) {
+ this.blockTimeout = config.options.timeout.toString()
+ }
+ if (config.options.scope != null && config.options.scope.length > 0) {
+ this.blockScope = config.options.scope
+ } else {
+ this.blockScope = "global" // 兼容先前版本遗留的默认值
+ }
+ break
+ case "allow":
+ break
+ case "log":
+ break
+ case "captcha":
+ this.captchaLife = ""
+ if (config.options.life != null || config.options.life > 0) {
+ this.captchaLife = config.options.life.toString()
+ }
+ break
+ case "notify":
+ break
+ case "get_302":
+ this.get302Life = ""
+ if (config.options.life != null || config.options.life > 0) {
+ this.get302Life = config.options.life.toString()
+ }
+ break
+ case "post_307":
+ this.post307Life = ""
+ if (config.options.life != null || config.options.life > 0) {
+ this.post307Life = config.options.life.toString()
+ }
+ break;
+ case "record_ip":
+ if (config.options != null) {
+ this.recordIPLevel = config.options.level
+ this.recordIPType = config.options.type
+ if (config.options.timeout > 0) {
+ this.recordIPTimeout = config.options.timeout.toString()
+ }
+ let that = this
+
+ // VUE需要在函数执行完之后才会调用watch函数,这样会导致设置的值被覆盖,所以这里使用setTimeout
+ setTimeout(function () {
+ that.recordIPListId = config.options.ipListId
+ that.recordIPListName = config.options.ipListName
+ })
+ }
+ break
+ case "tag":
+ this.tagTags = []
+ if (config.options.tags != null) {
+ this.tagTags = config.options.tags
+ }
+ break
+ case "page":
+ this.pageStatus = 403
+ this.pageBody = this.defaultPageBody
+ if (config.options.status != null) {
+ this.pageStatus = config.options.status
+ }
+ if (config.options.body != null) {
+ this.pageBody = config.options.body
+ }
+
+ break
+ case "go_group":
+ if (config.options != null) {
+ this.goGroupName = config.options.groupName
+ this.goGroupId = config.options.groupId
+ this.goGroup = this.vFirewallPolicy.inbound.groups.$find(function (k, v) {
+ return v.id == config.options.groupId
+ })
+ }
+ break
+ case "go_set":
+ if (config.options != null) {
+ this.goGroupName = config.options.groupName
+ this.goGroupId = config.options.groupId
+ this.goGroup = this.vFirewallPolicy.inbound.groups.$find(function (k, v) {
+ return v.id == config.options.groupId
+ })
+
+ // VUE需要在函数执行完之后才会调用watch函数,这样会导致设置的值被覆盖,所以这里使用setTimeout
+ let that = this
+ setTimeout(function () {
+ that.goSetId = config.options.setId
+ if (that.goGroup != null) {
+ let set = that.goGroup.sets.$find(function (k, v) {
+ return v.id == config.options.setId
+ })
+ if (set != null) {
+ that.goSetName = set.name
+ }
+ }
+ })
+ }
+ break
+ }
+
+ // 滚到界面底部
+ this.scroll()
+ },
+ cancel: function () {
+ this.isAdding = false
+ this.editingIndex = -1
+ },
+ confirm: function () {
+ if (this.action == null) {
+ return
+ }
+
+ if (this.actionOptions == null) {
+ this.actionOptions = {}
+ }
+
+ // record_ip
+ if (this.actionCode == "record_ip") {
+ let timeout = parseInt(this.recordIPTimeout)
+ if (isNaN(timeout)) {
+ timeout = 0
+ }
+ if (this.recordIPListId <= 0) {
+ return
+ }
+ this.actionOptions = {
+ type: this.recordIPType,
+ level: this.recordIPLevel,
+ timeout: timeout,
+ ipListId: this.recordIPListId,
+ ipListName: this.recordIPListName
+ }
+ } else if (this.actionCode == "tag") { // tag
+ if (this.tagTags == null || this.tagTags.length == 0) {
+ return
+ }
+ this.actionOptions = {
+ tags: this.tagTags
+ }
+ } else if (this.actionCode == "page") {
+ let pageStatus = this.pageStatus.toString()
+ if (!pageStatus.match(/^\d{3}$/)) {
+ pageStatus = 403
+ } else {
+ pageStatus = parseInt(pageStatus)
+ }
+
+ this.actionOptions = {
+ status: pageStatus,
+ body: this.pageBody
+ }
+ } else if (this.actionCode == "go_group") { // go_group
+ let groupId = this.goGroupId
+ if (typeof (groupId) == "string") {
+ groupId = parseInt(groupId)
+ if (isNaN(groupId)) {
+ groupId = 0
+ }
+ }
+ if (groupId <= 0) {
+ return
+ }
+ this.actionOptions = {
+ groupId: groupId.toString(),
+ groupName: this.goGroupName
+ }
+ } else if (this.actionCode == "go_set") { // go_set
+ let groupId = this.goGroupId
+ if (typeof (groupId) == "string") {
+ groupId = parseInt(groupId)
+ if (isNaN(groupId)) {
+ groupId = 0
+ }
+ }
+
+ let setId = this.goSetId
+ if (typeof (setId) == "string") {
+ setId = parseInt(setId)
+ if (isNaN(setId)) {
+ setId = 0
+ }
+ }
+ if (setId <= 0) {
+ return
+ }
+ this.actionOptions = {
+ groupId: groupId.toString(),
+ groupName: this.goGroupName,
+ setId: setId.toString(),
+ setName: this.goSetName
+ }
+ }
+
+ let options = {}
+ for (let k in this.actionOptions) {
+ if (this.actionOptions.hasOwnProperty(k)) {
+ options[k] = this.actionOptions[k]
+ }
+ }
+ if (this.editingIndex > -1) {
+ this.configs[this.editingIndex] = {
+ id: this.configs[this.editingIndex].id,
+ code: this.actionCode,
+ name: this.action.name,
+ options: options
+ }
+ } else {
+ this.configs.push({
+ id: (this.id++),
+ code: this.actionCode,
+ name: this.action.name,
+ options: options
+ })
+ }
+
+ this.cancel()
+ },
+ removeRecordIPList: function () {
+ this.recordIPListId = 0
+ },
+ selectRecordIPList: function () {
+ let that = this
+ teaweb.popup("/servers/iplists/selectPopup?type=" + this.recordIPType, {
+ width: "50em",
+ height: "30em",
+ callback: function (resp) {
+ that.recordIPListId = resp.data.list.id
+ that.recordIPListName = resp.data.list.name
+ }
+ })
+ },
+ changeTags: function (tags) {
+ this.tagTags = tags
+ },
+ loadJS: function (callback) {
+ if (typeof Sortable != "undefined") {
+ callback()
+ return
+ }
+
+ // 引入js
+ let jsFile = document.createElement("script")
+ jsFile.setAttribute("src", "/js/sortable.min.js")
+ jsFile.addEventListener("load", function () {
+ callback()
+ })
+ document.head.appendChild(jsFile)
+ },
+ scroll: function () {
+ setTimeout(function () {
+ let mainDiv = document.getElementsByClassName("main")
+ if (mainDiv.length > 0) {
+ mainDiv[0].scrollTo(0, 1000)
+ }
+ }, 10)
+ }
+ },
+ template: `
+
+
+
+ {{config.name}}
({{config.code.toUpperCase()}})
+
+
+
:有效期{{config.options.timeout}}秒
+
+
+
:有效期{{config.options.life}}秒
+
+
+
:有效期{{config.options.life}}秒
+
+
+
:有效期{{config.options.life}}秒
+
+
+
:{{config.options.ipListName}}
+
+
+
:{{config.options.tags.join(", ")}}
+
+
+
:[{{config.options.status}}]
+
+
+
:{{config.options.groupName}}
+
+
+
:{{config.options.groupName}} / {{config.options.setName}}
+
+
+
+
+ [所有服务]
+ [当前服务]
+
+
+
+
+
+
+
+
+
+ +
+
+
+
`
+})
+
+// 认证设置
+Vue.component("http-auth-config-box", {
+ props: ["v-auth-config", "v-is-location"],
+ data: function () {
+ let authConfig = this.vAuthConfig
+ if (authConfig == null) {
+ authConfig = {
+ isPrior: false,
+ isOn: false
+ }
+ }
+ if (authConfig.policyRefs == null) {
+ authConfig.policyRefs = []
+ }
+ return {
+ authConfig: authConfig
+ }
+ },
+ methods: {
+ isOn: function () {
+ return (!this.vIsLocation || this.authConfig.isPrior) && this.authConfig.isOn
+ },
+ add: function () {
+ let that = this
+ teaweb.popup("/servers/server/settings/access/createPopup", {
+ callback: function (resp) {
+ that.authConfig.policyRefs.push(resp.data.policyRef)
+ },
+ height: "28em"
+ })
+ },
+ update: function (index, policyId) {
+ let that = this
+ teaweb.popup("/servers/server/settings/access/updatePopup?policyId=" + policyId, {
+ callback: function (resp) {
+ teaweb.success("保存成功", function () {
+ teaweb.reload()
+ })
+ },
+ height: "28em"
+ })
+ },
+ remove: function (index) {
+ this.authConfig.policyRefs.$remove(index)
+ },
+ methodName: function (methodType) {
+ switch (methodType) {
+ case "basicAuth":
+ return "BasicAuth"
+ case "subRequest":
+ return "子请求"
+ }
+ return ""
+ }
+ },
+ template: `
+
+
+
+
+
+
认证方式
+
+
+
+ 名称
+ 认证方法
+ 参数
+ 状态
+ 操作
+
+
+
+
+ {{ref.authPolicy.name}}
+
+ {{methodName(ref.authPolicy.type)}}
+
+
+ {{ref.authPolicy.params.users.length}}个用户
+
+ [{{ref.authPolicy.params.method}}]
+ {{ref.authPolicy.params.url}}
+
+
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+添加认证方式
+
+
+
`
+})
+
+Vue.component("user-selector", {
+ mounted: function () {
+ let that = this
+
+ Tea.action("/servers/users/options")
+ .post()
+ .success(function (resp) {
+ that.users = resp.data.users
+ })
+ },
+ props: ["v-user-id"],
+ data: function () {
+ let userId = this.vUserId
+ if (userId == null) {
+ userId = 0
+ }
+ return {
+ users: [],
+ userId: userId
+ }
+ },
+ watch: {
+ userId: function (v) {
+ this.$emit("change", v)
+ }
+ },
+ template: `
+
+ [选择用户]
+ {{user.fullname}} ({{user.username}})
+
+
`
+})
+
+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 = "request"
+ let hash = window.location.hash
+ if (hash == "#response") {
+ type = "response"
+ }
+
+ // ref
+ let requestHeaderRef = this.vRequestHeaderRef
+ if (requestHeaderRef == null) {
+ requestHeaderRef = {
+ isPrior: false,
+ isOn: true,
+ headerPolicyId: 0
+ }
+ }
+
+ let responseHeaderRef = this.vResponseHeaderRef
+ if (responseHeaderRef == null) {
+ responseHeaderRef = {
+ isPrior: false,
+ isOn: true,
+ headerPolicyId: 0
+ }
+ }
+
+ // 请求相关
+ let requestSettingHeaders = []
+ let requestDeletingHeaders = []
+
+ let requestPolicy = this.vRequestHeaderPolicy
+ if (requestPolicy != null) {
+ if (requestPolicy.setHeaders != null) {
+ requestSettingHeaders = requestPolicy.setHeaders
+ }
+ if (requestPolicy.deleteHeaders != null) {
+ requestDeletingHeaders = requestPolicy.deleteHeaders
+ }
+ }
+
+ // 响应相关
+ let responseSettingHeaders = []
+ let responseDeletingHeaders = []
+
+ let responsePolicy = this.vResponseHeaderPolicy
+ if (responsePolicy != null) {
+ if (responsePolicy.setHeaders != null) {
+ responseSettingHeaders = responsePolicy.setHeaders
+ }
+ if (responsePolicy.deleteHeaders != null) {
+ responseDeletingHeaders = responsePolicy.deleteHeaders
+ }
+ }
+
+ return {
+ type: type,
+ typeName: (type == "request") ? "请求" : "响应",
+ requestHeaderRef: requestHeaderRef,
+ responseHeaderRef: responseHeaderRef,
+ requestSettingHeaders: requestSettingHeaders,
+ requestDeletingHeaders: requestDeletingHeaders,
+ responseSettingHeaders: responseSettingHeaders,
+ responseDeletingHeaders: responseDeletingHeaders
+ }
+ },
+ methods: {
+ selectType: function (type) {
+ this.type = type
+ window.location.hash = "#" + type
+ window.location.reload()
+ },
+ addSettingHeader: function (policyId) {
+ teaweb.popup("/servers/server/settings/headers/createSetPopup?" + this.vParams + "&headerPolicyId=" + policyId, {
+ callback: function () {
+ window.location.reload()
+ }
+ })
+ },
+ addDeletingHeader: function (policyId, type) {
+ teaweb.popup("/servers/server/settings/headers/createDeletePopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + type, {
+ callback: function () {
+ window.location.reload()
+ }
+ })
+ },
+ updateSettingPopup: function (policyId, headerId) {
+ teaweb.popup("/servers/server/settings/headers/updateSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&headerId=" + headerId, {
+ callback: function () {
+ window.location.reload()
+ }
+ })
+ },
+ deleteDeletingHeader: function (policyId, headerName) {
+ teaweb.confirm("确定要删除'" + headerName + "'吗?", function () {
+ Tea.action("/servers/server/settings/headers/deleteDeletingHeader")
+ .params({
+ headerPolicyId: policyId,
+ headerName: headerName
+ })
+ .post()
+ .refresh()
+ })
+ },
+ deleteHeader: function (policyId, type, headerId) {
+ teaweb.confirm("确定要删除此Header吗?", function () {
+ this.$post("/servers/server/settings/headers/delete")
+ .params({
+ headerPolicyId: policyId,
+ type: type,
+ headerId: headerId
+ })
+ .refresh()
+ }
+ )
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
由于已经在当前服务分组 中进行了对应的配置,在这里的配置将不会生效。
+
+
+
+
+
+
+
+ 名称
+ 值
+ 操作
+
+
+
+ {{header.name}}
+ {{header.value}}
+ 修改 删除
+
+
+
+
删除请求Header
+
+
+
+ 需要删除的Header
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
由于已经在当前服务分组 中进行了对应的配置,在这里的配置将不会生效。
+
+
+
+
+
+
+
+
+ 名称
+ 值
+ 操作
+
+
+
+ {{header.name}}
+ {{header.value}}
+ 修改 删除
+
+
+
+
删除响应Header
+
+
+
+ 需要删除的Header
+
+
+ +
+
+
+
+
+
+
`
+})
+
+Vue.component("http-cache-policy-selector", {
+ props: ["v-cache-policy"],
+ mounted: function () {
+ let that = this
+ Tea.action("/servers/components/cache/count")
+ .post()
+ .success(function (resp) {
+ that.count = resp.data.count
+ })
+ },
+ data: function () {
+ let cachePolicy = this.vCachePolicy
+ return {
+ count: 0,
+ cachePolicy: cachePolicy
+ }
+ },
+ methods: {
+ remove: function () {
+ this.cachePolicy = null
+ },
+ select: function () {
+ let that = this
+ teaweb.popup("/servers/components/cache/selectPopup", {
+ callback: function (resp) {
+ that.cachePolicy = resp.data.cachePolicy
+ }
+ })
+ },
+ create: function () {
+ let that = this
+ teaweb.popup("/servers/components/cache/createPopup", {
+ height: "26em",
+ callback: function (resp) {
+ that.cachePolicy = resp.data.cachePolicy
+ }
+ })
+ }
+ },
+ template: `
+
+
+ {{cachePolicy.name}}
+
+
+
`
+})
+
+Vue.component("http-pages-and-shutdown-box", {
+ props: ["v-pages", "v-shutdown-config", "v-is-location"],
+ data: function () {
+ let pages = []
+ if (this.vPages != null) {
+ pages = this.vPages
+ }
+ let shutdownConfig = {
+ isPrior: false,
+ isOn: false,
+ bodyType: "url",
+ url: "",
+ body: "",
+ status: 0
+ }
+ if (this.vShutdownConfig != null) {
+ if (this.vShutdownConfig.body == null) {
+ this.vShutdownConfig.body = ""
+ }
+ if (this.vShutdownConfig.bodyType == null) {
+ this.vShutdownConfig.bodyType = "url"
+ }
+ shutdownConfig = this.vShutdownConfig
+ }
+
+ let shutdownStatus = ""
+ if (shutdownConfig.status > 0) {
+ shutdownStatus = shutdownConfig.status.toString()
+ }
+
+ return {
+ pages: pages,
+ shutdownConfig: shutdownConfig,
+ shutdownStatus: shutdownStatus
+ }
+ },
+ watch: {
+ shutdownStatus: function (status) {
+ let statusInt = parseInt(status)
+ if (!isNaN(statusInt) && statusInt > 0 && statusInt < 1000) {
+ this.shutdownConfig.status = statusInt
+ } else {
+ this.shutdownConfig.status = 0
+ }
+ }
+ },
+ methods: {
+ addPage: function () {
+ let that = this
+ teaweb.popup("/servers/server/settings/pages/createPopup", {
+ height: "26em",
+ callback: function (resp) {
+ that.pages.push(resp.data.page)
+ }
+ })
+ },
+ updatePage: function (pageIndex, pageId) {
+ let that = this
+ teaweb.popup("/servers/server/settings/pages/updatePopup?pageId=" + pageId, {
+ height: "26em",
+ callback: function (resp) {
+ Vue.set(that.pages, pageIndex, resp.data.page)
+ }
+ })
+ },
+ removePage: function (pageIndex) {
+ let that = this
+ teaweb.confirm("确定要移除此页面吗?", function () {
+ that.pages.$remove(pageIndex)
+ })
+ },
+ addShutdownHTMLTemplate: function () {
+ this.shutdownConfig.body = `
+
+
+\t升级中
+\t
+
+
+
+网站升级中
+为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。
+
+
+
+
+`
+ }
+ },
+ 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", "deflate"],
+ level: 5,
+ decompressData: false,
+ gzipRef: null,
+ deflateRef: null,
+ brotliRef: null,
+ minLength: {count: 0, "unit": "kb"},
+ maxLength: {count: 0, "unit": "kb"},
+ mimeTypes: ["text/*", "application/*", "font/*"],
+ extensions: [".js", ".json", ".html", ".htm", ".xml", ".css", ".woff2", ".txt"],
+ conds: null
+ }
+ }
+
+ if (config.types == null) {
+ config.types = []
+ }
+ if (config.mimeTypes == null) {
+ config.mimeTypes = []
+ }
+ if (config.extensions == null) {
+ config.extensions = []
+ }
+
+ let allTypes = [
+ {
+ name: "Gzip",
+ code: "gzip",
+ isOn: true
+ },
+ {
+ name: "Deflate",
+ code: "deflate",
+ isOn: true
+ },
+ {
+ name: "Brotli",
+ code: "brotli",
+ isOn: true
+ }
+ ]
+
+ let configTypes = []
+ config.types.forEach(function (typeCode) {
+ allTypes.forEach(function (t) {
+ if (typeCode == t.code) {
+ t.isOn = true
+ configTypes.push(t)
+ }
+ })
+ })
+ allTypes.forEach(function (t) {
+ if (!config.types.$contains(t.code)) {
+ t.isOn = false
+ configTypes.push(t)
+ }
+ })
+
+ return {
+ config: config,
+ moreOptionsVisible: false,
+ allTypes: configTypes
+ }
+ },
+ watch: {
+ "config.level": function (v) {
+ let level = parseInt(v)
+ if (isNaN(level)) {
+ level = 1
+ } else if (level < 1) {
+ level = 1
+ } else if (level > 10) {
+ level = 10
+ }
+ this.config.level = level
+ }
+ },
+ methods: {
+ isOn: function () {
+ return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
+ },
+ changeExtensions: function (values) {
+ values.forEach(function (v, k) {
+ if (v.length > 0 && v[0] != ".") {
+ values[k] = "." + v
+ }
+ })
+ this.config.extensions = values
+ },
+ changeMimeTypes: function (values) {
+ this.config.mimeTypes = values
+ },
+ changeAdvancedVisible: function () {
+ this.moreOptionsVisible = !this.moreOptionsVisible
+ },
+ changeConds: function (conds) {
+ this.config.conds = conds
+ },
+ changeType: function () {
+ this.config.types = []
+ let that = this
+ this.allTypes.forEach(function (v) {
+ if (v.isOn) {
+ that.config.types.push(v.code)
+ }
+ })
+ },
+ initSortableTypes: function () {
+ let box = document.querySelector("#compression-types-box")
+ let that = this
+ Sortable.create(box, {
+ draggable: ".checkbox",
+ handle: ".icon.handle",
+ onStart: function () {
+
+ },
+ onUpdate: function (event) {
+ let checkboxes = box.querySelectorAll(".checkbox")
+ let codes = []
+ checkboxes.forEach(function (checkbox) {
+ let code = checkbox.getAttribute("data-code")
+ codes.push(code)
+ })
+ that.config.types = codes
+ }
+ })
+ }
+ },
+ template: ``
+})
+
+Vue.component("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"],
+ data: function () {
+ return {
+ isPrior: this.vConfig.isPrior
+ }
+ },
+ watch: {
+ isPrior: function (v) {
+ this.vConfig.isPrior = v
+ }
+ },
+ template: `
+
+ 打开独立配置
+
+
+
+
+
+
+
+
+ `
+})
+
+Vue.component("http-charsets-box", {
+ props: ["v-usual-charsets", "v-all-charsets", "v-charset-config", "v-is-location", "v-is-group"],
+ data: function () {
+ let charsetConfig = this.vCharsetConfig
+ if (charsetConfig == null) {
+ charsetConfig = {
+ isPrior: false,
+ isOn: false,
+ charset: "",
+ isUpper: false
+ }
+ }
+ return {
+ charsetConfig: charsetConfig,
+ advancedVisible: false
+ }
+ },
+ methods: {
+ changeAdvancedVisible: function (v) {
+ this.advancedVisible = v
+ }
+ },
+ template: ``
+})
+
+Vue.component("http-access-log-box", {
+ props: ["v-access-log", "v-keyword", "v-show-server-link"],
+ data: function () {
+ let accessLog = this.vAccessLog
+ if (accessLog.header != null && accessLog.header.Upgrade != null && accessLog.header.Upgrade.values != null && accessLog.header.Upgrade.values.$contains("websocket")) {
+ if (accessLog.scheme == "http") {
+ accessLog.scheme = "ws"
+ } else if (accessLog.scheme == "https") {
+ accessLog.scheme = "wss"
+ }
+ }
+
+ return {
+ accessLog: accessLog
+ }
+ },
+ methods: {
+ formatCost: function (seconds) {
+ var s = (seconds * 1000).toString();
+ var pieces = s.split(".");
+ if (pieces.length < 2) {
+ return s;
+ }
+
+ return pieces[0] + "." + pieces[1].substr(0, 3);
+ },
+ showLog: function () {
+ let that = this
+ let requestId = this.accessLog.requestId
+ this.$parent.$children.forEach(function (v) {
+ if (v.deselect != null) {
+ v.deselect()
+ }
+ })
+ this.select()
+ teaweb.popup("/servers/server/log/viewPopup?requestId=" + requestId, {
+ width: "50em",
+ height: "28em",
+ onClose: function () {
+ that.deselect()
+ }
+ })
+ },
+ select: function () {
+ this.$refs.box.parentNode.style.cssText = "background: rgba(0, 0, 0, 0.1)"
+ },
+ deselect: function () {
+ this.$refs.box.parentNode.style.cssText = ""
+ }
+ },
+ template: `
+
[{{accessLog.node.name}}节点 ]
+
[服务]
+
[{{accessLog.region}}] {{accessLog.remoteAddr}} [{{accessLog.timeLocal}}]
"{{accessLog.requestMethod}} {{accessLog.scheme}}://{{accessLog.host}} {{accessLog.requestURI}} {{accessLog.proto}}" {{accessLog.status}} cache hit waf {{accessLog.firewallActions}} - {{tag}} - 耗时:{{formatCost(accessLog.requestTime)}} ms
({{accessLog.humanTime}})
+
+
`
+})
+
+Vue.component("http-access-log-config-box", {
+ props: ["v-access-log-config", "v-fields", "v-default-field-codes", "v-is-location", "v-is-group"],
+ data: function () {
+ let that = this
+
+ // 初始化
+ setTimeout(function () {
+ that.changeFields()
+ }, 100)
+
+ let accessLog = {
+ isPrior: false,
+ isOn: false,
+ fields: [],
+ status1: true,
+ status2: true,
+ status3: true,
+ status4: true,
+ status5: true,
+
+ firewallOnly: 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
+ }
+ },
+ methods: {
+ changeFields: function () {
+ this.accessLog.fields = this.vFields.filter(function (v) {
+ return v.isChecked
+ }).map(function (v) {
+ return v.code
+ })
+ }
+ },
+ template: `
+
+
+
+
+
WAF相关
+
+
+ 是否只记录WAF相关日志
+
+
+
+
+
+
+
+
+
`
+})
+
+// 显示流量限制说明
+Vue.component("traffic-limit-view", {
+ props: ["v-traffic-limit"],
+ data: function () {
+ return {
+ config: this.vTrafficLimit
+ }
+ },
+ template: `
+
+ 日流量限制:{{config.dailySize.count}}{{config.dailySize.unit.toUpperCase()}}
+ 月流量限制:{{config.monthlySize.count}}{{config.monthlySize.unit.toUpperCase()}}
+
+
没有限制。
+
`
+})
+
+// 基本认证用户配置
+Vue.component("http-auth-basic-auth-user-box", {
+ props: ["v-users"],
+ data: function () {
+ let users = this.vUsers
+ if (users == null) {
+ users = []
+ }
+ return {
+ users: users,
+ isAdding: false,
+ updatingIndex: -1,
+
+ username: "",
+ password: ""
+ }
+ },
+ methods: {
+ add: function () {
+ this.isAdding = true
+ this.username = ""
+ this.password = ""
+
+ let that = this
+ setTimeout(function () {
+ that.$refs.username.focus()
+ }, 100)
+ },
+ cancel: function () {
+ this.isAdding = false
+ this.updatingIndex = -1
+ },
+ confirm: function () {
+ let that = this
+ if (this.username.length == 0) {
+ teaweb.warn("请输入用户名", function () {
+ that.$refs.username.focus()
+ })
+ return
+ }
+ if (this.password.length == 0) {
+ teaweb.warn("请输入密码", function () {
+ that.$refs.password.focus()
+ })
+ return
+ }
+ if (this.updatingIndex < 0) {
+ this.users.push({
+ username: this.username,
+ password: this.password
+ })
+ } else {
+ this.users[this.updatingIndex].username = this.username
+ this.users[this.updatingIndex].password = this.password
+ }
+ this.cancel()
+ },
+ update: function (index, user) {
+ this.updatingIndex = index
+
+ this.isAdding = true
+ this.username = user.username
+ this.password = user.password
+
+ let that = this
+ setTimeout(function () {
+ that.$refs.username.focus()
+ }, 100)
+ },
+ remove: function (index) {
+ this.users.$remove(index)
+ }
+ },
+ template: ``
+})
+
+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}}
+
BREAK
+
+
+
自动跳转HTTPS
+
+
+
文档根目录
+
+
+
反向代理
+
+
+
+
+
+
CACHE
+
+
+
{{location.web.charset.charset}}
+
+
+
+
+
+
+
+
+
Gzip:{{location.web.gzip.level}}
+
+
+
请求Header
+
响应Header
+
+
+
Websocket
+
+
+
+
PAGE [状态码{{page.status[0]}}] -> {{page.url}}
+
+
+ 临时关闭
+
+
+
+
+
+ REWRITE {{rewriteRule.pattern}} -> {{rewriteRule.replace}}
+
+
+
`
+})
+
+Vue.component("http-location-labels-label", {
+ props: ["v-class", "v-href"],
+ template: ` `
+})
+
+Vue.component("http-gzip-box", {
+ props: ["v-gzip-config", "v-gzip-ref", "v-is-location"],
+ data: function () {
+ let gzip = this.vGzipConfig
+ if (gzip == null) {
+ gzip = {
+ isOn: true,
+ level: 0,
+ minLength: null,
+ maxLength: null,
+ conds: null
+ }
+ }
+
+ return {
+ gzip: gzip,
+ advancedVisible: false
+ }
+ },
+ methods: {
+ isOn: function () {
+ return (!this.vIsLocation || this.vGzipRef.isPrior) && this.vGzipRef.isOn
+ },
+ changeAdvancedVisible: function (v) {
+ this.advancedVisible = v
+ }
+ },
+ template: ``
+})
+
+Vue.component("ssl-certs-view", {
+ props: ["v-certs"],
+ data: function () {
+ let certs = this.vCerts
+ if (certs == null) {
+ certs = []
+ }
+ return {
+ certs: certs
+ }
+ },
+ methods: {
+ // 格式化时间
+ formatTime: function (timestamp) {
+ return new Date(timestamp * 1000).format("Y-m-d")
+ },
+
+ // 查看详情
+ viewCert: function (certId) {
+ teaweb.popup("/servers/certs/certPopup?certId=" + certId, {
+ height: "28em",
+ width: "48em"
+ })
+ }
+ },
+ template: `
+
+
+ {{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}}
+
+
+
`
+})
+
+Vue.component("reverse-proxy-box", {
+ props: ["v-reverse-proxy-ref", "v-reverse-proxy-config", "v-is-location", "v-is-group", "v-family"],
+ data: function () {
+ let reverseProxyRef = this.vReverseProxyRef
+ if (reverseProxyRef == null) {
+ reverseProxyRef = {
+ isPrior: false,
+ isOn: false,
+ reverseProxyId: 0
+ }
+ }
+
+ let reverseProxyConfig = this.vReverseProxyConfig
+ if (reverseProxyConfig == null) {
+ reverseProxyConfig = {
+ requestPath: "",
+ stripPrefix: "",
+ requestURI: "",
+ requestHost: "",
+ requestHostType: 0,
+ addHeaders: [],
+ connTimeout: {count: 0, unit: "second"},
+ readTimeout: {count: 0, unit: "second"},
+ idleTimeout: {count: 0, unit: "second"},
+ maxConns: 0,
+ maxIdleConns: 0
+ }
+ }
+ 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: `
+
+
+
+
+
+
+ [请选择]
+ {{option.name}}
+
+
+
+
+
+
+ +
+
+
+
`
+})
+
+Vue.component("http-remote-addr-config-box", {
+ props: ["v-remote-addr-config", "v-is-location", "v-is-group"],
+ data: function () {
+ let config = this.vRemoteAddrConfig
+ if (config == null) {
+ config = {
+ isPrior: false,
+ isOn: false,
+ value: "${rawRemoteAddr}",
+ isCustomized: false
+ }
+ }
+
+ let optionValue = ""
+ if (!config.isCustomized && (config.value == "${remoteAddr}" || config.value == "${rawRemoteAddr}")) {
+ optionValue = config.value
+ }
+
+ return {
+ config: config,
+ options: [
+ {
+ name: "直接获取",
+ description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候可以直接从连接中读取到真实的IP地址。",
+ value: "${rawRemoteAddr}"
+ },
+ {
+ name: "从上级代理中获取",
+ description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\",这时候只能从上级代理中获取传递的IP地址。",
+ value: "${remoteAddr}"
+ },
+ {
+ name: "[自定义]",
+ description: "通过自定义变量来获取客户端真实的IP地址。",
+ value: ""
+ }
+ ],
+ optionValue: optionValue
+ }
+ },
+ methods: {
+ isOn: function () {
+ return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
+ },
+ changeOptionValue: function () {
+ if (this.optionValue.length > 0) {
+ this.config.value = this.optionValue
+ this.config.isCustomized = false
+ } else {
+ this.config.isCustomized = true
+ }
+ }
+ },
+ template: ``
+})
+
+// 访问日志搜索框
+Vue.component("http-access-log-search-box", {
+ props: ["v-ip", "v-domain", "v-keyword"],
+ 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
+ }
+ },
+ 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)
+ }
+ }
+ },
+ 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: `
+
+
+
+
+
+
+ [选择对象]
+ {{def.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
`
+})
+
+Vue.component("http-web-root-box", {
+ props: ["v-root-config", "v-is-location", "v-is-group"],
+ data: function () {
+ let rootConfig = this.vRootConfig
+ if (rootConfig == null) {
+ rootConfig = {
+ isPrior: false,
+ isOn: true,
+ dir: "",
+ indexes: [],
+ stripPrefix: "",
+ decodePath: false,
+ isBreak: false
+ }
+ }
+ if (rootConfig.indexes == null) {
+ rootConfig.indexes = []
+ }
+ return {
+ rootConfig: rootConfig,
+ advancedVisible: false
+ }
+ },
+ methods: {
+ changeAdvancedVisible: function (v) {
+ this.advancedVisible = v
+ },
+ addIndex: function () {
+ let that = this
+ teaweb.popup("/servers/server/settings/web/createIndex", {
+ height: "10em",
+ callback: function (resp) {
+ that.rootConfig.indexes.push(resp.data.index)
+ }
+ })
+ },
+ removeIndex: function (i) {
+ this.rootConfig.indexes.$remove(i)
+ },
+ isOn: function () {
+ return ((!this.vIsLocation && !this.vIsGroup) || this.rootConfig.isPrior) && this.rootConfig.isOn
+ }
+ },
+ template: ``
+})
+
+Vue.component("http-webp-config-box", {
+ props: ["v-webp-config", "v-is-location", "v-is-group"],
+ 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
+ }
+ },
+ 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
+ }
+ }
+ },
+ template: `
+`
+})
+
+Vue.component("http-firewall-rules-box", {
+ props: ["v-rules", "v-type"],
+ data: function () {
+ let rules = this.vRules
+ if (rules == null) {
+ rules = []
+ }
+ return {
+ rules: rules
+ }
+ },
+ methods: {
+ addRule: function () {
+ window.UPDATING_RULE = null
+ let that = this
+ teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
+ callback: function (resp) {
+ that.rules.push(resp.data.rule)
+ }
+ })
+ },
+ updateRule: function (index, rule) {
+ window.UPDATING_RULE = rule
+ let that = this
+ teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
+ callback: function (resp) {
+ Vue.set(that.rules, index, resp.data.rule)
+ }
+ })
+ },
+ removeRule: function (index) {
+ let that = this
+ teaweb.confirm("确定要删除此规则吗?", function () {
+ that.rules.$remove(index)
+ })
+ }
+ },
+ template: `
+
+
+
+ {{rule.name}}[{{rule.param}}]
+
+
+
+ {{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求
+
+
+
+
+ {{rule.checkpointOptions.allowDomains}}
+
+
+
+ | {{paramFilter.code}} {{rule.operator}} {{rule.value}}
+
+
+
+
+
+
+
+
+
+
`
+})
+
+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: ``
+})
+
+// URL扩展名条件
+Vue.component("http-cond-url-extension", {
+ props: ["v-cond"],
+ data: function () {
+ let cond = {
+ isRequest: true,
+ param: "${requestPathExtension}",
+ operator: "in",
+ value: "[]"
+ }
+ if (this.vCond != null && this.vCond.param == cond.param) {
+ cond.value = this.vCond.value
+ }
+
+ let extensions = []
+ try {
+ extensions = JSON.parse(cond.value)
+ } catch (e) {
+
+ }
+
+ return {
+ cond: cond,
+ extensions: extensions, // TODO 可以拖动排序
+
+ isAdding: false,
+ addingExt: ""
+ }
+ },
+ watch: {
+ extensions: function () {
+ this.cond.value = JSON.stringify(this.extensions)
+ }
+ },
+ methods: {
+ addExt: function () {
+ this.isAdding = !this.isAdding
+
+ if (this.isAdding) {
+ let that = this
+ setTimeout(function () {
+ that.$refs.addingExt.focus()
+ }, 100)
+ }
+ },
+ cancelAdding: function () {
+ this.isAdding = false
+ this.addingExt = ""
+ },
+ confirmAdding: function () {
+ // TODO 做更详细的校验
+ // TODO 如果有重复的则提示之
+
+ if (this.addingExt.length == 0) {
+ return
+ }
+ if (this.addingExt[0] != ".") {
+ this.addingExt = "." + this.addingExt
+ }
+ this.addingExt = this.addingExt.replace(/\s+/g, "").toLowerCase()
+ this.extensions.push(this.addingExt)
+
+ // 清除状态
+ this.cancelAdding()
+ },
+ removeExt: function (index) {
+ this.extensions.$remove(index)
+ }
+ },
+ template: ``
+})
+
+// 根据URL前缀
+Vue.component("http-cond-url-prefix", {
+ props: ["v-cond"],
+ data: function () {
+ let cond = {
+ isRequest: true,
+ param: "${requestPath}",
+ operator: "prefix",
+ value: ""
+ }
+ if (this.vCond != null && typeof (this.vCond.value) == "string") {
+ cond.value = this.vCond.value
+ }
+ return {
+ cond: cond
+ }
+ },
+ template: `
+
+
+
+
`
+})
+
+Vue.component("http-cond-url-not-prefix", {
+ props: ["v-cond"],
+ data: function () {
+ let cond = {
+ isRequest: true,
+ param: "${requestPath}",
+ operator: "prefix",
+ value: "",
+ isReverse: true
+ }
+ if (this.vCond != null && typeof this.vCond.value == "string") {
+ cond.value = this.vCond.value
+ }
+ return {
+ cond: cond
+ }
+ },
+ template: `
+
+
+
+
`
+})
+
+// URL精准匹配
+Vue.component("http-cond-url-eq", {
+ props: ["v-cond"],
+ data: function () {
+ let cond = {
+ isRequest: true,
+ param: "${requestPath}",
+ operator: "eq",
+ value: ""
+ }
+ if (this.vCond != null && typeof this.vCond.value == "string") {
+ cond.value = this.vCond.value
+ }
+ return {
+ cond: cond
+ }
+ },
+ template: `
+
+
+
+
`
+})
+
+Vue.component("http-cond-url-not-eq", {
+ props: ["v-cond"],
+ data: function () {
+ let cond = {
+ isRequest: true,
+ param: "${requestPath}",
+ operator: "eq",
+ value: "",
+ isReverse: true
+ }
+ if (this.vCond != null && typeof this.vCond.value == "string") {
+ cond.value = this.vCond.value
+ }
+ return {
+ cond: cond
+ }
+ },
+ template: `
+
+
+
+
`
+})
+
+// URL正则匹配
+Vue.component("http-cond-url-regexp", {
+ props: ["v-cond"],
+ data: function () {
+ let cond = {
+ isRequest: true,
+ param: "${requestPath}",
+ operator: "regexp",
+ value: ""
+ }
+ if (this.vCond != null && typeof this.vCond.value == "string") {
+ cond.value = this.vCond.value
+ }
+ return {
+ cond: cond
+ }
+ },
+ template: `
+
+
+
+
`
+})
+
+// 排除URL正则匹配
+Vue.component("http-cond-url-not-regexp", {
+ props: ["v-cond"],
+ data: function () {
+ let cond = {
+ isRequest: true,
+ param: "${requestPath}",
+ operator: "not regexp",
+ value: ""
+ }
+ if (this.vCond != null && typeof this.vCond.value == "string") {
+ cond.value = this.vCond.value
+ }
+ return {
+ cond: cond
+ }
+ },
+ 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: `
+
+
+
+
+ +添加MimeType
+
+
+
`
+})
+
+// 参数匹配
+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: ""
+ }
+ 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: `
+
+ 参数值
+
+
+
+
+
+
+
+
+
+ [常用参数]
+ {{v.code}} - {{v.name}}
+
+
+
+
+
+
+
+
+ 操作符
+
+
+
+ {{operator.name}}
+
+
+
+
+
+
+ 对比值
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+
+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("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
+
+
+The site traffic has exceeded the limit. Please contact with the site administrator.
+
+
+`
+ }
+ },
+ template: `
+
+
+
+
+ 是否启用
+
+
+
+
+
+
+
+
+ 日流量限制
+
+
+
+
+
+ 月流量限制
+
+
+
+
+
+
+ 网页提示内容
+
+
+
+
+
+
+
+
+
`
+})
+
+// TODO 支持关键词搜索
+// TODO 改成弹窗选择
+Vue.component("admin-selector", {
+ props: ["v-admin-id"],
+ mounted: function () {
+ let that = this
+ Tea.action("/admins/options")
+ .post()
+ .success(function (resp) {
+ that.admins = resp.data.admins
+ })
+ },
+ data: function () {
+ let adminId = this.vAdminId
+ if (adminId == null) {
+ adminId = 0
+ }
+ return {
+ admins: [],
+ adminId: adminId
+ }
+ },
+ template: `
+
+ [选择系统用户]
+ {{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)
+ })
+ }
+ },
+ template: ``
+})
+
+Vue.component("ip-item-text", {
+ props: ["v-item"],
+ template: `
+ *
+
+ {{vItem.ipFrom}}
+ - {{vItem.ipTo}}
+
+ {{vItem.ipFrom}}
+ 级别:{{vItem.eventLevelName}}
+ `
+})
+
+Vue.component("ip-box", {
+ props: [],
+ methods: {
+ popup: function () {
+ let e = this.$refs.container
+ let text = e.innerText
+ if (text == null) {
+ text = e.textContent
+ }
+
+ teaweb.popup("/servers/ipbox?ip=" + text, {
+ width: "50em",
+ height: "30em"
+ })
+ }
+ },
+ template: ` `
+})
+
+Vue.component("api-node-selector", {
+ props: [],
+ data: function () {
+ return {}
+ },
+ template: `
+ 暂未实现
+
`
+})
+
+Vue.component("api-node-addresses-box", {
+ props: ["v-addrs", "v-name"],
+ data: function () {
+ let addrs = this.vAddrs
+ if (addrs == null) {
+ addrs = []
+ }
+ return {
+ addrs: addrs
+ }
+ },
+ methods: {
+ // 添加IP地址
+ addAddr: function () {
+ let that = this;
+ teaweb.popup("/api/node/createAddrPopup", {
+ height: "16em",
+ callback: function (resp) {
+ that.addrs.push(resp.data.addr);
+ }
+ })
+ },
+
+ // 修改地址
+ updateAddr: function (index, addr) {
+ let that = this;
+ window.UPDATING_ADDR = addr
+ teaweb.popup("/api/node/updateAddrPopup?addressId=", {
+ callback: function (resp) {
+ Vue.set(that.addrs, index, resp.data.addr);
+ }
+ })
+ },
+
+ // 删除IP地址
+ removeAddr: function (index) {
+ this.addrs.$remove(index);
+ }
+ },
+ template: `
+
+
+
+
+ {{addr.protocol}}://{{addr.host.quoteIP()}}:{{addr.portRange}}
+
+
+
+
+
+
+
+ +
+
+
`
+})
+
+// 给Table增加排序功能
+function sortTable(callback) {
+ // 引入js
+ let jsFile = document.createElement("script")
+ jsFile.setAttribute("src", "/js/sortable.min.js")
+ jsFile.addEventListener("load", function () {
+ // 初始化
+ let box = document.querySelector("#sortable-table")
+ if (box == null) {
+ return
+ }
+ Sortable.create(box, {
+ draggable: "tbody",
+ handle: ".icon.handle",
+ onStart: function () {
+ },
+ onUpdate: function (event) {
+ let rows = box.querySelectorAll("tbody")
+ let rowIds = []
+ rows.forEach(function (row) {
+ rowIds.push(parseInt(row.getAttribute("v-id")))
+ })
+ callback(rowIds)
+ }
+ })
+ })
+ document.head.appendChild(jsFile)
+}
+
+function sortLoad(callback) {
+ let jsFile = document.createElement("script")
+ jsFile.setAttribute("src", "/js/sortable.min.js")
+ jsFile.addEventListener("load", function () {
+ if (typeof (callback) == "function") {
+ callback()
+ }
+ })
+ document.head.appendChild(jsFile)
+}
+
+
+Vue.component("page-box", {
+ data: function () {
+ return {
+ page: ""
+ }
+ },
+ created: function () {
+ let that = this;
+ setTimeout(function () {
+ that.page = Tea.Vue.page;
+ })
+ },
+ template: ``
+})
+
+Vue.component("network-addresses-box", {
+ props: ["v-server-type", "v-addresses", "v-protocol", "v-name", "v-from", "v-support-range"],
+ data: function () {
+ let addresses = this.vAddresses
+ if (addresses == null) {
+ addresses = []
+ }
+ let protocol = this.vProtocol
+ if (protocol == null) {
+ protocol = ""
+ }
+
+ let name = this.vName
+ if (name == null) {
+ name = "addresses"
+ }
+
+ let from = this.vFrom
+ if (from == null) {
+ from = ""
+ }
+
+ return {
+ addresses: addresses,
+ protocol: protocol,
+ name: name,
+ from: from
+ }
+ },
+ watch: {
+ "vServerType": function () {
+ this.addresses = []
+ },
+ "vAddresses": function () {
+ if (this.vAddresses != null) {
+ this.addresses = this.vAddresses
+ }
+ }
+ },
+ methods: {
+ addAddr: function () {
+ let that = this
+ window.UPDATING_ADDR = null
+ teaweb.popup("/servers/addPortPopup?serverType=" + this.vServerType + "&protocol=" + this.protocol + "&from=" + this.from + "&supportRange=" + (this.supportRange() ? 1 : 0), {
+ height: "18em",
+ callback: function (resp) {
+ var addr = resp.data.address
+ if (that.addresses.$find(function (k, v) {
+ return addr.host == v.host && addr.portRange == v.portRange && addr.protocol == v.protocol
+ }) != null) {
+ teaweb.warn("要添加的网络地址已经存在")
+ return
+ }
+ that.addresses.push(addr)
+ if (["https", "https4", "https6"].$contains(addr.protocol)) {
+ this.tlsProtocolName = "HTTPS"
+ } else if (["tls", "tls4", "tls6"].$contains(addr.protocol)) {
+ this.tlsProtocolName = "TLS"
+ }
+
+ // 发送事件
+ that.$emit("change", that.addresses)
+ }
+ })
+ },
+ removeAddr: function (index) {
+ this.addresses.$remove(index);
+
+ // 发送事件
+ this.$emit("change", this.addresses)
+ },
+ updateAddr: function (index, addr) {
+ let that = this
+ window.UPDATING_ADDR = addr
+ teaweb.popup("/servers/addPortPopup?serverType=" + this.vServerType + "&protocol=" + this.protocol + "&from=" + this.from + "&supportRange=" + (this.supportRange() ? 1 : 0), {
+ height: "18em",
+ callback: function (resp) {
+ var addr = resp.data.address
+ Vue.set(that.addresses, index, addr)
+
+ if (["https", "https4", "https6"].$contains(addr.protocol)) {
+ this.tlsProtocolName = "HTTPS"
+ } else if (["tls", "tls4", "tls6"].$contains(addr.protocol)) {
+ this.tlsProtocolName = "TLS"
+ }
+
+ // 发送事件
+ that.$emit("change", that.addresses)
+ }
+ })
+
+ // 发送事件
+ this.$emit("change", this.addresses)
+ },
+ supportRange: function () {
+ return this.vSupportRange || (this.vServerType == "tcpProxy" || this.vServerType == "udpProxy")
+ }
+ },
+ template: `
+
+
+
+ {{addr.protocol}}://
{{addr.host.quoteIP()}} * :
{{addr.portRange}} {{addr.portRange}}
+
+
+
+
+
[添加端口绑定]
+
`
+})
+
+/**
+ * 保存按钮
+ */
+Vue.component("submit-btn", {
+ template: '保存 '
+});
+
+// 可以展示更多条目的角图表
+Vue.component("more-items-angle", {
+ props: ["v-data-url", "v-url"],
+ data: function () {
+ return {
+ visible: false
+ }
+ },
+ methods: {
+ show: function () {
+ this.visible = !this.visible
+ if (this.visible) {
+ this.showBox()
+ } else {
+ this.hideBox()
+ }
+ },
+ showBox: function () {
+ let that = this
+
+ this.visible = true
+
+ Tea.action(this.vDataUrl)
+ .params({
+ url: this.vUrl
+ })
+ .post()
+ .success(function (resp) {
+ let groups = resp.data.groups
+
+ let boxLeft = that.$el.offsetLeft + 120;
+ let boxTop = that.$el.offsetTop + 70;
+
+ let box = document.createElement("div")
+ box.setAttribute("id", "more-items-box")
+ box.style.cssText = "z-index: 100; position: absolute; left: " + boxLeft + "px; top: " + boxTop + "px; max-height: 30em; overflow: auto; border-bottom: 1px solid rgba(34,36,38,.15)"
+ document.body.append(box)
+
+ let menuHTML = ""
+ box.innerHTML = menuHTML
+
+ let listener = function (e) {
+ if (e.target.tagName == "I") {
+ return
+ }
+
+ if (!that.isInBox(box, e.target)) {
+ document.removeEventListener("click", listener)
+ that.hideBox()
+ }
+ }
+ document.addEventListener("click", listener)
+ })
+ },
+ hideBox: function () {
+ let box = document.getElementById("more-items-box")
+ if (box != null) {
+ box.parentNode.removeChild(box)
+ }
+ this.visible = false
+ },
+ isInBox: function (parent, child) {
+ while (true) {
+ if (child == null) {
+ break
+ }
+ if (child.parentNode == parent) {
+ return true
+ }
+ child = child.parentNode
+ }
+ return false
+ }
+ },
+ template: ` `
+})
+
+/**
+ * 菜单项
+ */
+Vue.component("menu-item", {
+ props: ["href", "active", "code"],
+ data: function () {
+ let active = this.active
+ if (typeof (active) == "undefined") {
+ var itemCode = ""
+ if (typeof (window.TEA.ACTION.data.firstMenuItem) != "undefined") {
+ itemCode = window.TEA.ACTION.data.firstMenuItem
+ }
+ if (itemCode != null && itemCode.length > 0 && this.code != null && this.code.length > 0) {
+ if (itemCode.indexOf(",") > 0) {
+ active = itemCode.split(",").$contains(this.code)
+ } else {
+ active = (itemCode == this.code)
+ }
+ }
+ }
+
+ let href = (this.href == null) ? "" : this.href
+ if (typeof (href) == "string" && href.length > 0 && href.startsWith(".")) {
+ let qIndex = href.indexOf("?")
+ if (qIndex >= 0) {
+ href = Tea.url(href.substring(0, qIndex)) + href.substring(qIndex)
+ } else {
+ href = Tea.url(href)
+ }
+ }
+
+ return {
+ vHref: href,
+ vActive: active
+ }
+ },
+ methods: {
+ click: function (e) {
+ this.$emit("click", e)
+ }
+ },
+ template: '\
+ \
+ '
+});
+
+// 使用Icon的链接方式
+Vue.component("link-icon", {
+ props: ["href", "title", "target"],
+ data: function () {
+ return {
+ vTitle: (this.title == null) ? "打开链接" : this.title
+ }
+ },
+ template: ` `
+})
+
+// 带有下划虚线的连接
+Vue.component("link-red", {
+ props: ["href", "title"],
+ data: function () {
+ let href = this.href
+ if (href == null) {
+ href = ""
+ }
+ return {
+ vHref: href
+ }
+ },
+ methods: {
+ clickPrevent: function () {
+ emitClick(this, arguments)
+ }
+ },
+ template: ` `
+})
+
+// 会弹出窗口的链接
+Vue.component("link-popup", {
+ props: ["title"],
+ methods: {
+ clickPrevent: function () {
+ emitClick(this, arguments)
+ }
+ },
+ template: ` `
+})
+
+Vue.component("popup-icon", {
+ props: ["title", "href", "height"],
+ methods: {
+ clickPrevent: function () {
+ if (this.href != null && this.href.length > 0) {
+ teaweb.popup(this.href, {
+ height: this.height
+ })
+ }
+ }
+ },
+ template: ` `
+})
+
+// 小提示
+Vue.component("tip-icon", {
+ props: ["content"],
+ methods: {
+ showTip: function () {
+ teaweb.popupTip(this.content)
+ }
+ },
+ template: ` `
+})
+
+// 提交点击事件
+function emitClick(obj, arguments) {
+ let event = "click"
+ let newArgs = [event]
+ for (let i = 0; i < arguments.length; i++) {
+ newArgs.push(arguments[i])
+ }
+ obj.$emit.apply(obj, newArgs)
+}
+
+Vue.component("countries-selector", {
+ props: ["v-countries"],
+ data: function () {
+ let countries = this.vCountries
+ if (countries == null) {
+ countries = []
+ }
+ let countryIds = countries.$map(function (k, v) {
+ return v.id
+ })
+ return {
+ countries: countries,
+ countryIds: countryIds
+ }
+ },
+ methods: {
+ add: function () {
+ let countryStringIds = this.countryIds.map(function (v) {
+ return v.toString()
+ })
+ let that = this
+ teaweb.popup("/ui/selectCountriesPopup?countryIds=" + countryStringIds.join(","), {
+ width: "48em",
+ height: "23em",
+ callback: function (resp) {
+ that.countries = resp.data.countries
+ that.change()
+ }
+ })
+ },
+ remove: function (index) {
+ this.countries.$remove(index)
+ this.change()
+ },
+ change: function () {
+ this.countryIds = this.countries.$map(function (k, v) {
+ return v.id
+ })
+ }
+ },
+ template: ``
+})
+
+Vue.component("more-options-tbody", {
+ data: function () {
+ return {
+ isVisible: false
+ }
+ },
+ methods: {
+ show: function () {
+ this.isVisible = !this.isVisible
+ this.$emit("change", this.isVisible)
+ }
+ },
+ template: `
+
+ 更多选项 收起选项
+
+ `
+})
+
+Vue.component("download-link", {
+ props: ["v-element", "v-file", "v-value"],
+ created: function () {
+ let that = this
+ setTimeout(function () {
+ that.url = that.composeURL()
+ }, 1000)
+ },
+ data: function () {
+ let filename = this.vFile
+ if (filename == null || filename.length == 0) {
+ filename = "unknown-file"
+ }
+ return {
+ file: filename,
+ url: this.composeURL()
+ }
+ },
+ methods: {
+ composeURL: function () {
+ let text = ""
+ if (this.vValue != null) {
+ text = this.vValue
+ } else {
+ let e = document.getElementById(this.vElement)
+ if (e == null) {
+ teaweb.warn("找不到要下载的内容")
+ return
+ }
+ text = e.innerText
+ if (text == null) {
+ text = e.textContent
+ }
+ }
+ return Tea.url("/ui/download", {
+ file: this.file,
+ text: text
+ })
+ }
+ },
+ template: ` `,
+})
+
+Vue.component("values-box", {
+ props: ["values", "size", "maxlength", "name", "placeholder"],
+ data: function () {
+ let values = this.values;
+ if (values == null) {
+ values = [];
+ }
+ return {
+ "vValues": values,
+ "isUpdating": false,
+ "isAdding": false,
+ "index": 0,
+ "value": "",
+ isEditing: false
+ }
+ },
+ methods: {
+ create: function () {
+ this.isAdding = true;
+ var that = this;
+ setTimeout(function () {
+ that.$refs.value.focus();
+ }, 200);
+ },
+ update: function (index) {
+ this.cancel()
+ this.isUpdating = true;
+ this.index = index;
+ this.value = this.vValues[index];
+ var that = this;
+ setTimeout(function () {
+ that.$refs.value.focus();
+ }, 200);
+ },
+ confirm: function () {
+ if (this.value.length == 0) {
+ return
+ }
+
+ if (this.isUpdating) {
+ Vue.set(this.vValues, this.index, this.value);
+ } else {
+ this.vValues.push(this.value);
+ }
+ this.cancel()
+ this.$emit("change", this.vValues)
+ },
+ remove: function (index) {
+ this.vValues.$remove(index)
+ this.$emit("change", this.vValues)
+ },
+ cancel: function () {
+ this.isUpdating = false;
+ this.isAdding = false;
+ this.value = "";
+ },
+ updateAll: function (values) {
+ this.vValeus = values
+ },
+ addValue: function (v) {
+ this.vValues.push(v)
+ },
+
+ startEditing: function () {
+ this.isEditing = !this.isEditing
+ }
+ },
+ template: ``
+});
+
+Vue.component("datetime-input", {
+ props: ["v-name", "v-timestamp"],
+ mounted: function () {
+ let that = this
+ teaweb.datepicker(this.$refs.dayInput, function (v) {
+ that.day = v
+ that.hour = "23"
+ that.minute = "59"
+ that.second = "59"
+ that.change()
+ })
+ },
+ data: function () {
+ let timestamp = this.vTimestamp
+ if (timestamp != null) {
+ timestamp = parseInt(timestamp)
+ if (isNaN(timestamp)) {
+ timestamp = 0
+ }
+ } else {
+ timestamp = 0
+ }
+
+ let day = ""
+ let hour = ""
+ let minute = ""
+ let second = ""
+
+ if (timestamp > 0) {
+ let date = new Date()
+ date.setTime(timestamp * 1000)
+
+ let year = date.getFullYear().toString()
+ let month = this.leadingZero((date.getMonth() + 1).toString(), 2)
+ day = year + "-" + month + "-" + this.leadingZero(date.getDate().toString(), 2)
+
+ hour = this.leadingZero(date.getHours().toString(), 2)
+ minute = this.leadingZero(date.getMinutes().toString(), 2)
+ second = this.leadingZero(date.getSeconds().toString(), 2)
+ }
+
+ return {
+ timestamp: timestamp,
+ day: day,
+ hour: hour,
+ minute: minute,
+ second: second,
+
+ hasDayError: false,
+ hasHourError: false,
+ hasMinuteError: false,
+ hasSecondError: false
+ }
+ },
+ methods: {
+ change: function () {
+ let date = new Date()
+
+ // day
+ if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)) {
+ this.hasDayError = true
+ return
+ }
+ let pieces = this.day.split("-")
+ let year = parseInt(pieces[0])
+ date.setFullYear(year)
+
+ let month = parseInt(pieces[1])
+ if (month < 1 || month > 12) {
+ this.hasDayError = true
+ return
+ }
+ date.setMonth(month - 1)
+
+ let day = parseInt(pieces[2])
+ if (day < 1 || day > 32) {
+ this.hasDayError = true
+ return
+ }
+ date.setDate(day)
+
+ this.hasDayError = false
+
+ // hour
+ if (!/^\d+$/.test(this.hour)) {
+ this.hasHourError = true
+ return
+ }
+ let hour = parseInt(this.hour)
+ if (isNaN(hour)) {
+ this.hasHourError = true
+ return
+ }
+ if (hour < 0 || hour >= 24) {
+ this.hasHourError = true
+ return
+ }
+ this.hasHourError = false
+ date.setHours(hour)
+
+ // minute
+ if (!/^\d+$/.test(this.minute)) {
+ this.hasMinuteError = true
+ return
+ }
+ let minute = parseInt(this.minute)
+ if (isNaN(minute)) {
+ this.hasMinuteError = true
+ return
+ }
+ if (minute < 0 || minute >= 60) {
+ this.hasMinuteError = true
+ return
+ }
+ this.hasMinuteError = false
+ date.setMinutes(minute)
+
+ // second
+ if (!/^\d+$/.test(this.second)) {
+ this.hasSecondError = true
+ return
+ }
+ let second = parseInt(this.second)
+ if (isNaN(second)) {
+ this.hasSecondError = true
+ return
+ }
+ if (second < 0 || second >= 60) {
+ this.hasSecondError = true
+ return
+ }
+ this.hasSecondError = false
+ date.setSeconds(second)
+
+ this.timestamp = Math.floor(date.getTime() / 1000)
+ },
+ leadingZero: function (s, l) {
+ if (l <= s.length) {
+ return s
+ }
+ for (let i = 0; i < l - s.length; i++) {
+ s = "0" + s
+ }
+ return s
+ }
+ },
+ template: ``
+})
+
+// 启用状态标签
+Vue.component("label-on", {
+ props: ["v-is-on"],
+ template: '已启用 已停用
'
+})
+
+// 文字代码标签
+Vue.component("code-label", {
+ methods: {
+ click: function (args) {
+ this.$emit("click", args)
+ }
+ },
+ template: ` `
+})
+
+// tiny标签
+Vue.component("tiny-label", {
+ template: ` `
+})
+
+Vue.component("tiny-basic-label", {
+ template: ` `
+})
+
+// 更小的标签
+Vue.component("micro-basic-label", {
+ template: ` `
+})
+
+
+// 灰色的Label
+Vue.component("grey-label", {
+ template: ` `
+})
+
+
+/**
+ * 一级菜单
+ */
+Vue.component("first-menu", {
+ props: [],
+ template: ' \
+ '
+});
+
+/**
+ * 更多选项
+ */
+Vue.component("more-options-indicator", {
+ data: function () {
+ return {
+ visible: false
+ }
+ },
+ methods: {
+ changeVisible: function () {
+ this.visible = !this.visible
+ if (Tea.Vue != null) {
+ Tea.Vue.moreOptionsVisible = this.visible
+ }
+ this.$emit("change", this.visible)
+ }
+ },
+ template: '更多选项 收起选项 '
+});
+
+/**
+ * 二级菜单
+ */
+Vue.component("second-menu", {
+ template: ' \
+ '
+});
+
+Vue.component("more-options-angle", {
+ data: function () {
+ return {
+ isVisible: false
+ }
+ },
+ methods: {
+ show: function () {
+ this.isVisible = !this.isVisible
+ this.$emit("change", this.isVisible)
+ }
+ },
+ template: `更多选项 收起选项 `
+})
+
+/**
+ * 菜单项
+ */
+Vue.component("inner-menu-item", {
+ props: ["href", "active", "code"],
+ data: function () {
+ var active = this.active;
+ if (typeof(active) =="undefined") {
+ var itemCode = "";
+ if (typeof (window.TEA.ACTION.data.firstMenuItem) != "undefined") {
+ itemCode = window.TEA.ACTION.data.firstMenuItem;
+ }
+ active = (itemCode == this.code);
+ }
+ return {
+ vHref: (this.href == null) ? "" : this.href,
+ vActive: active
+ };
+ },
+ template: '\
+ [ ] \
+ '
+});
+
+Vue.component("health-check-config-box", {
+ props: ["v-health-check-config"],
+ data: function () {
+ let healthCheckConfig = this.vHealthCheckConfig
+ let urlProtocol = "http"
+ let urlPort = ""
+ let urlRequestURI = "/"
+ let urlHost = ""
+
+ if (healthCheckConfig == null) {
+ healthCheckConfig = {
+ isOn: false,
+ url: "",
+ interval: {count: 60, unit: "second"},
+ statusCodes: [200],
+ timeout: {count: 10, unit: "second"},
+ countTries: 3,
+ tryDelay: {count: 100, unit: "ms"},
+ autoDown: true,
+ countUp: 1,
+ countDown: 3,
+ userAgent: "",
+ onlyBasicRequest: false
+ }
+ let that = this
+ setTimeout(function () {
+ that.changeURL()
+ }, 500)
+ } else {
+ try {
+ let url = new URL(healthCheckConfig.url)
+ urlProtocol = url.protocol.substring(0, url.protocol.length - 1)
+
+ // 域名
+ urlHost = url.host
+ if (urlHost == "%24%7Bhost%7D") {
+ urlHost = "${host}"
+ }
+ let colonIndex = urlHost.indexOf(":")
+ if (colonIndex > 0) {
+ urlHost = urlHost.substring(0, colonIndex)
+ }
+
+ urlPort = url.port
+ urlRequestURI = url.pathname
+ if (url.search.length > 0) {
+ urlRequestURI += url.search
+ }
+ } catch (e) {
+ }
+
+ if (healthCheckConfig.statusCodes == null) {
+ healthCheckConfig.statusCodes = [200]
+ }
+ if (healthCheckConfig.interval == null) {
+ healthCheckConfig.interval = {count: 60, unit: "second"}
+ }
+ if (healthCheckConfig.timeout == null) {
+ healthCheckConfig.timeout = {count: 10, unit: "second"}
+ }
+ if (healthCheckConfig.tryDelay == null) {
+ healthCheckConfig.tryDelay = {count: 100, unit: "ms"}
+ }
+ if (healthCheckConfig.countUp == null || healthCheckConfig.countUp < 1) {
+ healthCheckConfig.countUp = 1
+ }
+ if (healthCheckConfig.countDown == null || healthCheckConfig.countDown < 1) {
+ healthCheckConfig.countDown = 3
+ }
+ }
+ return {
+ healthCheck: healthCheckConfig,
+ advancedVisible: false,
+ urlProtocol: urlProtocol,
+ urlHost: urlHost,
+ urlPort: urlPort,
+ urlRequestURI: urlRequestURI,
+ urlIsEditing: healthCheckConfig.url.length == 0
+ }
+ },
+ watch: {
+ urlRequestURI: function () {
+ if (this.urlRequestURI.length > 0 && this.urlRequestURI[0] != "/") {
+ this.urlRequestURI = "/" + this.urlRequestURI
+ }
+ this.changeURL()
+ },
+ urlPort: function (v) {
+ let port = parseInt(v)
+ if (!isNaN(port)) {
+ this.urlPort = port.toString()
+ } else {
+ this.urlPort = ""
+ }
+ this.changeURL()
+ },
+ urlProtocol: function () {
+ this.changeURL()
+ },
+ urlHost: function () {
+ this.changeURL()
+ },
+ "healthCheck.countTries": function (v) {
+ let count = parseInt(v)
+ if (!isNaN(count)) {
+ this.healthCheck.countTries = count
+ } else {
+ this.healthCheck.countTries = 0
+ }
+ },
+ "healthCheck.countUp": function (v) {
+ let count = parseInt(v)
+ if (!isNaN(count)) {
+ this.healthCheck.countUp = count
+ } else {
+ this.healthCheck.countUp = 0
+ }
+ },
+ "healthCheck.countDown": function (v) {
+ let count = parseInt(v)
+ if (!isNaN(count)) {
+ this.healthCheck.countDown = count
+ } else {
+ this.healthCheck.countDown = 0
+ }
+ }
+ },
+ methods: {
+ showAdvanced: function () {
+ this.advancedVisible = !this.advancedVisible
+ },
+ changeURL: function () {
+ let urlHost = this.urlHost
+ if (urlHost.length == 0) {
+ urlHost = "${host}"
+ }
+ this.healthCheck.url = this.urlProtocol + "://" + urlHost + ((this.urlPort.length > 0) ? ":" + this.urlPort : "") + this.urlRequestURI
+ },
+ changeStatus: function (values) {
+ this.healthCheck.statusCodes = values.$map(function (k, v) {
+ let status = parseInt(v)
+ if (isNaN(status)) {
+ return 0
+ } else {
+ return status
+ }
+ })
+ },
+ editURL: function () {
+ this.urlIsEditing = !this.urlIsEditing
+ }
+ },
+ template: ``
+})
+
+Vue.component("time-duration-box", {
+ props: ["v-name", "v-value", "v-count", "v-unit"],
+ mounted: function () {
+ this.change()
+ },
+ data: function () {
+ let v = this.vValue
+ if (v == null) {
+ v = {
+ count: this.vCount,
+ unit: this.vUnit
+ }
+ }
+ if (typeof (v["count"]) != "number") {
+ v["count"] = -1
+ }
+ return {
+ duration: v,
+ countString: (v.count >= 0) ? v.count.toString() : ""
+ }
+ },
+ watch: {
+ "countString": function (newValue) {
+ let value = newValue.trim()
+ if (value.length == 0) {
+ this.duration.count = -1
+ return
+ }
+ let count = parseInt(value)
+ if (!isNaN(count)) {
+ this.duration.count = count
+ }
+ this.change()
+ }
+ },
+ methods: {
+ change: function () {
+ this.$emit("change", this.duration)
+ }
+ },
+ template: `
+
+
+
+
+
+
+ 毫秒
+ 秒
+ 分钟
+ 小时
+ 天
+
+
+
`
+})
+
+Vue.component("not-found-box", {
+ props: ["message"],
+ template: ``
+})
+
+// 警告消息
+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)
+ }
+ },
+ watch: {
+ value: function (v) {
+ if (typeof v == "boolean") {
+ this.newValue = v
+ }
+ }
+ },
+ template: `
+
+
+
`
+})
+
+Vue.component("network-addresses-view", {
+ props: ["v-addresses"],
+ template: `
+
+ {{addr.protocol}}://{{addr.host.quoteIP()}} * :{{addr.portRange}}
+
+
`
+})
+
+Vue.component("size-capacity-view", {
+ props:["v-default-text", "v-value"],
+ template: `
+ {{vValue.count}}{{vValue.unit.toUpperCase()}}
+ {{vDefaultText}}
+
`
+})
+
+// 信息提示窗口
+Vue.component("tip-message-box", {
+ props: ["code"],
+ mounted: function () {
+ let that = this
+ Tea.action("/ui/showTip")
+ .params({
+ code: this.code
+ })
+ .success(function (resp) {
+ that.visible = resp.data.visible
+ })
+ .post()
+ },
+ data: function () {
+ return {
+ visible: false
+ }
+ },
+ methods: {
+ close: function () {
+ this.visible = false
+ Tea.action("/ui/hideTip")
+ .params({
+ code: this.code
+ })
+ .post()
+ }
+ },
+ template: ``
+})
+
+Vue.component("keyword", {
+ props: ["v-word"],
+ data: function () {
+ let word = this.vWord
+ if (word == null) {
+ word = ""
+ } else {
+ word = word.replace(/\)/, "\\)")
+ word = word.replace(/\(/, "\\(")
+ word = word.replace(/\+/, "\\+")
+ word = word.replace(/\^/, "\\^")
+ word = word.replace(/\$/, "\\$")
+ }
+
+ let slot = this.$slots["default"][0]
+ let text = this.encodeHTML(slot.text)
+ if (word.length > 0) {
+ text = text.replace(new RegExp("(" + word + ")", "ig"), "$1 ")
+ }
+
+ return {
+ word: word,
+ text: text
+ }
+ },
+ methods: {
+ encodeHTML: function (s) {
+ s = s.replace("&", "&")
+ s = s.replace("<", "<")
+ s = s.replace(">", ">")
+ return s
+ }
+ },
+ template: ` `
+})
+
+Vue.component("node-log-row", {
+ props: ["v-log", "v-keyword"],
+ data: function () {
+ return {
+ log: this.vLog,
+ keyword: this.vKeyword
+ }
+ },
+ template: `
+
[{{log.createdTime}}] [{{log.createdTime}}] [{{log.tag}}]{{log.description}} 共{{log.count}}条 {{log.server.name}}
+
`
+})
+
+Vue.component("provinces-selector", {
+ props: ["v-provinces"],
+ data: function () {
+ let provinces = this.vProvinces
+ if (provinces == null) {
+ provinces = []
+ }
+ let provinceIds = provinces.$map(function (k, v) {
+ return v.id
+ })
+ return {
+ provinces: provinces,
+ provinceIds: provinceIds
+ }
+ },
+ methods: {
+ add: function () {
+ let provinceStringIds = this.provinceIds.map(function (v) {
+ return v.toString()
+ })
+ let that = this
+ teaweb.popup("/ui/selectProvincesPopup?provinceIds=" + provinceStringIds.join(","), {
+ width: "48em",
+ height: "23em",
+ callback: function (resp) {
+ that.provinces = resp.data.provinces
+ that.change()
+ }
+ })
+ },
+ remove: function (index) {
+ this.provinces.$remove(index)
+ this.change()
+ },
+ change: function () {
+ this.provinceIds = this.provinces.$map(function (k, v) {
+ return v.id
+ })
+ }
+ },
+ template: ``
+})
+
+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"],
+ 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
+ }
+ 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
+ })
+ boxEditor.setValue(value)
+
+ 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)
+ }
+ },
+ data: function () {
+ let index = sourceCodeBoxIndex++
+
+ let valueBoxId = 'source-code-box-value-' + sourceCodeBoxIndex
+ if (this.id != null) {
+ valueBoxId = this.id
+ }
+
+ return {
+ index: index,
+ valueBoxId: valueBoxId
+ }
+ },
+ template: ``
+})
+
+Vue.component("size-capacity-box", {
+ props: ["v-name", "v-value", "v-count", "v-unit", "size", "maxlength"],
+ 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
+ }
+
+ return {
+ capacity: v,
+ countString: (v.count >= 0) ? v.count.toString() : "",
+ vSize: vSize,
+ vMaxlength: vMaxlength
+ }
+ },
+ 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: `
+
+
+
+
+
+
+ 字节
+ KB
+ MB
+ GB
+ TB
+ PB
+
+
+
`
+})
+
+/**
+ * 二级菜单
+ */
+Vue.component("inner-menu", {
+ template: `
+ `
+});
+
+Vue.component("datepicker", {
+ props: ["v-name", "v-value", "v-bottom-left"],
+ mounted: function () {
+ let that = this
+ teaweb.datepicker(this.$refs.dayInput, function (v) {
+ that.day = v
+ that.change()
+ }, !!this.vBottomLeft)
+ },
+ data: function () {
+ let name = this.vName
+ if (name == null) {
+ name = "day"
+ }
+
+ let day = this.vValue
+ if (day == null) {
+ day = ""
+ }
+
+ return {
+ name: name,
+ day: day
+ }
+ },
+ methods: {
+ change: function () {
+ this.$emit("change", this.day)
+ }
+ },
+ template: `
+
+
`
+})
+
+// 排序使用的箭头
+Vue.component("sort-arrow", {
+ props: ["name"],
+ data: function () {
+ let url = window.location.toString()
+ let order = ""
+ let newArgs = []
+ if (window.location.search != null && window.location.search.length > 0) {
+ let queryString = window.location.search.substring(1)
+ let pieces = queryString.split("&")
+ let that = this
+ pieces.forEach(function (v) {
+ let eqIndex = v.indexOf("=")
+ if (eqIndex > 0) {
+ let argName = v.substring(0, eqIndex)
+ let argValue = v.substring(eqIndex + 1)
+ if (argName == that.name) {
+ order = argValue
+ } else if (argValue != "asc" && argValue != "desc") {
+ newArgs.push(v)
+ }
+ } else {
+ newArgs.push(v)
+ }
+ })
+ }
+ if (order == "asc") {
+ newArgs.push(this.name + "=desc")
+ } else if (order == "desc") {
+ newArgs.push(this.name + "=asc")
+ } else {
+ newArgs.push(this.name + "=desc")
+ }
+
+ let qIndex = url.indexOf("?")
+ if (qIndex > 0) {
+ url = url.substring(0, qIndex) + "?" + newArgs.join("&")
+ } else {
+ url = url + "?" + newArgs.join("&")
+ }
+
+ return {
+ order: order,
+ url: url
+ }
+ },
+ template: ` `
+})
+
+Vue.component("user-link", {
+ props: ["v-user", "v-keyword"],
+ data: function () {
+ let user = this.vUser
+ if (user == null) {
+ user = {id: 0, "username": "", "fullname": ""}
+ }
+ return {
+ user: user
+ }
+ },
+ template: `
+ {{user.fullname}} ({{user.username}} )
+ [已删除]
+
`
+})
+
+// 监控节点分组选择
+Vue.component("report-node-groups-selector", {
+ props: ["v-group-ids"],
+ mounted: function () {
+ let that = this
+ Tea.action("/clusters/monitors/groups/options")
+ .post()
+ .success(function (resp) {
+ that.groups = resp.data.groups.map(function (group) {
+ group.isChecked = that.groupIds.$contains(group.id)
+ return group
+ })
+ that.isLoaded = true
+ })
+ },
+ data: function () {
+ var groupIds = this.vGroupIds
+ if (groupIds == null) {
+ groupIds = []
+ }
+
+ return {
+ groups: [],
+ groupIds: groupIds,
+ isLoaded: false,
+ allGroups: groupIds.length == 0
+ }
+ },
+ methods: {
+ check: function (group) {
+ group.isChecked = !group.isChecked
+ this.groupIds = []
+ let that = this
+ this.groups.forEach(function (v) {
+ if (v.isChecked) {
+ that.groupIds.push(v.id)
+ }
+ })
+ this.change()
+ },
+ change: function () {
+ let that = this
+ let groups = []
+ this.groupIds.forEach(function (groupId) {
+ let group = that.groups.$find(function (k, v) {
+ return v.id == groupId
+ })
+ if (group == null) {
+ return
+ }
+ groups.push({
+ id: group.id,
+ name: group.name
+ })
+ })
+ this.$emit("change", groups)
+ }
+ },
+ watch: {
+ allGroups: function (b) {
+ if (b) {
+ this.groupIds = []
+ this.groups.forEach(function (v) {
+ v.isChecked = false
+ })
+ }
+
+ this.change()
+ }
+ },
+ template: ``
+})
+
+Vue.component("finance-user-selector", {
+ mounted: function () {
+ let that = this
+
+ Tea.action("/finance/users/options")
+ .post()
+ .success(function (resp) {
+ that.users = resp.data.users
+ })
+ },
+ props: ["v-user-id"],
+ data: function () {
+ let userId = this.vUserId
+ if (userId == null) {
+ userId = 0
+ }
+ return {
+ users: [],
+ userId: userId
+ }
+ },
+ watch: {
+ userId: function (v) {
+ this.$emit("change", v)
+ }
+ },
+ template: `
+
+ [选择用户]
+ {{user.fullname}} ({{user.username}})
+
+
`
+})
+
+// 节点登录推荐端口
+Vue.component("node-login-suggest-ports", {
+ data: function () {
+ return {
+ ports: [],
+ availablePorts: [],
+ autoSelected: false,
+ isLoading: false
+ }
+ },
+ methods: {
+ reload: function (host) {
+ let that = this
+ this.autoSelected = false
+ this.isLoading = true
+ Tea.action("/clusters/cluster/suggestLoginPorts")
+ .params({
+ host: host
+ })
+ .success(function (resp) {
+ if (resp.data.availablePorts != null) {
+ that.availablePorts = resp.data.availablePorts
+ if (that.availablePorts.length > 0) {
+ that.autoSelectPort(that.availablePorts[0])
+ that.autoSelected = true
+ }
+ }
+ if (resp.data.ports != null) {
+ that.ports = resp.data.ports
+ if (that.ports.length > 0 && !that.autoSelected) {
+ that.autoSelectPort(that.ports[0])
+ that.autoSelected = true
+ }
+ }
+ })
+ .done(function () {
+ that.isLoading = false
+ })
+ .post()
+ },
+ selectPort: function (port) {
+ this.$emit("select", port)
+ },
+ autoSelectPort: function (port) {
+ this.$emit("auto-select", port)
+ }
+ },
+ template: `
+ 正在检查端口...
+
+ 可能端口:{{port}}
+
+
+
+ 常用端口:{{port}}
+
+ 常用端口有22等。
+ (可以点击要使用的端口)
+ `
+})
+
+Vue.component("node-group-selector", {
+ props: ["v-cluster-id", "v-group"],
+ data: function () {
+ return {
+ selectedGroup: this.vGroup
+ }
+ },
+ methods: {
+ selectGroup: function () {
+ let that = this
+ teaweb.popup("/clusters/cluster/groups/selectPopup?clusterId=" + this.vClusterId, {
+ callback: function (resp) {
+ that.selectedGroup = resp.data.group
+ }
+ })
+ },
+ addGroup: function () {
+ let that = this
+ teaweb.popup("/clusters/cluster/groups/createPopup?clusterId=" + this.vClusterId, {
+ callback: function (resp) {
+ that.selectedGroup = resp.data.group
+ }
+ })
+ },
+ removeGroup: function () {
+ this.selectedGroup = null
+ }
+ },
+ template: `
+
+
+ {{selectedGroup.name}}
+
+
+
`
+})
+
+// 节点IP地址管理(标签形式)
+Vue.component("node-ip-addresses-box", {
+ props: ["v-ip-addresses", "role"],
+ data: function () {
+ return {
+ ipAddresses: (this.vIpAddresses == null) ? [] : this.vIpAddresses,
+ supportThresholds: this.role != "ns"
+ }
+ },
+ methods: {
+ // 添加IP地址
+ addIPAddress: function () {
+ window.UPDATING_NODE_IP_ADDRESS = null
+
+ let that = this;
+ teaweb.popup("/nodes/ipAddresses/createPopup?supportThresholds=" + (this.supportThresholds ? 1 : 0), {
+ callback: function (resp) {
+ that.ipAddresses.push(resp.data.ipAddress);
+ },
+ height: "24em",
+ width: "44em"
+ })
+ },
+
+ // 修改地址
+ updateIPAddress: function (index, address) {
+ window.UPDATING_NODE_IP_ADDRESS = address
+
+ let that = this;
+ teaweb.popup("/nodes/ipAddresses/updatePopup?supportThresholds=" + (this.supportThresholds ? 1 : 0), {
+ callback: function (resp) {
+ Vue.set(that.ipAddresses, index, resp.data.ipAddress);
+ },
+ height: "24em",
+ width: "44em"
+ })
+ },
+
+ // 删除IP地址
+ removeIPAddress: function (index) {
+ this.ipAddresses.$remove(index);
+ },
+
+ // 判断是否为IPv6
+ isIPv6: function (ip) {
+ return ip.indexOf(":") > -1
+ }
+ },
+ template: `
+
+
+
+
+
[IPv6] {{address.ip}}
+
({{address.name}},不可访问 )
+
(不可访问)
+
[off]
+
[down]
+
[{{address.thresholds.length}}个阈值]
+
+
+
+
+
+
+
+
+ +
+
+
`
+})
+
+// 节点IP阈值
+Vue.component("node-ip-address-thresholds-view", {
+ props: ["v-thresholds"],
+ data: function () {
+ let thresholds = this.vThresholds
+ if (thresholds == null) {
+ thresholds = []
+ } else {
+ thresholds.forEach(function (v) {
+ if (v.items == null) {
+ v.items = []
+ }
+ if (v.actions == null) {
+ v.actions = []
+ }
+ })
+ }
+
+ return {
+ thresholds: thresholds,
+ allItems: window.IP_ADDR_THRESHOLD_ITEMS,
+ allOperators: [
+ {
+ "name": "小于等于",
+ "code": "lte"
+ },
+ {
+ "name": "大于",
+ "code": "gt"
+ },
+ {
+ "name": "不等于",
+ "code": "neq"
+ },
+ {
+ "name": "小于",
+ "code": "lt"
+ },
+ {
+ "name": "大于等于",
+ "code": "gte"
+ }
+ ],
+ allActions: window.IP_ADDR_THRESHOLD_ACTIONS
+ }
+ },
+ methods: {
+ itemName: function (item) {
+ let result = ""
+ this.allItems.forEach(function (v) {
+ if (v.code == item) {
+ result = v.name
+ }
+ })
+ return result
+ },
+ itemUnitName: function (itemCode) {
+ let result = ""
+ this.allItems.forEach(function (v) {
+ if (v.code == itemCode) {
+ result = v.unit
+ }
+ })
+ return result
+ },
+ itemDurationUnitName: function (unit) {
+ switch (unit) {
+ case "minute":
+ return "分钟"
+ case "second":
+ return "秒"
+ case "hour":
+ return "小时"
+ case "day":
+ return "天"
+ }
+ return unit
+ },
+ itemOperatorName: function (operator) {
+ let result = ""
+ this.allOperators.forEach(function (v) {
+ if (v.code == operator) {
+ result = v.name
+ }
+ })
+ return result
+ },
+ actionName: function (actionCode) {
+ let result = ""
+ this.allActions.forEach(function (v) {
+ if (v.code == actionCode) {
+ result = v.name
+ }
+ })
+ return result
+ }
+ },
+ template: `
+
+
+
+
+
+
+ [{{item.duration}}{{itemDurationUnitName(item.durationUnit)}}]
+
+ {{itemName(item.item)}}
+
+
+
+ 成功
+ 失败
+
+
+
+ [{{group.name}} ]
+
+ [{{itemOperatorName(item.operator)}}] {{item.value}}{{itemUnitName(item.item)}}
+
+
+ AND
+ ->
+ {{actionName(action.action)}}
+ 到{{action.options.ips.join(", ")}}
+ ({{action.options.url}})
+
+ AND
+
+
+
+
`
+})
+
+// 节点IP阈值
+Vue.component("node-ip-address-thresholds-box", {
+ props: ["v-thresholds"],
+ data: function () {
+ let thresholds = this.vThresholds
+ if (thresholds == null) {
+ thresholds = []
+ } else {
+ thresholds.forEach(function (v) {
+ if (v.items == null) {
+ v.items = []
+ }
+ if (v.actions == null) {
+ v.actions = []
+ }
+ })
+ }
+
+ return {
+ editingIndex: -1,
+ thresholds: thresholds,
+ addingThreshold: {
+ items: [],
+ actions: []
+ },
+ isAdding: false,
+ isAddingItem: false,
+ isAddingAction: false,
+
+ itemCode: "nodeAvgRequests",
+ itemReportGroups: [],
+ itemOperator: "lte",
+ itemValue: "",
+ itemDuration: "5",
+ allItems: window.IP_ADDR_THRESHOLD_ITEMS,
+ allOperators: [
+ {
+ "name": "小于等于",
+ "code": "lte"
+ },
+ {
+ "name": "大于",
+ "code": "gt"
+ },
+ {
+ "name": "不等于",
+ "code": "neq"
+ },
+ {
+ "name": "小于",
+ "code": "lt"
+ },
+ {
+ "name": "大于等于",
+ "code": "gte"
+ }
+ ],
+ allActions: window.IP_ADDR_THRESHOLD_ACTIONS,
+
+ actionCode: "up",
+ actionBackupIPs: "",
+ actionWebHookURL: ""
+ }
+ },
+ methods: {
+ add: function () {
+ this.isAdding = !this.isAdding
+ },
+ cancel: function () {
+ this.isAdding = false
+ this.editingIndex = -1
+ this.addingThreshold = {
+ items: [],
+ actions: []
+ }
+ },
+ confirm: function () {
+ if (this.addingThreshold.items.length == 0) {
+ teaweb.warn("需要至少添加一个阈值")
+ return
+ }
+ if (this.addingThreshold.actions.length == 0) {
+ teaweb.warn("需要至少添加一个动作")
+ return
+ }
+
+ if (this.editingIndex >= 0) {
+ this.thresholds[this.editingIndex].items = this.addingThreshold.items
+ this.thresholds[this.editingIndex].actions = this.addingThreshold.actions
+ } else {
+ this.thresholds.push({
+ items: this.addingThreshold.items,
+ actions: this.addingThreshold.actions
+ })
+ }
+
+ // 还原
+ this.cancel()
+ },
+ remove: function (index) {
+ this.cancel()
+ this.thresholds.$remove(index)
+ },
+ update: function (index) {
+ this.editingIndex = index
+ this.addingThreshold = {
+ items: this.thresholds[index].items.$copy(),
+ actions: this.thresholds[index].actions.$copy()
+ }
+ this.isAdding = true
+ },
+
+ addItem: function () {
+ this.isAddingItem = !this.isAddingItem
+ let that = this
+ setTimeout(function () {
+ that.$refs.itemValue.focus()
+ }, 100)
+ },
+ cancelItem: function () {
+ this.isAddingItem = false
+
+ this.itemCode = "nodeAvgRequests"
+ this.itmeOperator = "lte"
+ this.itemValue = ""
+ this.itemDuration = "5"
+ this.itemReportGroups = []
+ },
+ confirmItem: function () {
+ // 特殊阈值快速添加
+ if (["nodeHealthCheck"].$contains(this.itemCode)) {
+ if (this.itemValue.toString().length == 0) {
+ teaweb.warn("请选择检查结果")
+ return
+ }
+
+ let value = parseInt(this.itemValue)
+ if (isNaN(value)) {
+ value = 0
+ } else if (value < 0) {
+ value = 0
+ } else if (value > 1) {
+ value = 1
+ }
+
+ // 添加
+ this.addingThreshold.items.push({
+ item: this.itemCode,
+ operator: this.itemOperator,
+ value: value,
+ duration: 0,
+ durationUnit: "minute",
+ options: {}
+ })
+ this.cancelItem()
+ return
+ }
+
+ if (this.itemDuration.length == 0) {
+ let that = this
+ teaweb.warn("请输入统计周期", function () {
+ that.$refs.itemDuration.focus()
+ })
+ return
+ }
+ let itemDuration = parseInt(this.itemDuration)
+ if (isNaN(itemDuration) || itemDuration <= 0) {
+ teaweb.warn("请输入正确的统计周期", function () {
+ that.$refs.itemDuration.focus()
+ })
+ return
+ }
+
+ if (this.itemValue.length == 0) {
+ let that = this
+ teaweb.warn("请输入对比值", function () {
+ that.$refs.itemValue.focus()
+ })
+ return
+ }
+ let itemValue = parseFloat(this.itemValue)
+ if (isNaN(itemValue)) {
+ teaweb.warn("请输入正确的对比值", function () {
+ that.$refs.itemValue.focus()
+ })
+ return
+ }
+
+
+ let options = {}
+
+ switch (this.itemCode) {
+ case "connectivity": // 连通性校验
+ if (itemValue > 100) {
+ let that = this
+ teaweb.warn("连通性对比值不能超过100", function () {
+ that.$refs.itemValue.focus()
+ })
+ return
+ }
+
+ options["groups"] = this.itemReportGroups
+ break
+ }
+
+ // 添加
+ this.addingThreshold.items.push({
+ item: this.itemCode,
+ operator: this.itemOperator,
+ value: itemValue,
+ duration: itemDuration,
+ durationUnit: "minute",
+ options: options
+ })
+
+ // 还原
+ this.cancelItem()
+ },
+ removeItem: function (index) {
+ this.cancelItem()
+ this.addingThreshold.items.$remove(index)
+ },
+ changeReportGroups: function (groups) {
+ this.itemReportGroups = groups
+ },
+ itemName: function (item) {
+ let result = ""
+ this.allItems.forEach(function (v) {
+ if (v.code == item) {
+ result = v.name
+ }
+ })
+ return result
+ },
+ itemUnitName: function (itemCode) {
+ let result = ""
+ this.allItems.forEach(function (v) {
+ if (v.code == itemCode) {
+ result = v.unit
+ }
+ })
+ return result
+ },
+ itemDurationUnitName: function (unit) {
+ switch (unit) {
+ case "minute":
+ return "分钟"
+ case "second":
+ return "秒"
+ case "hour":
+ return "小时"
+ case "day":
+ return "天"
+ }
+ return unit
+ },
+ itemOperatorName: function (operator) {
+ let result = ""
+ this.allOperators.forEach(function (v) {
+ if (v.code == operator) {
+ result = v.name
+ }
+ })
+ return result
+ },
+
+ addAction: function () {
+ this.isAddingAction = !this.isAddingAction
+ },
+ cancelAction: function () {
+ this.isAddingAction = false
+ this.actionCode = "up"
+ this.actionBackupIPs = ""
+ this.actionWebHookURL = ""
+ },
+ confirmAction: function () {
+ this.doConfirmAction(false)
+ },
+ doConfirmAction: function (validated, options) {
+ // 是否已存在
+ let exists = false
+ let that = this
+ this.addingThreshold.actions.forEach(function (v) {
+ if (v.action == that.actionCode) {
+ exists = true
+ }
+ })
+ if (exists) {
+ teaweb.warn("此动作已经添加过了,无需重复添加")
+ return
+ }
+
+ if (options == null) {
+ options = {}
+ }
+
+ switch (this.actionCode) {
+ case "switch":
+ if (!validated) {
+ Tea.action("/ui/validateIPs")
+ .params({
+ "ips": this.actionBackupIPs
+ })
+ .success(function (resp) {
+ if (resp.data.ips.length == 0) {
+ teaweb.warn("请输入备用IP", function () {
+ that.$refs.actionBackupIPs.focus()
+ })
+ return
+ }
+ options["ips"] = resp.data.ips
+ that.doConfirmAction(true, options)
+ })
+ .fail(function (resp) {
+ teaweb.warn("输入的IP '" + resp.data.failIP + "' 格式不正确,请改正后提交", function () {
+ that.$refs.actionBackupIPs.focus()
+ })
+ })
+ .post()
+ return
+ }
+ break
+ case "webHook":
+ if (this.actionWebHookURL.length == 0) {
+ teaweb.warn("请输入WebHook URL", function () {
+ that.$refs.webHookURL.focus()
+ })
+ return
+ }
+ if (!this.actionWebHookURL.match(/^(http|https):\/\//i)) {
+ teaweb.warn("URL开头必须是http://或者https://", function () {
+ that.$refs.webHookURL.focus()
+ })
+ return
+ }
+ options["url"] = this.actionWebHookURL
+ }
+
+ this.addingThreshold.actions.push({
+ action: this.actionCode,
+ options: options
+ })
+
+ // 还原
+ this.cancelAction()
+ },
+ removeAction: function (index) {
+ this.cancelAction()
+ this.addingThreshold.actions.$remove(index)
+ },
+ actionName: function (actionCode) {
+ let result = ""
+ this.allActions.forEach(function (v) {
+ if (v.code == actionCode) {
+ result = v.name
+ }
+ })
+ return result
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+ [{{item.duration}}{{itemDurationUnitName(item.durationUnit)}}]
+
+ {{itemName(item.item)}}
+
+
+
+ 成功
+ 失败
+
+
+
+ [{{group.name}} ]
+
+ [{{itemOperatorName(item.operator)}}] {{item.value}}{{itemUnitName(item.item)}}
+
+ AND
+
+ ->
+
{{actionName(action.action)}}
+ 到{{action.options.ips.join(", ")}}
+ ({{action.options.url}})
+ AND
+
+
+
+
+
+
+
+
+
+
+
+ 阈值
+ 动作
+
+
+
+
+
+
+
+
+ [{{item.duration}}{{itemDurationUnitName(item.durationUnit)}}]
+
+ {{itemName(item.item)}}
+
+
+
+ 成功
+ 失败
+
+
+
+ [{{group.name}} ]
+ [{{itemOperatorName(item.operator)}}] {{item.value}}{{itemUnitName(item.item)}}
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ {{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: `
+
+
+ {{selectedRegion.name}}
+
+
+
`
+})
+
+Vue.component("dns-route-selector", {
+ props: ["v-all-routes", "v-routes"],
+ data: function () {
+ let routes = this.vRoutes
+ if (routes == null) {
+ routes = []
+ }
+ routes.$sort(function (v1, v2) {
+ if (v1.domainId == v2.domainId) {
+ return v1.code < v2.code
+ }
+ return (v1.domainId < v2.domainId) ? 1 : -1
+ })
+ return {
+ routes: routes,
+ routeCodes: routes.$map(function (k, v) {
+ return v.code + "@" + v.domainId
+ }),
+ isAdding: false,
+ routeCode: "",
+ keyword: "",
+ searchingRoutes: this.vAllRoutes.$copy()
+ }
+ },
+ methods: {
+ add: function () {
+ this.isAdding = true
+ this.keyword = ""
+ this.routeCode = ""
+
+ let that = this
+ setTimeout(function () {
+ that.$refs.keywordRef.focus()
+ }, 200)
+ },
+ cancel: function () {
+ this.isAdding = false
+ },
+ confirm: function () {
+ if (this.routeCode.length == 0) {
+ return
+ }
+ if (this.routeCodes.$contains(this.routeCode)) {
+ teaweb.warn("已经添加过此线路,不能重复添加")
+ return
+ }
+ let that = this
+ let route = this.vAllRoutes.$find(function (k, v) {
+ return v.code + "@" + v.domainId == that.routeCode
+ })
+ if (route == null) {
+ return
+ }
+
+ this.routeCodes.push(this.routeCode)
+ this.routes.push(route)
+
+ this.routes.$sort(function (v1, v2) {
+ if (v1.domainId == v2.domainId) {
+ return v1.code < v2.code
+ }
+ return (v1.domainId < v2.domainId) ? 1 : -1
+ })
+
+ this.routeCode = ""
+ this.isAdding = false
+ },
+ remove: function (route) {
+ this.routeCodes.$removeValue(route.code + "@" + route.domainId)
+ this.routes.$removeIf(function (k, v) {
+ return v.code + "@" + v.domainId == route.code + "@" + route.domainId
+ })
+ }
+ },
+ watch: {
+ keyword: function (keyword) {
+ if (keyword.length == 0) {
+ this.searchingRoutes = this.vAllRoutes.$copy()
+ this.routeCode = ""
+ return
+ }
+ this.searchingRoutes = this.vAllRoutes.filter(function (route) {
+ return teaweb.match(route.name, keyword) || teaweb.match(route.domainName, keyword)
+ })
+ if (this.searchingRoutes.length > 0) {
+ this.routeCode = this.searchingRoutes[0].code + "@" + this.searchingRoutes[0].domainId
+ } else {
+ this.routeCode = ""
+ }
+ }
+ },
+ template: `
+
+
+
+ {{route.name}} ({{route.domainName}})
+
+
+
+
+
+
+
+
+
+ [请选择]
+ {{route.name}}({{route.domainName}})
+
+
+
+
+
+
+
+ 确定
+
+
+
+
+
`
+})
+
+Vue.component("dns-domain-selector", {
+ props: ["v-domain-id", "v-domain-name"],
+ data: function () {
+ let domainId = this.vDomainId
+ if (domainId == null) {
+ domainId = 0
+ }
+ let domainName = this.vDomainName
+ if (domainName == null) {
+ domainName = ""
+ }
+ return {
+ domainId: domainId,
+ domainName: domainName
+ }
+ },
+ methods: {
+ select: function () {
+ let that = this
+ teaweb.popup("/dns/domains/selectPopup", {
+ callback: function (resp) {
+ that.domainId = resp.data.domainId
+ that.domainName = resp.data.domainName
+ that.change()
+ }
+ })
+ },
+ remove: function() {
+ this.domainId = 0
+ this.domainName = ""
+ this.change()
+ },
+ update: function () {
+ let that = this
+ teaweb.popup("/dns/domains/selectPopup?domainId=" + this.domainId, {
+ callback: function (resp) {
+ that.domainId = resp.data.domainId
+ that.domainName = resp.data.domainName
+ that.change()
+ }
+ })
+ },
+ change: function () {
+ this.$emit("change", {
+ id: this.domainId,
+ name: this.domainName
+ })
+ }
+ },
+ template: `
+
+
+
+ {{domainName}}
+
+
+
+
+
+
`
+})
+
+Vue.component("grant-selector", {
+ props: ["v-grant", "v-node-cluster-id", "v-ns-cluster-id"],
+ data: function () {
+ return {
+ grantId: (this.vGrant == null) ? 0 : this.vGrant.id,
+ grant: this.vGrant,
+ nodeClusterId: (this.vNodeClusterId != null) ? this.vNodeClusterId : 0,
+ nsClusterId: (this.vNsClusterId != null) ? this.vNsClusterId : 0
+ }
+ },
+ methods: {
+ // 选择授权
+ select: function () {
+ let that = this;
+ teaweb.popup("/clusters/grants/selectPopup?nodeClusterId=" + this.nodeClusterId + "&nsClusterId=" + this.nsClusterId, {
+ callback: (resp) => {
+ that.grantId = resp.data.grant.id;
+ if (that.grantId > 0) {
+ that.grant = resp.data.grant;
+ }
+ that.notifyUpdate()
+ },
+ height: "26em"
+ })
+ },
+
+ // 创建授权
+ create: function () {
+ let that = this
+ teaweb.popup("/clusters/grants/createPopup", {
+ height: "26em",
+ callback: (resp) => {
+ that.grantId = resp.data.grant.id;
+ if (that.grantId > 0) {
+ that.grant = resp.data.grant;
+ }
+ that.notifyUpdate()
+ }
+ })
+ },
+
+ // 修改授权
+ update: function () {
+ if (this.grant == null) {
+ window.location.reload();
+ return;
+ }
+ let that = this
+ teaweb.popup("/clusters/grants/updatePopup?grantId=" + this.grant.id, {
+ height: "26em",
+ callback: (resp) => {
+ that.grant = resp.data.grant
+ that.notifyUpdate()
+ }
+ })
+ },
+
+ // 删除已选择授权
+ remove: function () {
+ this.grant = null
+ this.grantId = 0
+ this.notifyUpdate()
+ },
+ notifyUpdate: function () {
+ this.$emit("change", this.grant)
+ }
+ },
+ template: `
+
+
{{grant.name}}
({{grant.methodName}}) ({{grant.username}})
+
+
`
+})
+
+window.REQUEST_COND_COMPONENTS = [{"type":"url-extension","name":"URL扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-extension","paramsTitle":"扩展名列表","isRequest":true},{"type":"url-prefix","name":"URL前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-prefix","paramsTitle":"URL前缀","isRequest":true},{"type":"url-eq","name":"URL精准匹配","description":"检查URL中的文件路径是否一致","component":"http-cond-url-eq","paramsTitle":"URL完整路径","isRequest":true},{"type":"url-regexp","name":"URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致","component":"http-cond-url-regexp","paramsTitle":"正则表达式","isRequest":true},{"type":"params","name":"参数匹配","description":"根据参数值进行匹配","component":"http-cond-params","paramsTitle":"参数配置","isRequest":true},{"type":"url-not-prefix","name":"排除:URL前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-not-prefix","paramsTitle":"URL前缀","isRequest":true},{"type":"url-not-eq","name":"排除:URL精准匹配","description":"检查URL中的文件路径是否一致","component":"http-cond-url-not-eq","paramsTitle":"URL完整路径","isRequest":true},{"type":"url-not-regexp","name":"排除:URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致,如果一致,则不匹配","component":"http-cond-url-not-regexp","paramsTitle":"正则表达式","isRequest":true},{"type":"mime-type","name":"内容MimeType","description":"根据服务器返回的内容的MimeType进行过滤。注意:当用于缓存条件时,此条件需要结合别的请求条件使用。","component":"http-cond-mime-type","paramsTitle":"MimeType列表","isRequest":false}]
+
+window.REQUEST_COND_OPERATORS = [{"description":"判断是否正则表达式匹配","name":"正则表达式匹配","op":"regexp"},{"description":"判断是否正则表达式不匹配","name":"正则表达式不匹配","op":"not regexp"},{"description":"使用字符串对比参数值是否相等于某个值","name":"字符串等于","op":"eq"},{"description":"参数值包含某个前缀","name":"字符串前缀","op":"prefix"},{"description":"参数值包含某个后缀","name":"字符串后缀","op":"suffix"},{"description":"参数值包含另外一个字符串","name":"字符串包含","op":"contains"},{"description":"参数值不包含另外一个字符串","name":"字符串不包含","op":"not contains"},{"description":"使用字符串对比参数值是否不相等于某个值","name":"字符串不等于","op":"not"},{"description":"判断参数值在某个列表中","name":"在列表中","op":"in"},{"description":"判断参数值不在某个列表中","name":"不在列表中","op":"not in"},{"description":"判断小写的扩展名(不带点)在某个列表中","name":"扩展名","op":"file ext"},{"description":"判断MimeType在某个列表中,支持类似于image/*的语法","name":"MimeType","op":"mime type"},{"description":"判断版本号在某个范围内,格式为version1,version2","name":"版本号范围","op":"version range"},{"description":"将参数转换为整数数字后进行对比","name":"整数等于","op":"eq int"},{"description":"将参数转换为可以有小数的浮点数字进行对比","name":"浮点数等于","op":"eq float"},{"description":"将参数转换为数字进行对比","name":"数字大于","op":"gt"},{"description":"将参数转换为数字进行对比","name":"数字大于等于","op":"gte"},{"description":"将参数转换为数字进行对比","name":"数字小于","op":"lt"},{"description":"将参数转换为数字进行对比","name":"数字小于等于","op":"lte"},{"description":"对整数参数值取模,除数为10,对比值为余数","name":"整数取模10","op":"mod 10"},{"description":"对整数参数值取模,除数为100,对比值为余数","name":"整数取模100","op":"mod 100"},{"description":"对整数参数值取模,对比值格式为:除数,余数,比如10,1","name":"整数取模","op":"mod"},{"description":"将参数转换为IP进行对比","name":"IP等于","op":"eq ip"},{"description":"将参数转换为IP进行对比","name":"IP大于","op":"gt ip"},{"description":"将参数转换为IP进行对比","name":"IP大于等于","op":"gte ip"},{"description":"将参数转换为IP进行对比","name":"IP小于","op":"lt ip"},{"description":"将参数转换为IP进行对比","name":"IP小于等于","op":"lte ip"},{"description":"IP在某个范围之内,范围格式可以是英文逗号分隔的ip1,ip2,或者CIDR格式的ip/bits","name":"IP范围","op":"ip range"},{"description":"对IP参数值取模,除数为10,对比值为余数","name":"IP取模10","op":"ip mod 10"},{"description":"对IP参数值取模,除数为100,对比值为余数","name":"IP取模100","op":"ip mod 100"},{"description":"对IP参数值取模,对比值格式为:除数,余数,比如10,1","name":"IP取模","op":"ip mod"},{"description":"判断参数值解析后的文件是否存在","name":"文件存在","op":"file exist"},{"description":"判断参数值解析后的文件是否不存在","name":"文件不存在","op":"file not exist"}]
+
+window.REQUEST_VARIABLES = [{"code":"${edgeVersion}","description":"","name":"边缘节点版本"},{"code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适合前端有别的反向代理服务时使用,存在伪造的风险","name":"客户端地址(IP)"},{"code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","name":"客户端地址(IP)"},{"code":"${remotePort}","description":"","name":"客户端端口"},{"code":"${remoteUser}","description":"","name":"客户端用户名"},{"code":"${requestURI}","description":"比如/hello?name=lily","name":"请求URI"},{"code":"${requestPath}","description":"比如/hello","name":"请求路径(不包括参数)"},{"code":"${requestURL}","description":"比如https://example.com/hello?name=lily","name":"完整的请求URL"},{"code":"${requestLength}","description":"","name":"请求内容长度"},{"code":"${requestMethod}","description":"比如GET、POST","name":"请求方法"},{"code":"${requestFilename}","description":"","name":"请求文件路径"},{"code":"${scheme}","description":"","name":"请求协议,http或https"},{"code":"${proto}","description:":"类似于HTTP/1.0","name":"包含版本的HTTP请求协议"},{"code":"${timeISO8601}","description":"比如2018-07-16T23:52:24.839+08:00","name":"ISO 8601格式的时间"},{"code":"${timeLocal}","description":"比如17/Jul/2018:09:52:24 +0800","name":"本地时间"},{"code":"${msec}","description":"比如1531756823.054","name":"带有毫秒的时间"},{"code":"${timestamp}","description":"","name":"unix时间戳,单位为秒"},{"code":"${host}","description":"","name":"主机名"},{"code":"${serverName}","description":"","name":"接收请求的服务器名"},{"code":"${serverPort}","description":"","name":"接收请求的服务器端口"},{"code":"${referer}","description":"","name":"请求来源URL"},{"code":"${referer.host}","description":"","name":"请求来源URL域名"},{"code":"${userAgent}","description":"","name":"客户端信息"},{"code":"${contentType}","description":"","name":"请求头部的Content-Type"},{"code":"${cookies}","description":"","name":"所有cookie组合字符串"},{"code":"${cookie.NAME}","description":"","name":"单个cookie值"},{"code":"${args}","description":"","name":"所有参数组合字符串"},{"code":"${arg.NAME}","description":"","name":"单个参数值"},{"code":"${headers}","description":"","name":"所有Header信息组合字符串"},{"code":"${header.NAME}","description":"","name":"单个Header值"}]
+
+window.METRIC_HTTP_KEYS = [{"name":"客户端地址(IP)","code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适用于前端可能有别的反向代理的情形,存在被伪造的可能","icon":""},{"name":"直接客户端地址(IP)","code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","icon":""},{"name":"客户端用户名","code":"${remoteUser}","description":"通过基本认证填入的用户名","icon":""},{"name":"请求URI","code":"${requestURI}","description":"包含参数,比如/hello?name=lily","icon":""},{"name":"请求路径","code":"${requestPath}","description":"不包含参数,比如/hello","icon":""},{"name":"完整URL","code":"${requestURL}","description":"比如https://example.com/hello?name=lily","icon":""},{"name":"请求方法","code":"${requestMethod}","description":"比如GET、POST等","icon":""},{"name":"请求协议Scheme","code":"${scheme}","description":"http或https","icon":""},{"name":"文件扩展名","code":"${requestPathExtension}","description":"请求路径中的文件扩展名,包括点符号,比如.html、.png","icon":""},{"name":"主机名","code":"${host}","description":"通常是请求的域名","icon":""},{"name":"请求协议Proto","code":"${proto}","description":"包含版本的HTTP请求协议,类似于HTTP/1.0","icon":""},{"name":"HTTP协议","code":"${proto}","description":"包含版本的HTTP请求协议,类似于HTTP/1.0","icon":""},{"name":"URL参数值","code":"${arg.NAME}","description":"单个URL参数值","icon":""},{"name":"请求来源URL","code":"${referer}","description":"请求来源Referer URL","icon":""},{"name":"请求来源URL域名","code":"${referer.host}","description":"请求来源Referer URL域名","icon":""},{"name":"Header值","code":"${header.NAME}","description":"单个Header值,比如${header.User-Agent}","icon":""},{"name":"Cookie值","code":"${cookie.NAME}","description":"单个cookie值,比如${cookie.sid}","icon":""},{"name":"状态码","code":"${status}","description":"","icon":""},{"name":"响应的Content-Type值","code":"${response.contentType}","description":"","icon":""}]
+
+window.IP_ADDR_THRESHOLD_ITEMS = [{"code":"nodeAvgRequests","description":"当前节点在单位时间内接收到的平均请求数。","name":"节点平均请求数","unit":"个"},{"code":"nodeAvgTrafficOut","description":"当前节点在单位时间内发送的下行流量。","name":"节点平均下行流量","unit":"M"},{"code":"nodeAvgTrafficIn","description":"当前节点在单位时间内接收的上行流量。","name":"节点平均上行流量","unit":"M"},{"code":"nodeHealthCheck","description":"当前节点健康检查结果。","name":"节点健康检查结果","unit":""},{"code":"connectivity","description":"通过区域监控得到的当前IP地址的连通性数值,取值在0和100之间。","name":"IP连通性","unit":"%"},{"code":"groupAvgRequests","description":"当前节点所在分组在单位时间内接收到的平均请求数。","name":"分组平均请求数","unit":"个"},{"code":"groupAvgTrafficOut","description":"当前节点所在分组在单位时间内发送的下行流量。","name":"分组平均下行流量","unit":"M"},{"code":"groupAvgTrafficIn","description":"当前节点所在分组在单位时间内接收的上行流量。","name":"分组平均上行流量","unit":"M"},{"code":"clusterAvgRequests","description":"当前节点所在集群在单位时间内接收到的平均请求数。","name":"集群平均请求数","unit":"个"},{"code":"clusterAvgTrafficOut","description":"当前节点所在集群在单位时间内发送的下行流量。","name":"集群平均下行流量","unit":"M"},{"code":"clusterAvgTrafficIn","description":"当前节点所在集群在单位时间内接收的上行流量。","name":"集群平均上行流量","unit":"M"}]
+
+window.IP_ADDR_THRESHOLD_ACTIONS = [{"code":"up","description":"上线当前IP。","name":"上线"},{"code":"down","description":"下线当前IP。","name":"下线"},{"code":"notify","description":"发送已达到阈值通知。","name":"通知"},{"code":"switch","description":"在DNS中记录中将IP切换到指定的备用IP。","name":"切换"},{"code":"webHook","description":"调用外部的WebHook。","name":"WebHook"}]
+
diff --git a/web/views/@default/@layout.html b/web/views/@default/@layout.html
index 5543a23a..8a5ec53e 100644
--- a/web/views/@default/@layout.html
+++ b/web/views/@default/@layout.html
@@ -15,7 +15,7 @@
{$TEA.VUE}
{$echo "header"}
-
+
diff --git a/web/views/@default/@layout_popup.html b/web/views/@default/@layout_popup.html
index fea2295b..4aaa4117 100644
--- a/web/views/@default/@layout_popup.html
+++ b/web/views/@default/@layout_popup.html
@@ -12,7 +12,7 @@
{$echo "header"}
-
+
diff --git a/web/views/@default/index/index.html b/web/views/@default/index/index.html
index e73d8f66..9b0f97cd 100644
--- a/web/views/@default/index/index.html
+++ b/web/views/@default/index/index.html
@@ -14,7 +14,7 @@
-
+
diff --git a/web/views/@default/recover/index.html b/web/views/@default/recover/index.html
index 947f343d..466a5402 100644
--- a/web/views/@default/recover/index.html
+++ b/web/views/@default/recover/index.html
@@ -10,7 +10,7 @@
-
+
diff --git a/web/views/@default/setup/confirm/index.html b/web/views/@default/setup/confirm/index.html
index 372a380a..71e6584c 100644
--- a/web/views/@default/setup/confirm/index.html
+++ b/web/views/@default/setup/confirm/index.html
@@ -10,7 +10,7 @@
-
+
diff --git a/web/views/@default/setup/index.html b/web/views/@default/setup/index.html
index 4d5b8875..8d9a034d 100644
--- a/web/views/@default/setup/index.html
+++ b/web/views/@default/setup/index.html
@@ -10,7 +10,7 @@
-
+