1.4.5.2
This commit is contained in:
67
EdgeAdmin/web/public/js/components/ns/ns-access-log-box.js
Normal file
67
EdgeAdmin/web/public/js/components/ns/ns-access-log-box.js
Normal file
@@ -0,0 +1,67 @@
|
||||
Vue.component("ns-access-log-box", {
|
||||
props: ["v-access-log", "v-keyword"],
|
||||
data: function () {
|
||||
let accessLog = this.vAccessLog
|
||||
let isFailure = false
|
||||
|
||||
if (accessLog.isRecursive) {
|
||||
if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
} else {
|
||||
if (accessLog.recordType == "SOA" || accessLog.recordType == "NS") {
|
||||
if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
}
|
||||
|
||||
// 没有找到记录的不需要高亮显示,防止管理员看到红色就心理恐慌
|
||||
}
|
||||
|
||||
return {
|
||||
accessLog: accessLog,
|
||||
isFailure: isFailure
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showLog: function () {
|
||||
let that = this
|
||||
let requestId = this.accessLog.requestId
|
||||
this.$parent.$children.forEach(function (v) {
|
||||
if (v.deselect != null) {
|
||||
v.deselect()
|
||||
}
|
||||
})
|
||||
this.select()
|
||||
|
||||
teaweb.popup("/ns/clusters/accessLogs/viewPopup?requestId=" + requestId, {
|
||||
width: "50em",
|
||||
height: "24em",
|
||||
onClose: function () {
|
||||
that.deselect()
|
||||
}
|
||||
})
|
||||
},
|
||||
select: function () {
|
||||
this.$refs.box.parentNode.style.cssText = "background: rgba(0, 0, 0, 0.1)"
|
||||
},
|
||||
deselect: function () {
|
||||
this.$refs.box.parentNode.style.cssText = ""
|
||||
}
|
||||
},
|
||||
template: `<div class="access-log-row" :style="{'color': isFailure ? '#dc143c' : ''}" ref="box">
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey">[{{accessLog.region}}]</span> <keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword> [{{accessLog.timeLocal}}] [{{accessLog.networking}}] <em>{{accessLog.questionType}} <keyword :v-word="vKeyword">{{accessLog.questionName}}</keyword></em> ->
|
||||
|
||||
<span v-if="accessLog.recordType != null && accessLog.recordType.length > 0"><em>{{accessLog.recordType}} <keyword :v-word="vKeyword">{{accessLog.recordValue}}</keyword></em></span>
|
||||
<span v-else class="disabled"> [没有记录]</span>
|
||||
|
||||
<!-- <a href="" @click.prevent="showLog" title="查看详情"><i class="icon expand"></i></a>-->
|
||||
<div v-if="(accessLog.nsRoutes != null && accessLog.nsRoutes.length > 0) || accessLog.isRecursive" style="margin-top: 0.3em">
|
||||
<span class="ui label tiny basic grey" v-for="route in accessLog.nsRoutes">线路: {{route.name}}</span>
|
||||
<span class="ui label tiny basic grey" v-if="accessLog.isRecursive">递归DNS</span>
|
||||
</div>
|
||||
<div v-if="accessLog.error != null && accessLog.error.length > 0" style="color:#dc143c">
|
||||
<i class="icon warning circle"></i>错误:[{{accessLog.error}}]
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
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: `<div>
|
||||
<input type="hidden" name="accessLogJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table definition selectable">
|
||||
<prior-checkbox :v-config="config" v-if="!vIsParent"></prior-checkbox>
|
||||
<tbody v-show="vIsParent || config.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td>
|
||||
<checkbox name="isOn" value="1" v-model="config.isOn"></checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>只记录失败查询</td>
|
||||
<td>
|
||||
<checkbox v-model="config.missingRecordsOnly"></checkbox>
|
||||
<p class="comment">选中后,表示只记录查询失败的日志。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>包含未添加的域名</td>
|
||||
<td>
|
||||
<checkbox name="logMissingDomains" value="1" v-model="config.logMissingDomains"></checkbox>
|
||||
<p class="comment">选中后,表示日志中包含对没有在系统里创建的域名访问。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
Vue.component("ns-cluster-combo-box", {
|
||||
props: ["v-cluster-id", "name"],
|
||||
data: function () {
|
||||
let that = this
|
||||
Tea.action("/ns/clusters/options")
|
||||
.post()
|
||||
.success(function (resp) {
|
||||
that.clusters = resp.data.clusters
|
||||
})
|
||||
|
||||
|
||||
let inputName = "clusterId"
|
||||
if (this.name != null && this.name.length > 0) {
|
||||
inputName = this.name
|
||||
}
|
||||
|
||||
return {
|
||||
clusters: [],
|
||||
inputName: inputName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change: function (item) {
|
||||
if (item == null) {
|
||||
this.$emit("change", 0)
|
||||
} else {
|
||||
this.$emit("change", item.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `<div v-if="clusters.length > 0" style="min-width: 10.4em">
|
||||
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" :name="inputName" :v-value="vClusterId" @change="change"></combo-box>
|
||||
</div>`
|
||||
})
|
||||
28
EdgeAdmin/web/public/js/components/ns/ns-cluster-selector.js
Normal file
28
EdgeAdmin/web/public/js/components/ns/ns-cluster-selector.js
Normal file
@@ -0,0 +1,28 @@
|
||||
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: `<div>
|
||||
<select class="ui dropdown auto-width" name="clusterId" v-model="clusterId">
|
||||
<option value="0">[选择集群]</option>
|
||||
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
</div>`
|
||||
})
|
||||
102
EdgeAdmin/web/public/js/components/ns/ns-create-records-table.js
Normal file
102
EdgeAdmin/web/public/js/components/ns/ns-create-records-table.js
Normal file
@@ -0,0 +1,102 @@
|
||||
Vue.component("ns-create-records-table", {
|
||||
props: ["v-types"],
|
||||
data: function () {
|
||||
let types = this.vTypes
|
||||
if (types == null) {
|
||||
types = []
|
||||
}
|
||||
return {
|
||||
types: types,
|
||||
records: [
|
||||
{
|
||||
name: "",
|
||||
type: "A",
|
||||
value: "",
|
||||
routeCodes: [],
|
||||
ttl: 600,
|
||||
index: 0
|
||||
}
|
||||
],
|
||||
lastIndex: 0,
|
||||
isAddingRoutes: false // 是否正在添加线路
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add: function () {
|
||||
this.records.push({
|
||||
name: "",
|
||||
type: "A",
|
||||
value: "",
|
||||
routeCodes: [],
|
||||
ttl: 600,
|
||||
index: ++this.lastIndex
|
||||
})
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.nameInputs.$last().focus()
|
||||
}, 100)
|
||||
},
|
||||
remove: function (index) {
|
||||
this.records.$remove(index)
|
||||
},
|
||||
addRoutes: function () {
|
||||
this.isAddingRoutes = true
|
||||
},
|
||||
cancelRoutes: function () {
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.isAddingRoutes = false
|
||||
}, 1000)
|
||||
},
|
||||
changeRoutes: function (record, routes) {
|
||||
if (routes == null) {
|
||||
record.routeCodes = []
|
||||
} else {
|
||||
record.routeCodes = routes.map(function (route) {
|
||||
return route.code
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="recordsJSON" :value="JSON.stringify(records)"/>
|
||||
<table class="ui table selectable celled" style="max-width: 60em">
|
||||
<thead class="full-width">
|
||||
<tr>
|
||||
<th style="width:10em">记录名</th>
|
||||
<th style="width:7em">记录类型</th>
|
||||
<th>线路</th>
|
||||
<th v-if="!isAddingRoutes">记录值</th>
|
||||
<th v-if="!isAddingRoutes">TTL</th>
|
||||
<th class="one op" v-if="!isAddingRoutes">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="(record, index) in records" :key="record.index">
|
||||
<td>
|
||||
<input type="text" style="width:10em" v-model="record.name" ref="nameInputs"/>
|
||||
</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="record.type">
|
||||
<option v-for="type in types" :value="type.type">{{type.type}}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<ns-routes-selector @add="addRoutes" @cancel="cancelRoutes" @change="changeRoutes(record, $event)"></ns-routes-selector>
|
||||
</td>
|
||||
<td v-if="!isAddingRoutes">
|
||||
<input type="text" style="width:10em" maxlength="512" v-model="record.value"/>
|
||||
</td>
|
||||
<td v-if="!isAddingRoutes">
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" v-model="record.ttl" style="width:5em" maxlength="8"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="!isAddingRoutes">
|
||||
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
|
||||
</div>`,
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
Vue.component("ns-domain-group-selector", {
|
||||
props: ["v-domain-group-id"],
|
||||
data: function () {
|
||||
let groupId = this.vDomainGroupId
|
||||
if (groupId == null) {
|
||||
groupId = 0
|
||||
}
|
||||
return {
|
||||
userId: 0,
|
||||
groupId: groupId
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change: function (group) {
|
||||
if (group != null) {
|
||||
this.$emit("change", group.id)
|
||||
} else {
|
||||
this.$emit("change", 0)
|
||||
}
|
||||
},
|
||||
reload: function (userId) {
|
||||
this.userId = userId
|
||||
this.$refs.comboBox.clear()
|
||||
this.$refs.comboBox.setDataURL("/ns/domains/groups/options?userId=" + userId)
|
||||
this.$refs.comboBox.reloadData()
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<combo-box
|
||||
data-url="/ns/domains/groups/options"
|
||||
placeholder="选择分组"
|
||||
data-key="groups"
|
||||
name="groupId"
|
||||
:v-value="groupId"
|
||||
@change="change"
|
||||
ref="comboBox">
|
||||
</combo-box>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,154 @@
|
||||
Vue.component("ns-node-ddos-protection-config-box", {
|
||||
props: ["v-ddos-protection-config", "v-default-configs", "v-is-node", "v-cluster-is-on"],
|
||||
data: function () {
|
||||
let config = this.vDdosProtectionConfig
|
||||
if (config == null) {
|
||||
config = {
|
||||
tcp: {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
maxConnections: 0,
|
||||
maxConnectionsPerIP: 0,
|
||||
newConnectionsRate: 0,
|
||||
newConnectionsRateBlockTimeout: 0,
|
||||
newConnectionsSecondlyRate: 0,
|
||||
newConnectionSecondlyRateBlockTimeout: 0,
|
||||
allowIPList: [],
|
||||
ports: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initialize
|
||||
if (config.tcp == null) {
|
||||
config.tcp = {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
maxConnections: 0,
|
||||
maxConnectionsPerIP: 0,
|
||||
newConnectionsRate: 0,
|
||||
newConnectionsRateBlockTimeout: 0,
|
||||
newConnectionsSecondlyRate: 0,
|
||||
newConnectionSecondlyRateBlockTimeout: 0,
|
||||
allowIPList: [],
|
||||
ports: []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
config: config,
|
||||
defaultConfigs: this.vDefaultConfigs,
|
||||
isNode: this.vIsNode,
|
||||
|
||||
isAddingPort: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeTCPPorts: function (ports) {
|
||||
this.config.tcp.ports = ports
|
||||
},
|
||||
changeTCPAllowIPList: function (ipList) {
|
||||
this.config.tcp.allowIPList = ipList
|
||||
}
|
||||
},
|
||||
template: `<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">启用</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">在这些端口上使用当前配置。默认为53端口。</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>`
|
||||
})
|
||||
@@ -0,0 +1,154 @@
|
||||
Vue.component("ns-record-health-check-config-box", {
|
||||
props:["value", "v-parent-config"],
|
||||
data: function () {
|
||||
let config = this.value
|
||||
if (config == null) {
|
||||
config = {
|
||||
isOn: false,
|
||||
port: 0,
|
||||
timeoutSeconds: 0,
|
||||
countUp: 0,
|
||||
countDown: 0
|
||||
}
|
||||
}
|
||||
|
||||
let parentConfig = this.vParentConfig
|
||||
|
||||
return {
|
||||
config: config,
|
||||
portString: config.port.toString(),
|
||||
timeoutSecondsString: config.timeoutSeconds.toString(),
|
||||
countUpString: config.countUp.toString(),
|
||||
countDownString: config.countDown.toString(),
|
||||
|
||||
portIsEditing: config.port > 0,
|
||||
timeoutSecondsIsEditing: config.timeoutSeconds > 0,
|
||||
countUpIsEditing: config.countUp > 0,
|
||||
countDownIsEditing: config.countDown > 0,
|
||||
|
||||
parentConfig: parentConfig
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
portString: function (value) {
|
||||
let port = parseInt(value.toString())
|
||||
if (isNaN(port) || port > 65535 || port < 1) {
|
||||
this.config.port = 0
|
||||
} else {
|
||||
this.config.port = port
|
||||
}
|
||||
},
|
||||
timeoutSecondsString: function (value) {
|
||||
let timeoutSeconds = parseInt(value.toString())
|
||||
if (isNaN(timeoutSeconds) || timeoutSeconds > 1000 || timeoutSeconds < 1) {
|
||||
this.config.timeoutSeconds = 0
|
||||
} else {
|
||||
this.config.timeoutSeconds = timeoutSeconds
|
||||
}
|
||||
},
|
||||
countUpString: function (value) {
|
||||
let countUp = parseInt(value.toString())
|
||||
if (isNaN(countUp) || countUp > 1000 || countUp < 1) {
|
||||
this.config.countUp = 0
|
||||
} else {
|
||||
this.config.countUp = countUp
|
||||
}
|
||||
},
|
||||
countDownString: function (value) {
|
||||
let countDown = parseInt(value.toString())
|
||||
if (isNaN(countDown) || countDown > 1000 || countDown < 1) {
|
||||
this.config.countDown = 0
|
||||
} else {
|
||||
this.config.countDown = countDown
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="recordHealthCheckJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="title">启用当前记录健康检查</td>
|
||||
<td>
|
||||
<checkbox v-model="config.isOn"></checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-show="config.isOn">
|
||||
<tr>
|
||||
<td>检测端口</td>
|
||||
<td>
|
||||
<span v-if="!portIsEditing" class="grey">
|
||||
默认{{parentConfig.port}}
|
||||
<a href="" @click.prevent="portIsEditing = true; portString = parentConfig.port">[修改]</a>
|
||||
</span>
|
||||
<div v-show="portIsEditing">
|
||||
<div style="margin-bottom: 0.5em">
|
||||
<a href="" @click.prevent="portIsEditing = false; portString = '0'">[使用默认]</a>
|
||||
</div>
|
||||
<input type="text" v-model="portString" maxlength="5" style="width: 5em"/>
|
||||
<p class="comment">通过尝试连接A/AAAA记录中的IP加此端口来确定当前记录是否健康。</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>超时时间</td>
|
||||
<td>
|
||||
<span v-if="!timeoutSecondsIsEditing" class="grey">
|
||||
默认{{parentConfig.timeoutSeconds}}秒
|
||||
<a href="" @click.prevent="timeoutSecondsIsEditing = true; timeoutSecondsString = parentConfig.timeoutSeconds">[修改]</a>
|
||||
</span>
|
||||
<div v-show="timeoutSecondsIsEditing">
|
||||
<div style="margin-bottom: 0.5em">
|
||||
<a href="" @click.prevent="timeoutSecondsIsEditing = false; timeoutSecondsString = '0'">[使用默认]</a>
|
||||
</div>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" style="width: 4em" v-model="timeoutSecondsString" maxlength="3"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认连续上线次数</td>
|
||||
<td>
|
||||
<span v-if="!countUpIsEditing" class="grey">
|
||||
默认{{parentConfig.countUp}}次
|
||||
<a href="" @click.prevent="countUpIsEditing = true; countUpString = parentConfig.countUp">[修改]</a>
|
||||
</span>
|
||||
<div v-show="countUpIsEditing">
|
||||
<div style="margin-bottom: 0.5em">
|
||||
<a href="" @click.prevent="countUpIsEditing = false; countUpString = '0'">[使用默认]</a>
|
||||
</div>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" style="width: 4em" v-model="countUpString" maxlength="3"/>
|
||||
<span class="ui label">次</span>
|
||||
</div>
|
||||
<p class="comment">连续检测<span v-if="config.countUp > 0">{{config.countUp}}</span><span v-else>N</span>次成功后,认为当前记录是在线的。</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认连续下线次数</td>
|
||||
<td>
|
||||
<span v-if="!countDownIsEditing" class="grey">
|
||||
默认{{parentConfig.countDown}}次
|
||||
<a href="" @click.prevent="countDownIsEditing = true; countDownString = parentConfig.countDown">[修改]</a>
|
||||
</span>
|
||||
<div v-show="countDownIsEditing">
|
||||
<div style="margin-bottom: 0.5em">
|
||||
<a href="" @click.prevent="countDownIsEditing = false; countDownString = '0'">[使用默认]</a>
|
||||
</div>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" style="width: 4em" v-model="countDownString" maxlength="3"/>
|
||||
<span class="ui label">次</span>
|
||||
</div>
|
||||
<p class="comment">连续检测<span v-if="config.countDown > 0">{{config.countDown}}</span><span v-else>N</span>次失败后,认为当前记录是离线的。</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
@@ -0,0 +1,109 @@
|
||||
Vue.component("ns-records-health-check-config-box", {
|
||||
props:["value"],
|
||||
data: function () {
|
||||
let config = this.value
|
||||
if (config == null) {
|
||||
config = {
|
||||
isOn: false,
|
||||
port: 80,
|
||||
timeoutSeconds: 5,
|
||||
countUp: 1,
|
||||
countDown: 3
|
||||
}
|
||||
}
|
||||
return {
|
||||
config: config,
|
||||
portString: config.port.toString(),
|
||||
timeoutSecondsString: config.timeoutSeconds.toString(),
|
||||
countUpString: config.countUp.toString(),
|
||||
countDownString: config.countDown.toString()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
portString: function (value) {
|
||||
let port = parseInt(value.toString())
|
||||
if (isNaN(port) || port > 65535 || port < 1) {
|
||||
this.config.port = 80
|
||||
} else {
|
||||
this.config.port = port
|
||||
}
|
||||
},
|
||||
timeoutSecondsString: function (value) {
|
||||
let timeoutSeconds = parseInt(value.toString())
|
||||
if (isNaN(timeoutSeconds) || timeoutSeconds > 1000 || timeoutSeconds < 1) {
|
||||
this.config.timeoutSeconds = 5
|
||||
} else {
|
||||
this.config.timeoutSeconds = timeoutSeconds
|
||||
}
|
||||
},
|
||||
countUpString: function (value) {
|
||||
let countUp = parseInt(value.toString())
|
||||
if (isNaN(countUp) || countUp > 1000 || countUp < 1) {
|
||||
this.config.countUp = 1
|
||||
} else {
|
||||
this.config.countUp = countUp
|
||||
}
|
||||
},
|
||||
countDownString: function (value) {
|
||||
let countDown = parseInt(value.toString())
|
||||
if (isNaN(countDown) || countDown > 1000 || countDown < 1) {
|
||||
this.config.countDown = 3
|
||||
} else {
|
||||
this.config.countDown = countDown
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="recordsHealthCheckJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="title">启用健康检查</td>
|
||||
<td>
|
||||
<checkbox v-model="config.isOn"></checkbox>
|
||||
<p class="comment">选中后,表示启用当前域名下A/AAAA记录的健康检查;启用此设置后,你仍需设置单个A/AAAA记录的健康检查。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-show="config.isOn">
|
||||
<tr>
|
||||
<td>默认检测端口</td>
|
||||
<td>
|
||||
<input type="text" v-model="portString" maxlength="5" style="width: 5em"/>
|
||||
<p class="comment">通过尝试连接A/AAAA记录中的IP加此端口来确定当前记录是否健康。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认超时时间</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" style="width: 4em" v-model="timeoutSecondsString" maxlength="3"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认连续上线次数</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" style="width: 4em" v-model="countUpString" maxlength="3"/>
|
||||
<span class="ui label">次</span>
|
||||
</div>
|
||||
<p class="comment">连续检测<span v-if="config.countUp > 0">{{config.countUp}}</span><span v-else>N</span>次成功后,认为当前记录是在线的。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认连续下线次数</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" style="width: 4em" v-model="countDownString" maxlength="3"/>
|
||||
<span class="ui label">次</span>
|
||||
</div>
|
||||
<p class="comment">连续检测<span v-if="config.countDown > 0">{{config.countDown}}</span><span v-else>N</span>次失败后,认为当前记录是离线的。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
165
EdgeAdmin/web/public/js/components/ns/ns-recursion-config-box.js
Normal file
165
EdgeAdmin/web/public/js/components/ns/ns-recursion-config-box.js
Normal file
@@ -0,0 +1,165 @@
|
||||
// 递归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: `<div>
|
||||
<input type="hidden" name="recursionJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="isOn" value="1" v-model="config.isOn"/>
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">启用后,如果找不到某个域名的解析记录,则向上一级DNS查找。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-show="config.isOn">
|
||||
<tr>
|
||||
<td>从节点本机读取<br/>上级DNS主机</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="useLocalHosts" value="1" v-model="config.useLocalHosts"/>
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">选中后,节点会试图从<code-label>/etc/resolv.conf</code-label>文件中读取DNS配置。 </p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!config.useLocalHosts">
|
||||
<td>上级DNS主机地址 *</td>
|
||||
<td>
|
||||
<div v-if="config.hosts.length > 0">
|
||||
<div v-for="(host, index) in config.hosts" class="ui label tiny basic">
|
||||
{{host.host}}
|
||||
<a href="" title="修改" @click.prevent="updateHost(host)"><i class="icon pencil tiny"></i></a>
|
||||
<a href="" title="删除" @click.prevent="removeHost(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div v-if="hostIsAdding">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" placeholder="DNS主机地址" v-model="host" ref="hostRef" @keyup.enter="confirmHost" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmHost">确认</button> <a href="" title="取消" @click.prevent="cancelHost"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 0.5em">
|
||||
<button type="button" class="ui button tiny" @click.prevent="addHost">+</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>允许的域名</td>
|
||||
<td><values-box name="allowDomains" :values="config.allowDomains" @change="changeAllowDomains"></values-box>
|
||||
<p class="comment">支持星号通配符,比如<code-label>*.example.org</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>不允许的域名</td>
|
||||
<td>
|
||||
<values-box name="denyDomains" :values="config.denyDomains" @change="changeDenyDomains"></values-box>
|
||||
<p class="comment">支持星号通配符,比如<code-label>*.example.org</code-label>。优先级比允许的域名高。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
585
EdgeAdmin/web/public/js/components/ns/ns-route-ranges-box.js
Normal file
585
EdgeAdmin/web/public/js/components/ns/ns-route-ranges-box.js
Normal file
@@ -0,0 +1,585 @@
|
||||
Vue.component("ns-route-ranges-box", {
|
||||
props: ["v-ranges"],
|
||||
data: function () {
|
||||
let ranges = this.vRanges
|
||||
if (ranges == null) {
|
||||
ranges = []
|
||||
}
|
||||
return {
|
||||
ranges: ranges,
|
||||
isAdding: false,
|
||||
isAddingBatch: false,
|
||||
|
||||
// 类型
|
||||
rangeType: "ipRange",
|
||||
isReverse: false,
|
||||
|
||||
// IP范围
|
||||
ipRangeFrom: "",
|
||||
ipRangeTo: "",
|
||||
|
||||
batchIPRange: "",
|
||||
|
||||
// CIDR
|
||||
ipCIDR: "",
|
||||
batchIPCIDR: "",
|
||||
|
||||
// region
|
||||
regions: [],
|
||||
regionType: "country",
|
||||
regionConnector: "OR"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addIPRange: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.ipRangeFrom.focus()
|
||||
}, 100)
|
||||
},
|
||||
addCIDR: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.ipCIDR.focus()
|
||||
}, 100)
|
||||
},
|
||||
addRegions: function () {
|
||||
this.isAdding = true
|
||||
},
|
||||
addRegion: function (regionType) {
|
||||
this.regionType = regionType
|
||||
},
|
||||
remove: function (index) {
|
||||
this.ranges.$remove(index)
|
||||
},
|
||||
cancelIPRange: function () {
|
||||
this.isAdding = false
|
||||
this.ipRangeFrom = ""
|
||||
this.ipRangeTo = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
cancelIPCIDR: function () {
|
||||
this.isAdding = false
|
||||
this.ipCIDR = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
cancelRegions: function () {
|
||||
this.isAdding = false
|
||||
this.regions = []
|
||||
this.regionType = "country"
|
||||
this.regionConnector = "OR"
|
||||
this.isReverse = false
|
||||
},
|
||||
confirmIPRange: function () {
|
||||
// 校验IP
|
||||
let that = this
|
||||
this.ipRangeFrom = this.ipRangeFrom.trim()
|
||||
if (!this.validateIP(this.ipRangeFrom)) {
|
||||
teaweb.warn("开始IP填写错误", function () {
|
||||
that.$refs.ipRangeFrom.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.ipRangeTo = this.ipRangeTo.trim()
|
||||
if (!this.validateIP(this.ipRangeTo)) {
|
||||
teaweb.warn("结束IP填写错误", function () {
|
||||
that.$refs.ipRangeTo.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.ranges.push({
|
||||
type: "ipRange",
|
||||
params: {
|
||||
ipFrom: this.ipRangeFrom,
|
||||
ipTo: this.ipRangeTo,
|
||||
isReverse: this.isReverse
|
||||
}
|
||||
})
|
||||
this.cancelIPRange()
|
||||
},
|
||||
confirmIPCIDR: function () {
|
||||
let that = this
|
||||
if (this.ipCIDR.length == 0) {
|
||||
teaweb.warn("请填写CIDR", function () {
|
||||
that.$refs.ipCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.validateCIDR(this.ipCIDR)) {
|
||||
teaweb.warn("请输入正确的CIDR", function () {
|
||||
that.$refs.ipCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
this.ranges.push({
|
||||
type: "cidr",
|
||||
params: {
|
||||
cidr: this.ipCIDR,
|
||||
isReverse: this.isReverse
|
||||
}
|
||||
})
|
||||
this.cancelIPCIDR()
|
||||
},
|
||||
confirmRegions: function () {
|
||||
if (this.regions.length == 0) {
|
||||
this.cancelRegions()
|
||||
return
|
||||
}
|
||||
this.ranges.push({
|
||||
type: "region",
|
||||
connector: this.regionConnector,
|
||||
params: {
|
||||
regions: this.regions,
|
||||
isReverse: this.isReverse
|
||||
}
|
||||
})
|
||||
this.cancelRegions()
|
||||
},
|
||||
addBatchIPRange: function () {
|
||||
this.isAddingBatch = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.batchIPRange.focus()
|
||||
}, 100)
|
||||
},
|
||||
addBatchCIDR: function () {
|
||||
this.isAddingBatch = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.batchIPCIDR.focus()
|
||||
}, 100)
|
||||
},
|
||||
cancelBatchIPRange: function () {
|
||||
this.isAddingBatch = false
|
||||
this.batchIPRange = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
cancelBatchIPCIDR: function () {
|
||||
this.isAddingBatch = false
|
||||
this.batchIPCIDR = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
confirmBatchIPRange: function () {
|
||||
let that = this
|
||||
let rangesText = this.batchIPRange
|
||||
if (rangesText.length == 0) {
|
||||
teaweb.warn("请填写要加入的IP范围", function () {
|
||||
that.$refs.batchIPRange.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let validRanges = []
|
||||
let invalidLine = ""
|
||||
rangesText.split("\n").forEach(function (line) {
|
||||
line = line.trim()
|
||||
if (line.length == 0) {
|
||||
return
|
||||
}
|
||||
line = line.replace(",", ",")
|
||||
let pieces = line.split(",")
|
||||
if (pieces.length != 2) {
|
||||
invalidLine = line
|
||||
return
|
||||
}
|
||||
let ipFrom = pieces[0].trim()
|
||||
let ipTo = pieces[1].trim()
|
||||
if (!that.validateIP(ipFrom) || !that.validateIP(ipTo)) {
|
||||
invalidLine = line
|
||||
return
|
||||
}
|
||||
validRanges.push({
|
||||
type: "ipRange",
|
||||
params: {
|
||||
ipFrom: ipFrom,
|
||||
ipTo: ipTo,
|
||||
isReverse: that.isReverse
|
||||
}
|
||||
})
|
||||
})
|
||||
if (invalidLine.length > 0) {
|
||||
teaweb.warn("'" + invalidLine + "'格式错误", function () {
|
||||
that.$refs.batchIPRange.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
validRanges.forEach(function (v) {
|
||||
that.ranges.push(v)
|
||||
})
|
||||
this.cancelBatchIPRange()
|
||||
},
|
||||
confirmBatchIPCIDR: function () {
|
||||
let that = this
|
||||
let rangesText = this.batchIPCIDR
|
||||
if (rangesText.length == 0) {
|
||||
teaweb.warn("请填写要加入的CIDR", function () {
|
||||
that.$refs.batchIPCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let validRanges = []
|
||||
let invalidLine = ""
|
||||
rangesText.split("\n").forEach(function (line) {
|
||||
let cidr = line.trim()
|
||||
if (cidr.length == 0) {
|
||||
return
|
||||
}
|
||||
if (!that.validateCIDR(cidr)) {
|
||||
invalidLine = line
|
||||
return
|
||||
}
|
||||
validRanges.push({
|
||||
type: "cidr",
|
||||
params: {
|
||||
cidr: cidr,
|
||||
isReverse: that.isReverse
|
||||
}
|
||||
})
|
||||
})
|
||||
if (invalidLine.length > 0) {
|
||||
teaweb.warn("'" + invalidLine + "'格式错误", function () {
|
||||
that.$refs.batchIPCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
validRanges.forEach(function (v) {
|
||||
that.ranges.push(v)
|
||||
})
|
||||
this.cancelBatchIPCIDR()
|
||||
},
|
||||
selectRegionCountry: function (country) {
|
||||
if (country == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "country",
|
||||
id: country.id,
|
||||
name: country.name
|
||||
})
|
||||
this.$refs.regionCountryComboBox.clear()
|
||||
},
|
||||
selectRegionProvince: function (province) {
|
||||
if (province == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "province",
|
||||
id: province.id,
|
||||
name: province.name
|
||||
})
|
||||
this.$refs.regionProvinceComboBox.clear()
|
||||
},
|
||||
selectRegionCity: function (city) {
|
||||
if (city == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "city",
|
||||
id: city.id,
|
||||
name: city.name
|
||||
})
|
||||
this.$refs.regionCityComboBox.clear()
|
||||
},
|
||||
selectRegionProvider: function (provider) {
|
||||
if (provider == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "provider",
|
||||
id: provider.id,
|
||||
name: provider.name
|
||||
})
|
||||
this.$refs.regionProviderComboBox.clear()
|
||||
},
|
||||
removeRegion: function (index) {
|
||||
this.regions.$remove(index)
|
||||
},
|
||||
validateIP: function (ip) {
|
||||
if (ip.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (ip.indexOf(":") >= 0) {
|
||||
let pieces = ip.split(":")
|
||||
if (pieces.length > 8) {
|
||||
return false
|
||||
}
|
||||
let isOk = true
|
||||
pieces.forEach(function (piece) {
|
||||
if (!/^[\da-fA-F]{0,4}$/.test(piece)) {
|
||||
isOk = false
|
||||
}
|
||||
})
|
||||
|
||||
return isOk
|
||||
}
|
||||
|
||||
if (!ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
|
||||
return false
|
||||
}
|
||||
let pieces = ip.split(".")
|
||||
let isOk = true
|
||||
pieces.forEach(function (v) {
|
||||
let v1 = parseInt(v)
|
||||
if (v1 > 255) {
|
||||
isOk = false
|
||||
}
|
||||
})
|
||||
return isOk
|
||||
},
|
||||
validateCIDR: function (cidr) {
|
||||
let pieces = cidr.split("/")
|
||||
if (pieces.length != 2) {
|
||||
return false
|
||||
}
|
||||
let ip = pieces[0]
|
||||
if (!this.validateIP(ip)) {
|
||||
return false
|
||||
}
|
||||
let mask = pieces[1]
|
||||
if (!/^\d{1,3}$/.test(mask)) {
|
||||
return false
|
||||
}
|
||||
mask = parseInt(mask, 10)
|
||||
if (cidr.indexOf(":") >= 0) { // IPv6
|
||||
return mask <= 128
|
||||
}
|
||||
return mask <= 32
|
||||
},
|
||||
updateRangeType: function (rangeType) {
|
||||
this.rangeType = rangeType
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="rangesJSON" :value="JSON.stringify(ranges)"/>
|
||||
<div v-if="ranges.length > 0">
|
||||
<div class="ui label tiny basic" v-for="(range, index) in ranges" style="margin-bottom: 0.3em">
|
||||
<span class="red" v-if="range.params.isReverse">[排除]</span>
|
||||
<span v-if="range.type == 'ipRange'">IP范围:</span>
|
||||
<span v-if="range.type == 'cidr'">CIDR:</span>
|
||||
<span v-if="range.type == 'region'"></span>
|
||||
<span v-if="range.type == 'ipRange'">{{range.params.ipFrom}} - {{range.params.ipTo}}</span>
|
||||
<span v-if="range.type == 'cidr'">{{range.params.cidr}}</span>
|
||||
<span v-if="range.type == 'region'">
|
||||
<span v-for="(region, index) in range.params.regions">
|
||||
<span v-if="region.type == 'country'">国家/地区</span>
|
||||
<span v-if="region.type == 'province'">省份</span>
|
||||
<span v-if="region.type == 'city'">城市</span>
|
||||
<span v-if="region.type == 'provider'">ISP</span>
|
||||
:{{region.name}}
|
||||
<span v-if="index < range.params.regions.length - 1" class="grey">
|
||||
|
||||
<span v-if="range.connector == 'OR' || range.connector == '' || range.connector == null">或</span>
|
||||
<span v-if="range.connector == 'AND'">且</span>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- IP范围 -->
|
||||
<div v-if="rangeType == 'ipRange'">
|
||||
<!-- 添加单个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">开始IP *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="开始IP" maxlength="40" size="40" style="width: 15em" v-model="ipRangeFrom" ref="ipRangeFrom" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>结束IP *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="结束IP" maxlength="40" size="40" style="width: 15em" v-model="ipRangeTo" ref="ipRangeTo" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- 添加多个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">IP范围列表 *</td>
|
||||
<td>
|
||||
<textarea rows="5" ref="batchIPRange" v-model="batchIPRange"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>开始IP,结束IP</code-label>,比如<code-label>192.168.1.100,192.168.1.200</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmBatchIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelBatchIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addIPRange">添加单个IP范围</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatchIPRange">批量添加IP范围</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CIDR -->
|
||||
<div v-if="rangeType == 'cidr'">
|
||||
<!-- 添加单个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">CIDR *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="IP/MASK" maxlength="40" size="40" style="width: 15em" v-model="ipCIDR" ref="ipCIDR" @keyup.enter="confirmIPCIDR" @keypress.enter.prevent="1"/>
|
||||
<p class="comment">类似于<code-label>192.168.2.1/24</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPCIDR">确定</button>
|
||||
<a href="" @click.prevent="cancelIPCIDR" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- 添加多个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">IP范围列表 *</td>
|
||||
<td>
|
||||
<textarea rows="5" ref="batchIPCIDR" v-model="batchIPCIDR"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>IP/MASK</code-label>,比如<code-label>192.168.2.1/24</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmBatchIPCIDR">确定</button>
|
||||
<a href="" @click.prevent="cancelBatchIPCIDR" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addCIDR">添加单个CIDR</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatchCIDR">批量添加CIDR</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 区域 -->
|
||||
<div v-if="rangeType == 'region'">
|
||||
<!-- 添加区域 -->
|
||||
<div v-if="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td>已添加</td>
|
||||
<td>
|
||||
<span v-for="(region, index) in regions">
|
||||
<span class="ui label small basic">
|
||||
<span v-if="region.type == 'country'">国家/地区</span>
|
||||
<span v-if="region.type == 'province'">省份</span>
|
||||
<span v-if="region.type == 'city'">城市</span>
|
||||
<span v-if="region.type == 'provider'">ISP</span>
|
||||
:{{region.name}} <a href="" title="删除" @click.prevent="removeRegion(index)"><i class="icon remove small"></i></a>
|
||||
</span>
|
||||
<span v-if="index < regions.length - 1" class="grey">
|
||||
|
||||
<span v-if="regionConnector == 'OR' || regionConnector == ''">或</span>
|
||||
<span v-if="regionConnector == 'AND'">且</span>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">添加新<span v-if="regionType == 'country'">国家/地区</span><span v-if="regionType == 'province'">省份</span><span v-if="regionType == 'city'">城市</span><span v-if="regionType == 'provider'">ISP</span>
|
||||
|
||||
*</td>
|
||||
<td>
|
||||
<!-- region country name -->
|
||||
<div v-if="regionType == 'country'">
|
||||
<combo-box title="" width="14em" data-url="/ui/countryOptions" data-key="countries" placeholder="点这里选择国家/地区" @change="selectRegionCountry" ref="regionCountryComboBox" key="combo-box-country"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- region province name -->
|
||||
<div v-if="regionType == 'province'" >
|
||||
<combo-box title="" data-url="/ui/provinceOptions" data-key="provinces" placeholder="点这里选择省份" @change="selectRegionProvince" ref="regionProvinceComboBox" key="combo-box-province"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- region city name -->
|
||||
<div v-if="regionType == 'city'" >
|
||||
<combo-box title="" data-url="/ui/cityOptions" data-key="cities" placeholder="点这里选择城市" @change="selectRegionCity" ref="regionCityComboBox" key="combo-box-city"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- ISP Name -->
|
||||
<div v-if="regionType == 'provider'" >
|
||||
<combo-box title="" data-url="/ui/providerOptions" data-key="providers" placeholder="点这里选择ISP" @change="selectRegionProvider" ref="regionProviderComboBox" key="combo-box-isp"></combo-box>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1em">
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'country'}" type="button" @click.prevent="addRegion('country')">添加国家/地区</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'province'}" type="button" @click.prevent="addRegion('province')">添加省份</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'city'}" type="button" @click.prevent="addRegion('city')">添加城市</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'provider'}" type="button" @click.prevent="addRegion('provider')">ISP</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>区域之间关系</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="regionConnector">
|
||||
<option value="OR">或</option>
|
||||
<option value="AND">且</option>
|
||||
</select>
|
||||
<p class="comment" v-if="regionConnector == 'OR'">匹配所选任一区域即认为匹配成功。</p>
|
||||
<p class="comment" v-if="regionConnector == 'AND'">匹配所有所选区域才认为匹配成功。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmRegions">确定</button>
|
||||
<a href="" @click.prevent="cancelRegions" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addRegions">添加区域</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
30
EdgeAdmin/web/public/js/components/ns/ns-route-selector.js
Normal file
30
EdgeAdmin/web/public/js/components/ns/ns-route-selector.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// 选择单一线路
|
||||
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: `<div>
|
||||
<div v-if="routes.length > 0">
|
||||
<select class="ui dropdown" name="routeCode" v-model="routeCode">
|
||||
<option value="">[线路]</option>
|
||||
<option v-for="route in routes" :value="route.code">{{route.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
165
EdgeAdmin/web/public/js/components/ns/ns-routes-selector.js
Normal file
165
EdgeAdmin/web/public/js/components/ns/ns-routes-selector.js
Normal file
@@ -0,0 +1,165 @@
|
||||
// 选择多个线路
|
||||
Vue.component("ns-routes-selector", {
|
||||
props: ["v-routes", "name"],
|
||||
mounted: function () {
|
||||
let that = this
|
||||
Tea.action("/ns/routes/options")
|
||||
.post()
|
||||
.success(function (resp) {
|
||||
that.routes = resp.data.routes
|
||||
|
||||
// provinces
|
||||
let provinces = {}
|
||||
if (resp.data.provinces != null && resp.data.provinces.length > 0) {
|
||||
for (const province of resp.data.provinces) {
|
||||
let countryCode = province.countryCode
|
||||
if (typeof provinces[countryCode] == "undefined") {
|
||||
provinces[countryCode] = []
|
||||
}
|
||||
provinces[countryCode].push({
|
||||
name: province.name,
|
||||
code: province.code
|
||||
})
|
||||
}
|
||||
}
|
||||
that.provinces = provinces
|
||||
})
|
||||
},
|
||||
data: function () {
|
||||
let selectedRoutes = this.vRoutes
|
||||
if (selectedRoutes == null) {
|
||||
selectedRoutes = []
|
||||
}
|
||||
|
||||
let inputName = this.name
|
||||
if (typeof inputName != "string" || inputName.length == 0) {
|
||||
inputName = "routeCodes"
|
||||
}
|
||||
|
||||
return {
|
||||
routeCode: "default",
|
||||
inputName: inputName,
|
||||
routes: [],
|
||||
|
||||
provinces: {}, // country code => [ province1, province2, ... ]
|
||||
provinceRouteCode: "",
|
||||
|
||||
isAdding: false,
|
||||
routeType: "default",
|
||||
selectedRoutes: selectedRoutes,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
routeType: function (v) {
|
||||
this.routeCode = ""
|
||||
let that = this
|
||||
this.routes.forEach(function (route) {
|
||||
if (route.type == v && that.routeCode.length == 0) {
|
||||
that.routeCode = route.code
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add: function () {
|
||||
this.isAdding = true
|
||||
this.routeType = "default"
|
||||
this.routeCode = "default"
|
||||
this.provinceRouteCode = ""
|
||||
this.$emit("add")
|
||||
},
|
||||
cancel: function () {
|
||||
this.isAdding = false
|
||||
this.$emit("cancel")
|
||||
},
|
||||
confirm: function () {
|
||||
if (this.routeCode.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let that = this
|
||||
|
||||
// route
|
||||
let selectedRoute = null
|
||||
for (const route of this.routes) {
|
||||
if (route.code == this.routeCode) {
|
||||
selectedRoute = route
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedRoute != null) {
|
||||
// province route
|
||||
if (this.provinceRouteCode.length > 0 && this.provinces[this.routeCode] != null) {
|
||||
for (const province of this.provinces[this.routeCode]) {
|
||||
if (province.code == this.provinceRouteCode) {
|
||||
selectedRoute = {
|
||||
name: selectedRoute.name + "-" + province.name,
|
||||
code: province.code
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
that.selectedRoutes.push(selectedRoute)
|
||||
}
|
||||
|
||||
this.$emit("change", this.selectedRoutes)
|
||||
this.cancel()
|
||||
},
|
||||
remove: function (index) {
|
||||
this.selectedRoutes.$remove(index)
|
||||
this.$emit("change", this.selectedRoutes)
|
||||
}
|
||||
}
|
||||
,
|
||||
template: `<div>
|
||||
<div v-show="selectedRoutes.length > 0">
|
||||
<div class="ui label basic text small" v-for="(route, index) in selectedRoutes" style="margin-bottom: 0.3em">
|
||||
<input type="hidden" :name="inputName" :value="route.code"/>
|
||||
{{route.name}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div v-if="isAdding" style="margin-bottom: 1em">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">选择类型 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="routeType">
|
||||
<option value="default">[默认线路]</option>
|
||||
<option value="user">自定义线路</option>
|
||||
<option value="isp">运营商</option>
|
||||
<option value="china">中国省市</option>
|
||||
<option value="world">全球国家地区</option>
|
||||
<option value="agent">搜索引擎</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>选择线路 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="routeCode">
|
||||
<option v-for="route in routes" :value="route.code" v-if="route.type == routeType">{{route.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="routeCode.length > 0 && provinces[routeCode] != null">
|
||||
<td>选择省/州</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="provinceRouteCode">
|
||||
<option value="">[全域]</option>
|
||||
<option v-for="province in provinces[routeCode]" :value="province.code">{{province.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div>
|
||||
<button type="button" class="ui button tiny" @click.prevent="confirm">确定</button>
|
||||
<a href="" title="取消" @click.prevent="cancel">取消</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui button tiny" type="button" @click.prevent="add" v-if="!isAdding">+</button>
|
||||
</div>`
|
||||
})
|
||||
14
EdgeAdmin/web/public/js/components/ns/ns-user-selector.js
Normal file
14
EdgeAdmin/web/public/js/components/ns/ns-user-selector.js
Normal file
@@ -0,0 +1,14 @@
|
||||
Vue.component("ns-user-selector", {
|
||||
props: ["v-user-id"],
|
||||
data: function () {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
change: function (userId) {
|
||||
this.$emit("change", userId)
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<user-selector :v-user-id="vUserId" data-url="/ns/users/options" @change="change"></user-selector>
|
||||
</div>`
|
||||
})
|
||||
Reference in New Issue
Block a user