Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
Vue.component("node-cache-disk-dirs-box", {
props: ["value", "name"],
data: function () {
let dirs = this.value
if (dirs == null) {
dirs = []
}
return {
dirs: dirs,
isEditing: false,
isAdding: false,
addingPath: ""
}
},
methods: {
add: function () {
this.isAdding = true
let that = this
setTimeout(function () {
that.$refs.addingPath.focus()
}, 100)
},
confirm: function () {
let addingPath = this.addingPath.trim()
if (addingPath.length == 0) {
let that = this
teaweb.warn("请输入要添加的缓存目录", function () {
that.$refs.addingPath.focus()
})
return
}
if (addingPath[0] != "/") {
addingPath = "/" + addingPath
}
this.dirs.push({
path: addingPath
})
this.cancel()
},
cancel: function () {
this.addingPath = ""
this.isAdding = false
this.isEditing = false
},
remove: function (index) {
let that = this
teaweb.confirm("确定要删除此目录吗?", function () {
that.dirs.$remove(index)
})
}
},
template: `<div>
<input type="hidden" :name="name" :value="JSON.stringify(dirs)"/>
<div style="margin-bottom: 0.3em">
<span class="ui label small basic" v-for="(dir, index) in dirs">
<i class="icon folder"></i>{{dir.path}} &nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</span>
</div>
<!-- 添加 -->
<div v-if="isAdding">
<div class="ui fields inline">
<div class="ui field">
<input type="text" style="width: 30em" v-model="addingPath" @keyup.enter="confirm()" @keypress.enter.prevent="1" @keydown.esc="cancel()" ref="addingPath" placeholder="新的缓存目录,比如 /mnt/cache"/>
</div>
<div class="ui field">
<button class="ui button small" type="button" @click.prevent="confirm">确定</button>
&nbsp; <a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
</div>
</div>
</div>
<div v-if="!isAdding">
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
</div>
</div>`
})

View File

@@ -0,0 +1,20 @@
Vue.component("node-combo-box", {
props: ["v-cluster-id", "v-node-id"],
data: function () {
let that = this
Tea.action("/clusters/nodeOptions")
.params({
clusterId: this.vClusterId
})
.post()
.success(function (resp) {
that.nodes = resp.data.nodes
})
return {
nodes: []
}
},
template: `<div v-if="nodes.length > 0">
<combo-box title="节点" placeholder="节点名称" :v-items="nodes" name="nodeId" :v-value="vNodeId"></combo-box>
</div>`
})

View File

@@ -0,0 +1,38 @@
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: `<div>
<div class="ui label small basic" v-if="selectedGroup != null">
<input type="hidden" name="groupId" :value="selectedGroup.id"/>
{{selectedGroup.name}} &nbsp;<a href="" title="删除" @click.prevent="removeGroup()"><i class="icon remove"></i></a>
</div>
<div v-if="selectedGroup == null">
<a href="" @click.prevent="selectGroup()">[选择分组]</a> &nbsp; <a href="" @click.prevent="addGroup()">[添加分组]</a>
</div>
</div>`
})

View File

@@ -0,0 +1,53 @@
Vue.component("node-ip-address-clusters-selector", {
props: ["vClusters"],
mounted: function () {
this.checkClusters()
},
data: function () {
let clusters = this.vClusters
if (clusters == null) {
clusters = []
}
return {
clusters: clusters,
hasCheckedCluster: false,
clustersVisible: false
}
},
methods: {
checkClusters: function () {
let that = this
let b = false
this.clusters.forEach(function (cluster) {
if (cluster.isChecked) {
b = true
}
})
this.hasCheckedCluster = b
return b
},
changeCluster: function (cluster) {
cluster.isChecked = !cluster.isChecked
this.checkClusters()
},
showClusters: function () {
this.clustersVisible = !this.clustersVisible
}
},
template: `<div>
<span v-if="!hasCheckedCluster">默认用于所有集群 &nbsp; <a href="" @click.prevent="showClusters">修改 <i class="icon angle" :class="{down: !clustersVisible, up:clustersVisible}"></i></a></span>
<div v-if="hasCheckedCluster">
<span v-for="cluster in clusters" class="ui label basic small" v-if="cluster.isChecked">{{cluster.name}}</span> &nbsp; <a href="" @click.prevent="showClusters">修改 <i class="icon angle" :class="{down: !clustersVisible, up:clustersVisible}"></i></a>
<p class="comment">当前IP仅在所选集群中有效。</p>
</div>
<div v-show="clustersVisible">
<div class="ui divider"></div>
<checkbox v-for="cluster in clusters" :v-value="cluster.id" :value="cluster.isChecked ? cluster.id : 0" style="margin-right: 1em" @input="changeCluster(cluster)" name="clusterIds">
{{cluster.name}}
</checkbox>
</div>
</div>`
})

View File

@@ -0,0 +1,563 @@
// 节点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: `<div>
<input type="hidden" name="thresholdsJSON" :value="JSON.stringify(thresholds)"/>
<!-- 已有条件 -->
<div v-if="thresholds.length > 0">
<div class="ui label basic small" v-for="(threshold, index) in thresholds">
<span v-for="(item, itemIndex) in threshold.items">
<span v-if="item.item != 'nodeHealthCheck'">
[{{item.duration}}{{itemDurationUnitName(item.durationUnit)}}]
</span>
{{itemName(item.item)}}
<span v-if="item.item == 'nodeHealthCheck'">
<!-- 健康检查 -->
<span v-if="item.value == 1">成功</span>
<span v-if="item.value == 0">失败</span>
</span>
<span v-else>
<!-- 连通性 -->
<span v-if="item.item == 'connectivity' && item.options != null && item.options.groups != null && item.options.groups.length > 0">[<span v-for="(group, groupIndex) in item.options.groups">{{group.name}} <span v-if="groupIndex != item.options.groups.length - 1">&nbsp; </span></span>]</span>
<span class="grey">[{{itemOperatorName(item.operator)}}]</span> &nbsp;{{item.value}}{{itemUnitName(item.item)}}
</span>
&nbsp;<span v-if="itemIndex != threshold.items.length - 1" style="font-style: italic">AND &nbsp;</span>
</span>
-&gt;
<span v-for="(action, actionIndex) in threshold.actions">{{actionName(action.action)}}
<span v-if="action.action == 'switch'">到{{action.options.ips.join(", ")}}</span>
<span v-if="action.action == 'webHook'" class="small grey">({{action.options.url}})</span>
&nbsp;<span v-if="actionIndex != threshold.actions.length - 1" style="font-style: italic">AND &nbsp;</span></span>
&nbsp;
<a href="" title="修改" @click.prevent="update(index)"><i class="icon pencil small"></i></a>
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon small remove"></i></a>
</div>
</div>
<!-- 新阈值 -->
<div v-if="isAdding" style="margin-top: 0.5em">
<table class="ui table celled">
<thead>
<tr>
<td style="width: 50%; background: #f9fafb; border-bottom: 1px solid rgba(34,36,38,.1)">阈值</td>
<th>动作</th>
</tr>
</thead>
<tr>
<td style="background: white">
<!-- 已经添加的项目 -->
<div>
<div v-for="(item, index) in addingThreshold.items" class="ui label basic small" style="margin-bottom: 0.5em;">
<span v-if="item.item != 'nodeHealthCheck'">
[{{item.duration}}{{itemDurationUnitName(item.durationUnit)}}]
</span>
{{itemName(item.item)}}
<span v-if="item.item == 'nodeHealthCheck'">
<!-- 健康检查 -->
<span v-if="item.value == 1">成功</span>
<span v-if="item.value == 0">失败</span>
</span>
<span v-else>
<!-- 连通性 -->
<span v-if="item.item == 'connectivity' && item.options != null && item.options.groups != null && item.options.groups.length > 0">[<span v-for="(group, groupIndex) in item.options.groups">{{group.name}} <span v-if="groupIndex != item.options.groups.length - 1">&nbsp; </span></span>]</span>
<span class="grey">[{{itemOperatorName(item.operator)}}]</span> {{item.value}}{{itemUnitName(item.item)}}
</span>
&nbsp;
<a href="" title="删除" @click.prevent="removeItem(index)"><i class="icon remove small"></i></a>
</div>
</div>
<!-- 正在添加的项目 -->
<div v-if="isAddingItem" style="margin-top: 0.8em">
<table class="ui table">
<tr>
<td style="width: 6em">统计项目</td>
<td>
<select class="ui dropdown auto-width" v-model="itemCode">
<option v-for="item in allItems" :value="item.code">{{item.name}}</option>
</select>
<p class="comment" style="font-weight: normal" v-for="item in allItems" v-if="item.code == itemCode">{{item.description}}</p>
</td>
</tr>
<tr v-show="itemCode != 'nodeHealthCheck'">
<td>统计周期</td>
<td>
<div class="ui input right labeled">
<input type="text" v-model="itemDuration" style="width: 4em" maxlength="4" ref="itemDuration" @keyup.enter="confirmItem()" @keypress.enter.prevent="1"/>
<span class="ui label">分钟</span>
</div>
</td>
</tr>
<tr v-show="itemCode != 'nodeHealthCheck'">
<td>操作符</td>
<td>
<select class="ui dropdown auto-width" v-model="itemOperator">
<option v-for="operator in allOperators" :value="operator.code">{{operator.name}}</option>
</select>
</td>
</tr>
<tr v-show="itemCode != 'nodeHealthCheck'">
<td>对比值</td>
<td>
<div class="ui input right labeled">
<input type="text" maxlength="20" style="width: 5em" v-model="itemValue" ref="itemValue" @keyup.enter="confirmItem()" @keypress.enter.prevent="1"/>
<span class="ui label" v-for="item in allItems" v-if="item.code == itemCode">{{item.unit}}</span>
</div>
</td>
</tr>
<tr v-show="itemCode == 'nodeHealthCheck'">
<td>检查结果</td>
<td>
<select class="ui dropdown auto-width" v-model="itemValue">
<option value="1">成功</option>
<option value="0">失败</option>
</select>
<p class="comment" style="font-weight: normal">只有状态发生改变的时候才会触发。</p>
</td>
</tr>
<!-- 连通性 -->
<tr v-if="itemCode == 'connectivity'">
<td>终端分组</td>
<td style="font-weight: normal">
<div style="zoom: 0.8"><report-node-groups-selector @change="changeReportGroups"></report-node-groups-selector></div>
</td>
</tr>
</table>
<div style="margin-top: 0.8em">
<button class="ui button tiny" type="button" @click.prevent="confirmItem">确定</button> &nbsp;
<a href="" title="取消" @click.prevent="cancelItem"><i class="icon remove small"></i></a>
</div>
</div>
<div style="margin-top: 0.8em" v-if="!isAddingItem">
<button class="ui button tiny" type="button" @click.prevent="addItem">+</button>
</div>
</td>
<td style="background: white">
<!-- 已经添加的动作 -->
<div>
<div v-for="(action, index) in addingThreshold.actions" class="ui label basic small" style="margin-bottom: 0.5em">
{{actionName(action.action)}} &nbsp;
<span v-if="action.action == 'switch'">到{{action.options.ips.join(", ")}}</span>
<span v-if="action.action == 'webHook'" class="small grey">({{action.options.url}})</span>
<a href="" title="删除" @click.prevent="removeAction(index)"><i class="icon remove small"></i></a>
</div>
</div>
<!-- 正在添加的动作 -->
<div v-if="isAddingAction" style="margin-top: 0.8em">
<table class="ui table">
<tr>
<td style="width: 6em">动作类型</td>
<td>
<select class="ui dropdown auto-width" v-model="actionCode">
<option v-for="action in allActions" :value="action.code">{{action.name}}</option>
</select>
<p class="comment" v-for="action in allActions" v-if="action.code == actionCode">{{action.description}}</p>
</td>
</tr>
<!-- 切换 -->
<tr v-if="actionCode == 'switch'">
<td>备用IP *</td>
<td>
<textarea rows="2" v-model="actionBackupIPs" ref="actionBackupIPs"></textarea>
<p class="comment">每行一个备用IP。</p>
</td>
</tr>
<!-- WebHook -->
<tr v-if="actionCode == 'webHook'">
<td>URL *</td>
<td>
<input type="text" maxlength="1000" placeholder="https://..." v-model="actionWebHookURL" ref="webHookURL" @keyup.enter="confirmAction()" @keypress.enter.prevent="1"/>
<p class="comment">完整的URL比如<code-label>https://example.com/webhook/api</code-label>系统会在触发阈值的时候通过GET调用此URL。</p>
</td>
</tr>
</table>
<div style="margin-top: 0.8em">
<button class="ui button tiny" type="button" @click.prevent="confirmAction">确定</button> &nbsp;
<a href="" title="取消" @click.prevent="cancelAction"><i class="icon remove small"></i></a>
</div>
</div>
<div style="margin-top: 0.8em" v-if="!isAddingAction">
<button class="ui button tiny" type="button" @click.prevent="addAction">+</button>
</div>
</td>
</tr>
</table>
<!-- 添加阈值 -->
<div>
<button class="ui button tiny" :class="{disabled: (isAddingItem || isAddingAction)}" type="button" @click.prevent="confirm">确定</button> &nbsp;
<a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
</div>
</div>
<div v-if="!isAdding" style="margin-top: 0.5em">
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
</div>
</div>`
})

View File

@@ -0,0 +1,132 @@
// 节点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: `<div>
<!-- 已有条件 -->
<div v-if="thresholds.length > 0">
<div class="ui label basic small" v-for="(threshold, index) in thresholds" style="margin-bottom: 0.8em">
<span v-for="(item, itemIndex) in threshold.items">
<span>
<span v-if="item.item != 'nodeHealthCheck'">
[{{item.duration}}{{itemDurationUnitName(item.durationUnit)}}]
</span>
{{itemName(item.item)}}
<span v-if="item.item == 'nodeHealthCheck'">
<!-- 健康检查 -->
<span v-if="item.value == 1">成功</span>
<span v-if="item.value == 0">失败</span>
</span>
<span v-else>
<!-- 连通性 -->
<span v-if="item.item == 'connectivity' && item.options != null && item.options.groups != null && item.options.groups.length > 0">[<span v-for="(group, groupIndex) in item.options.groups">{{group.name}} <span v-if="groupIndex != item.options.groups.length - 1">&nbsp; </span></span>]</span>
<span class="grey">[{{itemOperatorName(item.operator)}}]</span> {{item.value}}{{itemUnitName(item.item)}} &nbsp;
</span>
</span>
<span v-if="itemIndex != threshold.items.length - 1" style="font-style: italic">AND &nbsp;</span></span>
-&gt;
<span v-for="(action, actionIndex) in threshold.actions">{{actionName(action.action)}}
<span v-if="action.action == 'switch'">到{{action.options.ips.join(", ")}}</span>
<span v-if="action.action == 'webHook'" class="small grey">({{action.options.url}})</span>
&nbsp;
<span v-if="actionIndex != threshold.actions.length - 1" style="font-style: italic">AND &nbsp;</span>
</span>
</div>
</div>
</div>`
})

View File

@@ -0,0 +1,82 @@
// 节点IP地址管理标签形式
Vue.component("node-ip-addresses-box", {
props: ["v-ip-addresses", "role", "v-node-id"],
data: function () {
let nodeId = this.vNodeId
if (nodeId == null) {
nodeId = 0
}
return {
ipAddresses: (this.vIpAddresses == null) ? [] : this.vIpAddresses,
supportThresholds: this.role != "ns",
nodeId: nodeId
}
},
methods: {
// 添加IP地址
addIPAddress: function () {
window.UPDATING_NODE_IP_ADDRESS = null
let that = this;
teaweb.popup("/nodes/ipAddresses/createPopup?nodeId=" + this.nodeId + "&supportThresholds=" + (this.supportThresholds ? 1 : 0), {
callback: function (resp) {
that.ipAddresses.push(resp.data.ipAddress);
},
height: "24em",
width: "44em"
})
},
// 修改地址
updateIPAddress: function (index, address) {
window.UPDATING_NODE_IP_ADDRESS = teaweb.clone(address)
let that = this;
teaweb.popup("/nodes/ipAddresses/updatePopup?nodeId=" + this.nodeId + "&supportThresholds=" + (this.supportThresholds ? 1 : 0), {
callback: function (resp) {
Vue.set(that.ipAddresses, index, resp.data.ipAddress);
},
height: "24em",
width: "44em"
})
},
// 删除IP地址
removeIPAddress: function (index) {
this.ipAddresses.$remove(index);
},
// 判断是否为IPv6
isIPv6: function (ip) {
return ip.indexOf(":") > -1
}
},
template: `<div>
<input type="hidden" name="ipAddressesJSON" :value="JSON.stringify(ipAddresses)"/>
<div v-if="ipAddresses.length > 0">
<div>
<div v-for="(address, index) in ipAddresses" class="ui label tiny basic">
<span v-if="isIPv6(address.ip)" class="grey">[IPv6]</span> {{address.ip}}
<span class="small grey" v-if="address.name.length > 0">(备注:{{address.name}}<span v-if="!address.canAccess">,不公开访问</span></span>
<span class="small grey" v-if="address.name.length == 0 && !address.canAccess">(不公开访问)</span>
<span class="small red" v-if="!address.isOn" title="未启用">[off]</span>
<span class="small red" v-if="!address.isUp" title="已下线">[down]</span>
<span class="small" v-if="address.thresholds != null && address.thresholds.length > 0">[{{address.thresholds.length}}个阈值]</span>
&nbsp;
<span v-if="address.clusters != null && address.clusters.length > 0">
&nbsp; <span class="small grey">专属集群:[</span><span v-for="(cluster, index) in address.clusters" class="small grey">{{cluster.name}}<span v-if="index < address.clusters.length - 1"></span></span><span class="small grey">]</span>
&nbsp;
</span>
<a href="" title="修改" @click.prevent="updateIPAddress(index, address)"><i class="icon pencil small"></i></a>
<a href="" title="删除" @click.prevent="removeIPAddress(index)"><i class="icon remove"></i></a>
</div>
</div>
<div class="ui divider"></div>
</div>
<div>
<button class="ui button small" type="button" @click.prevent="addIPAddress()">+</button>
</div>
</div>`
})

View File

@@ -0,0 +1,37 @@
// 节点级别选择器
Vue.component("node-level-selector", {
props: ["v-node-level"],
data: function () {
let levelCode = this.vNodeLevel
if (levelCode == null || levelCode < 1) {
levelCode = 1
}
return {
levels: [
{
name: "边缘节点",
code: 1,
description: "普通的边缘节点。"
},
{
name: "L2节点",
code: 2,
description: "特殊的边缘节点,同时负责同组上一级节点的回源。"
}
],
levelCode: levelCode
}
},
watch: {
levelCode: function (code) {
this.$emit("change", code)
}
},
template: `<div>
<select class="ui dropdown auto-width" name="level" v-model="levelCode">
<option v-for="level in levels" :value="level.code">{{level.name}}</option>
</select>
<p class="comment" v-if="typeof(levels[levelCode - 1]) != null"><plus-label
></plus-label>{{levels[levelCode - 1].description}}</p>
</div>`
})

View File

@@ -0,0 +1,60 @@
// 节点登录推荐端口
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: `<span>
<span v-if="isLoading">正在检查端口...</span>
<span v-if="availablePorts.length > 0">
可能端口:<a href="" v-for="port in availablePorts" @click.prevent="selectPort(port)" class="ui label tiny basic blue" style="border: 1px #2185d0 dashed; font-weight: normal">{{port}}</a>
&nbsp; &nbsp;
</span>
<span v-if="ports.length > 0">
常用端口:<a href="" v-for="port in ports" @click.prevent="selectPort(port)" class="ui label tiny basic blue" style="border: 1px #2185d0 dashed; font-weight: normal">{{port}}</a>
</span>
<span v-if="ports.length == 0">常用端口有22等。</span>
<span v-if="ports.length > 0" class="grey small">(可以点击要使用的端口)</span>
</span>`
})

View File

@@ -0,0 +1,38 @@
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: `<div>
<div class="ui label small basic" v-if="selectedRegion != null">
<input type="hidden" name="regionId" :value="selectedRegion.id"/>
{{selectedRegion.name}} &nbsp;<a href="" title="删除" @click.prevent="removeRegion()"><i class="icon remove"></i></a>
</div>
<div v-if="selectedRegion == null">
<a href="" @click.prevent="selectRegion()">[选择区域]</a> &nbsp; <a href="" @click.prevent="addRegion()">[添加区域]</a>
</div>
</div>`
})

View File

@@ -0,0 +1,47 @@
Vue.component("node-schedule-action-box", {
props: ["value", "v-actions"],
data: function () {
let actionConfig = this.value
if (actionConfig == null) {
actionConfig = {
code: "",
params: {}
}
}
return {
actions: this.vActions,
currentAction: null,
actionConfig: actionConfig
}
},
watch: {
"actionConfig.code": function (actionCode) {
if (actionCode.length == 0) {
this.currentAction = null
} else {
this.currentAction = this.actions.$find(function (k, v) {
return v.code == actionCode
})
}
this.actionConfig.params = {}
}
},
template: `<div>
<input type="hidden" name="actionJSON" :value="JSON.stringify(actionConfig)"/>
<div>
<div>
<select class="ui dropdown auto-width" v-model="actionConfig.code">
<option value="">[选择动作]</option>
<option v-for="action in actions" :value="action.code">{{action.name}}</option>
</select>
</div>
<p class="comment" v-if="currentAction != null">{{currentAction.description}}</p>
<div v-if="actionConfig.code == 'webHook'">
<input type="text" placeholder="https://..." v-model="actionConfig.params.url"/>
<p class="comment">接收通知的URL。</p>
</div>
</div>
</div>`
})

View File

@@ -0,0 +1,326 @@
Vue.component("node-schedule-conds-box", {
props: ["value", "v-params", "v-operators"],
mounted: function () {
this.formatConds(this.condsConfig.conds)
this.$forceUpdate()
},
data: function () {
let condsConfig = this.value
if (condsConfig == null) {
condsConfig = {
conds: [],
connector: "and"
}
}
if (condsConfig.conds == null) {
condsConfig.conds = []
}
let paramMap = {}
this.vParams.forEach(function (param) {
paramMap[param.code] = param
})
let operatorMap = {}
this.vOperators.forEach(function (operator) {
operatorMap[operator.code] = operator.name
})
return {
condsConfig: condsConfig,
params: this.vParams,
paramMap: paramMap,
operatorMap: operatorMap,
operator: "",
isAdding: false,
paramCode: "",
param: null,
valueBandwidth: {
count: 100,
unit: "mb"
},
valueTraffic: {
count: 1,
unit: "gb"
},
valueCPU: 80,
valueMemory: 90,
valueLoad: 20,
valueRate: 0
}
},
watch: {
paramCode: function (code) {
if (code.length == 0) {
this.param = null
} else {
this.param = this.params.$find(function (k, v) {
return v.code == code
})
}
this.$emit("changeparam", this.param)
}
},
methods: {
add: function () {
this.isAdding = true
},
confirm: function () {
if (this.param == null) {
teaweb.warn("请选择参数")
return
}
if (this.param.operators != null && this.param.operators.length > 0 && this.operator.length == 0) {
teaweb.warn("请选择操作符")
return
}
if (this.param.operators == null || this.param.operators.length == 0) {
this.operator = ""
}
let value = null
switch (this.param.valueType) {
case "bandwidth": {
if (this.valueBandwidth.unit.length == 0) {
teaweb.warn("请选择带宽单位")
return
}
let count = parseInt(this.valueBandwidth.count.toString())
if (isNaN(count)) {
count = 0
}
if (count < 0) {
count = 0
}
value = {
count: count,
unit: this.valueBandwidth.unit
}
}
break
case "traffic": {
if (this.valueTraffic.unit.length == 0) {
teaweb.warn("请选择带宽单位")
return
}
let count = parseInt(this.valueTraffic.count.toString())
if (isNaN(count)) {
count = 0
}
if (count < 0) {
count = 0
}
value = {
count: count,
unit: this.valueTraffic.unit
}
}
break
case "cpu":
let cpu = parseInt(this.valueCPU.toString())
if (isNaN(cpu)) {
cpu = 0
}
if (cpu < 0) {
cpu = 0
}
if (cpu > 100) {
cpu = 100
}
value = cpu
break
case "memory":
let memory = parseInt(this.valueMemory.toString())
if (isNaN(memory)) {
memory = 0
}
if (memory < 0) {
memory = 0
}
if (memory > 100) {
memory = 100
}
value = memory
break
case "load":
let load = parseInt(this.valueLoad.toString())
if (isNaN(load)) {
load = 0
}
if (load < 0) {
load = 0
}
value = load
break
case "rate":
let rate = parseInt(this.valueRate.toString())
if (isNaN(rate)) {
rate = 0
}
if (rate < 0) {
rate = 0
}
value = rate
break
}
this.condsConfig.conds.push({
param: this.param.code,
operator: this.operator,
value: value
})
this.formatConds(this.condsConfig.conds)
this.cancel()
},
cancel: function () {
this.isAdding = false
this.paramCode = ""
this.param = null
},
remove: function (index) {
this.condsConfig.conds.$remove(index)
},
formatConds: function (conds) {
let that = this
conds.forEach(function (cond) {
switch (that.paramMap[cond.param].valueType) {
case "bandwidth":
cond.valueFormat = cond.value.count + cond.value.unit[0].toUpperCase() + cond.value.unit.substring(1) + "ps"
return
case "traffic":
cond.valueFormat = cond.value.count + cond.value.unit.toUpperCase()
return
case "cpu":
cond.valueFormat = cond.value + "%"
return
case "memory":
cond.valueFormat = cond.value + "%"
return
case "load":
cond.valueFormat = cond.value
return
case "rate":
cond.valueFormat = cond.value + "/秒"
return
}
})
}
},
template: `<div>
<input type="hidden" name="condsJSON" :value="JSON.stringify(this.condsConfig)"/>
<!-- 已有条件 -->
<div v-if="condsConfig.conds.length > 0" style="margin-bottom: 1em">
<span v-for="(cond, index) in condsConfig.conds">
<span class="ui label basic small">
<span>{{paramMap[cond.param].name}}
<span v-if="paramMap[cond.param].operators != null && paramMap[cond.param].operators.length > 0"><span class="grey">{{operatorMap[cond.operator]}}</span> {{cond.valueFormat}}</span>
&nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</span>
</span>
<span v-if="index < condsConfig.conds.length - 1"> &nbsp;<span v-if="condsConfig.connector == 'and'">且</span><span v-else>或</span>&nbsp; </span>
</span>
</div>
<div v-if="isAdding">
<table class="ui table">
<tbody>
<tr>
<td class="title">参数</td>
<td>
<select class="ui dropdown auto-width" v-model="paramCode">
<option value="">[选择参数]</option>
<option v-for="paramOption in params" :value="paramOption.code">{{paramOption.name}}</option>
</select>
<p class="comment" v-if="param != null">{{param.description}}</p>
</td>
</tr>
<tr v-if="param != null && param.operators != null && param.operators.length > 0">
<td>操作符</td>
<td>
<select class="ui dropdown auto-width" v-if="param != null" v-model="operator">
<option value="">[选择操作符]</option>
<option v-for="operator in param.operators" :value="operator">{{operatorMap[operator]}}</option>
</select>
</td>
</tr>
<tr v-if="param != null && param.operators != null && param.operators.length > 0">
<td>{{param.valueName}}</td>
<td>
<!-- 带宽 -->
<div v-if="param.valueType == 'bandwidth'">
<div class="ui fields inline">
<div class="ui field">
<input type="text" maxlength="10" size="6" v-model="valueBandwidth.count" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
</div>
<div class="ui field">
<select class="ui dropdown auto-width" v-model="valueBandwidth.unit">
<option value="gb">Gbps</option>
<option value="mb">Mbps</option>
</select>
</div>
</div>
</div>
<!-- 流量 -->
<div v-if="param.valueType == 'traffic'">
<div class="ui fields inline">
<div class="ui field">
<input type="text" maxlength="10" size="6" v-model="valueTraffic.count" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
</div>
<div class="ui field">
<select class="ui dropdown auto-width" v-model="valueTraffic.unit">
<option value="mb">MiB</option>
<option value="gb">GiB</option>
<option value="tb">TiB</option>
<option value="pb">PiB</option>
<option value="eb">EiB</option>
</select>
</div>
</div>
</div>
<!-- cpu -->
<div v-if="param.valueType == 'cpu'">
<div class="ui input right labeled">
<input type="text" v-model="valueCPU" maxlength="3" size="3" style="width: 4em" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
<span class="ui label">%</span>
</div>
</div>
<!-- memory -->
<div v-if="param.valueType == 'memory'">
<div class="ui input right labeled">
<input type="text" v-model="valueMemory" maxlength="3" size="3" style="width: 4em" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
<span class="ui label">%</span>
</div>
</div>
<!-- load -->
<div v-if="param.valueType == 'load'">
<input type="text" v-model="valueLoad" maxlength="3" size="3" style="width: 4em" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
</div>
<!-- rate -->
<div v-if="param.valueType == 'rate'">
<div class="ui input right labeled">
<input type="text" v-model="valueRate" maxlength="8" size="8" style="width: 8em" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
<span class="ui label">/秒</span>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<button class="ui button small" type="button" @click.prevent="confirm">确定</button> &nbsp; <a href="" @click.prevent="cancel">取消</a>
</div>
<div v-if="!isAdding">
<button class="ui button small" type="button" @click.prevent="add">+</button>
</div>
</div>`
})

View File

@@ -0,0 +1,61 @@
Vue.component("node-schedule-conds-viewer", {
props: ["value", "v-params", "v-operators"],
mounted: function () {
this.formatConds(this.condsConfig.conds)
this.$forceUpdate()
},
data: function () {
let paramMap = {}
this.vParams.forEach(function (param) {
paramMap[param.code] = param
})
let operatorMap = {}
this.vOperators.forEach(function (operator) {
operatorMap[operator.code] = operator.name
})
return {
condsConfig: this.value,
paramMap: paramMap,
operatorMap: operatorMap
}
},
methods: {
formatConds: function (conds) {
let that = this
conds.forEach(function (cond) {
switch (that.paramMap[cond.param].valueType) {
case "bandwidth":
cond.valueFormat = cond.value.count + cond.value.unit[0].toUpperCase() + cond.value.unit.substring(1) + "ps"
return
case "traffic":
cond.valueFormat = cond.value.count + cond.value.unit.toUpperCase()
return
case "cpu":
cond.valueFormat = cond.value + "%"
return
case "memory":
cond.valueFormat = cond.value + "%"
return
case "load":
cond.valueFormat = cond.value
return
case "rate":
cond.valueFormat = cond.value + "/秒"
return
}
})
}
},
template: `<div>
<span v-for="(cond, index) in condsConfig.conds">
<span class="ui label basic small">
<span>{{paramMap[cond.param].name}}
<span v-if="paramMap[cond.param].operators != null && paramMap[cond.param].operators.length > 0"><span class="grey">{{operatorMap[cond.operator]}}</span> {{cond.valueFormat}}</span>
</span>
</span>
<span v-if="index < condsConfig.conds.length - 1"> &nbsp;<span v-if="condsConfig.connector == 'and'">且</span><span v-else>或</span>&nbsp; </span>
</span>
</div>`
})