Initial commit (code only without large binaries)
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
// 单个集群选择
|
||||
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: `<div>
|
||||
<select class="ui dropdown" style="max-width: 10em" name="clusterId" v-model="clusterId">
|
||||
<option value="0">[选择集群]</option>
|
||||
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,112 @@
|
||||
Vue.component("ddos-protection-ip-list-config-box", {
|
||||
props: ["v-ip-list"],
|
||||
data: function () {
|
||||
let list = this.vIpList
|
||||
if (list == null) {
|
||||
list = []
|
||||
}
|
||||
return {
|
||||
list: list,
|
||||
isAdding: false,
|
||||
addingIP: {
|
||||
ip: "",
|
||||
description: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.addingIPInput.focus()
|
||||
})
|
||||
},
|
||||
confirm: function () {
|
||||
let ip = this.addingIP.ip
|
||||
if (ip.length == 0) {
|
||||
this.warn("请输入IP")
|
||||
return
|
||||
}
|
||||
|
||||
let exists = false
|
||||
this.list.forEach(function (v) {
|
||||
if (v.ip == ip) {
|
||||
exists = true
|
||||
}
|
||||
})
|
||||
if (exists) {
|
||||
this.warn("IP '" + ip + "'已经存在")
|
||||
return
|
||||
}
|
||||
|
||||
let that = this
|
||||
Tea.Vue.$post("/ui/validateIPs")
|
||||
.params({
|
||||
ips: [ip]
|
||||
})
|
||||
.success(function () {
|
||||
that.list.push({
|
||||
ip: ip,
|
||||
description: that.addingIP.description
|
||||
})
|
||||
that.notifyChange()
|
||||
that.cancel()
|
||||
})
|
||||
.fail(function () {
|
||||
that.warn("请输入正确的IP")
|
||||
})
|
||||
},
|
||||
cancel: function () {
|
||||
this.isAdding = false
|
||||
this.addingIP = {
|
||||
ip: "",
|
||||
description: ""
|
||||
}
|
||||
},
|
||||
remove: function (index) {
|
||||
this.list.$remove(index)
|
||||
this.notifyChange()
|
||||
},
|
||||
warn: function (message) {
|
||||
let that = this
|
||||
teaweb.warn(message, function () {
|
||||
that.$refs.addingIPInput.focus()
|
||||
})
|
||||
},
|
||||
notifyChange: function () {
|
||||
this.$emit("change", this.list)
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div v-if="list.length > 0">
|
||||
<div class="ui label basic tiny" v-for="(ipConfig, index) in list">
|
||||
{{ipConfig.ip}} <span class="grey small" v-if="ipConfig.description.length > 0">({{ipConfig.description}})</span> <a href="" @click.prevent="remove(index)" title="删除"><i class="icon remove"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div v-if="isAdding">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui input left labeled">
|
||||
<span class="ui label">IP</span>
|
||||
<input type="text" v-model="addingIP.ip" ref="addingIPInput" maxlength="40" size="20" placeholder="IP" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input left labeled">
|
||||
<span class="ui label">备注</span>
|
||||
<input type="text" v-model="addingIP.description" maxlength="10" size="10" placeholder="备注(可选)" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirm">确定</button>
|
||||
<a href="" @click.prevent="cancel()">取消</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isAdding">
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,115 @@
|
||||
Vue.component("ddos-protection-ports-config-box", {
|
||||
props: ["v-ports"],
|
||||
data: function () {
|
||||
let ports = this.vPorts
|
||||
if (ports == null) {
|
||||
ports = []
|
||||
}
|
||||
return {
|
||||
ports: ports,
|
||||
isAdding: false,
|
||||
addingPort: {
|
||||
port: "",
|
||||
description: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.addingPortInput.focus()
|
||||
})
|
||||
},
|
||||
confirm: function () {
|
||||
let portString = this.addingPort.port
|
||||
if (portString.length == 0) {
|
||||
this.warn("请输入端口号")
|
||||
return
|
||||
}
|
||||
if (!/^\d+$/.test(portString)) {
|
||||
this.warn("请输入正确的端口号")
|
||||
return
|
||||
}
|
||||
let port = parseInt(portString, 10)
|
||||
if (port <= 0) {
|
||||
this.warn("请输入正确的端口号")
|
||||
return
|
||||
}
|
||||
if (port > 65535) {
|
||||
this.warn("请输入正确的端口号")
|
||||
return
|
||||
}
|
||||
|
||||
let exists = false
|
||||
this.ports.forEach(function (v) {
|
||||
if (v.port == port) {
|
||||
exists = true
|
||||
}
|
||||
})
|
||||
if (exists) {
|
||||
this.warn("端口号已经存在")
|
||||
return
|
||||
}
|
||||
|
||||
this.ports.push({
|
||||
port: port,
|
||||
description: this.addingPort.description
|
||||
})
|
||||
this.notifyChange()
|
||||
this.cancel()
|
||||
},
|
||||
cancel: function () {
|
||||
this.isAdding = false
|
||||
this.addingPort = {
|
||||
port: "",
|
||||
description: ""
|
||||
}
|
||||
},
|
||||
remove: function (index) {
|
||||
this.ports.$remove(index)
|
||||
this.notifyChange()
|
||||
},
|
||||
warn: function (message) {
|
||||
let that = this
|
||||
teaweb.warn(message, function () {
|
||||
that.$refs.addingPortInput.focus()
|
||||
})
|
||||
},
|
||||
notifyChange: function () {
|
||||
this.$emit("change", this.ports)
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div v-if="ports.length > 0">
|
||||
<div class="ui label basic tiny" v-for="(portConfig, index) in ports">
|
||||
{{portConfig.port}} <span class="grey small" v-if="portConfig.description.length > 0">({{portConfig.description}})</span> <a href="" @click.prevent="remove(index)" title="删除"><i class="icon remove"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div v-if="isAdding">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui input left labeled">
|
||||
<span class="ui label">端口</span>
|
||||
<input type="text" v-model="addingPort.port" ref="addingPortInput" maxlength="5" size="5" placeholder="端口号" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input left labeled">
|
||||
<span class="ui label">备注</span>
|
||||
<input type="text" v-model="addingPort.description" maxlength="12" size="12" placeholder="备注(可选)" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirm">确定</button>
|
||||
<a href="" @click.prevent="cancel()">取消</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isAdding">
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
Vue.component("node-cluster-combo-box", {
|
||||
props: ["v-cluster-id"],
|
||||
data: function () {
|
||||
let that = this
|
||||
Tea.action("/clusters/options")
|
||||
.post()
|
||||
.success(function (resp) {
|
||||
that.clusters = resp.data.clusters
|
||||
})
|
||||
return {
|
||||
clusters: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change: function (item) {
|
||||
if (item == null) {
|
||||
this.$emit("change", 0)
|
||||
} else {
|
||||
this.$emit("change", item.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `<div v-if="clusters.length > 0" style="min-width: 10.4em">
|
||||
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" name="clusterId" :v-value="vClusterId" @change="change"></combo-box>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
// 显示节点的多个集群
|
||||
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: `<div>
|
||||
<a v-if="cluster != null" :href="'/clusters/cluster?clusterId=' + cluster.id" title="主集群" style="margin-bottom: 0.3em;">
|
||||
<span class="ui label basic grey" :class="labelSize" v-if="labelSize != 'tiny'">{{cluster.name}}</span>
|
||||
<grey-label v-if="labelSize == 'tiny'">{{cluster.name}}</grey-label>
|
||||
</a>
|
||||
<a v-for="c in secondaryClusters" :href="'/clusters/cluster?clusterId=' + c.id" :class="labelSize" title="从集群">
|
||||
<span class="ui label basic grey" :class="labelSize" v-if="labelSize != 'tiny'">{{c.name}}</span>
|
||||
<grey-label v-if="labelSize == 'tiny'">{{c.name}}</grey-label>
|
||||
</a>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,98 @@
|
||||
// 一个节点的多个集群选择器
|
||||
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: `<div>
|
||||
<input type="hidden" name="primaryClusterId" :value="primaryClusterId"/>
|
||||
<input type="hidden" name="secondaryClusterIds" :value="JSON.stringify(secondaryClusterIds)"/>
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">主集群</td>
|
||||
<td>
|
||||
<div v-if="primaryCluster != null">
|
||||
<div class="ui label basic small">{{primaryCluster.name}} <a href="" title="删除" @click.prevent="removePrimary"><i class="icon remove small"></i></a> </div>
|
||||
</div>
|
||||
<div style="margin-top: 0.6em" v-if="primaryClusterId == 0">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addPrimary">+</button>
|
||||
</div>
|
||||
<p class="comment">多个集群配置有冲突时,优先使用主集群配置。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>从集群</td>
|
||||
<td>
|
||||
<div v-if="secondaryClusters.length > 0">
|
||||
<div class="ui label basic small" v-for="(cluster, index) in secondaryClusters"><span class="grey">{{cluster.name}}</span> <a href="" title="删除" @click.prevent="removeSecondary(index)"><i class="icon remove small"></i></a> </div>
|
||||
</div>
|
||||
<div style="margin-top: 0.6em">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addSecondary">+</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,154 @@
|
||||
Vue.component("node-ddos-protection-config-box", {
|
||||
props: ["v-ddos-protection-config", "v-default-configs", "v-is-node", "v-cluster-is-on"],
|
||||
data: function () {
|
||||
let config = this.vDdosProtectionConfig
|
||||
if (config == null) {
|
||||
config = {
|
||||
tcp: {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
maxConnections: 0,
|
||||
maxConnectionsPerIP: 0,
|
||||
newConnectionsRate: 0,
|
||||
newConnectionsRateBlockTimeout: 0,
|
||||
newConnectionsSecondlyRate: 0,
|
||||
newConnectionSecondlyRateBlockTimeout: 0,
|
||||
allowIPList: [],
|
||||
ports: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initialize
|
||||
if (config.tcp == null) {
|
||||
config.tcp = {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
maxConnections: 0,
|
||||
maxConnectionsPerIP: 0,
|
||||
newConnectionsRate: 0,
|
||||
newConnectionsRateBlockTimeout: 0,
|
||||
newConnectionsSecondlyRate: 0,
|
||||
newConnectionSecondlyRateBlockTimeout: 0,
|
||||
allowIPList: [],
|
||||
ports: []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
config: config,
|
||||
defaultConfigs: this.vDefaultConfigs,
|
||||
isNode: this.vIsNode,
|
||||
|
||||
isAddingPort: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeTCPPorts: function (ports) {
|
||||
this.config.tcp.ports = ports
|
||||
},
|
||||
changeTCPAllowIPList: function (ipList) {
|
||||
this.config.tcp.allowIPList = ipList
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="ddosProtectionJSON" :value="JSON.stringify(config)"/>
|
||||
|
||||
<p class="comment">功能说明:此功能为<strong>试验性质</strong>,目前仅能防御简单的DDoS攻击,试验期间建议仅在被攻击时启用,仅支持已安装<code-label>nftables v0.9</code-label>以上的Linux系统。<pro-warning-label></pro-warning-label></p>
|
||||
|
||||
<div class="ui message" v-if="vClusterIsOn">当前节点所在集群已设置DDoS防护。</div>
|
||||
|
||||
<h4>TCP设置</h4>
|
||||
<table class="ui table definition selectable">
|
||||
<prior-checkbox :v-config="config.tcp" v-if="isNode"></prior-checkbox>
|
||||
<tbody v-show="config.tcp.isPrior || !isNode">
|
||||
<tr>
|
||||
<td class="title">启用DDoS防护</td>
|
||||
<td>
|
||||
<checkbox v-model="config.tcp.isOn"></checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-show="config.tcp.isOn && (config.tcp.isPrior || !isNode)">
|
||||
<tr>
|
||||
<td class="title">单节点TCP最大连接数</td>
|
||||
<td>
|
||||
<digit-input name="tcpMaxConnections" v-model="config.tcp.maxConnections" maxlength="6" size="6" style="width: 6em"></digit-input>
|
||||
<p class="comment">单个节点可以接受的TCP最大连接数。如果为0,则默认为{{defaultConfigs.tcpMaxConnections}}。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>单IP TCP最大连接数</td>
|
||||
<td>
|
||||
<digit-input name="tcpMaxConnectionsPerIP" v-model="config.tcp.maxConnectionsPerIP" maxlength="6" size="6" style="width: 6em"></digit-input>
|
||||
<p class="comment">单个IP可以连接到节点的TCP最大连接数。如果为0,则默认为{{defaultConfigs.tcpMaxConnectionsPerIP}};最小值为{{defaultConfigs.tcpMinConnectionsPerIP}}。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>单IP TCP新连接速率<em>(分钟)</em></td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<digit-input name="tcpNewConnectionsRate" v-model="config.tcp.newConnectionsRate" maxlength="6" size="6" style="width: 6em" :min="defaultConfigs.tcpNewConnectionsMinRate"></digit-input>
|
||||
<span class="ui label">个新连接/每分钟</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field" style="line-height: 2.4em">
|
||||
屏蔽
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<digit-input name="tcpNewConnectionsRateBlockTimeout" v-model="config.tcp.newConnectionsRateBlockTimeout" maxlength="6" size="6" style="width: 5em"></digit-input>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="comment">单个IP每分钟可以创建TCP新连接的数量。如果为0,则默认为{{defaultConfigs.tcpNewConnectionsMinutelyRate}};最小值为{{defaultConfigs.tcpNewConnectionsMinMinutelyRate}}。如果没有填写屏蔽时间,则只丢弃数据包。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>单IP TCP新连接速率<em>(秒钟)</em></td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<digit-input name="tcpNewConnectionsSecondlyRate" v-model="config.tcp.newConnectionsSecondlyRate" maxlength="6" size="6" style="width: 6em" :min="defaultConfigs.tcpNewConnectionsMinRate"></digit-input>
|
||||
<span class="ui label">个新连接/每秒钟</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field" style="line-height: 2.4em">
|
||||
屏蔽
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<digit-input name="tcpNewConnectionsSecondlyRateBlockTimeout" v-model="config.tcp.newConnectionsSecondlyRateBlockTimeout" maxlength="6" size="6" style="width: 5em"></digit-input>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="comment">单个IP每秒钟可以创建TCP新连接的数量。如果为0,则默认为{{defaultConfigs.tcpNewConnectionsSecondlyRate}};最小值为{{defaultConfigs.tcpNewConnectionsMinSecondlyRate}}。如果没有填写屏蔽时间,则只丢弃数据包。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TCP端口列表</td>
|
||||
<td>
|
||||
<ddos-protection-ports-config-box :v-ports="config.tcp.ports" @change="changeTCPPorts"></ddos-protection-ports-config-box>
|
||||
<p class="comment">在这些端口上使用当前配置。默认为80和443两个端口。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP白名单</td>
|
||||
<td>
|
||||
<ddos-protection-ip-list-config-box :v-ip-list="config.tcp.allowIPList" @change="changeTCPAllowIPList"></ddos-protection-ip-list-config-box>
|
||||
<p class="comment">在白名单中的IP不受当前设置的限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
Reference in New Issue
Block a user