初步实现多集群共享节点

This commit is contained in:
GoEdgeLab
2021-07-31 22:23:07 +08:00
parent 15fb58581d
commit 3654c27c09
38 changed files with 610 additions and 201 deletions

View File

@@ -1,3 +1,4 @@
// 单个集群选择
Vue.component("cluster-selector", {
mounted: function () {
let that = this

View File

@@ -0,0 +1,28 @@
// 显示节点的多个集群
Vue.component("node-clusters-labels", {
props: ["v-primary-cluster", "v-secondary-clusters", "size"],
data: function () {
let cluster = this.vPrimaryCluster
let secondaryClusters = this.vSecondaryClusters
if (secondaryClusters == null) {
secondaryClusters = []
}
let labelSize = this.size
if (labelSize == null) {
labelSize = "small"
}
if (labelSize == "tiny") {
labelSize += " olive"
}
return {
cluster: cluster,
secondaryClusters: secondaryClusters,
labelSize: labelSize
}
},
template: `<div>
<a v-if="cluster != null" :href="'/clusters/cluster?clusterId=' + cluster.id" class="ui label basic" :class="labelSize" title="主集群" style="margin-bottom: 0.3em;">{{cluster.name}}</a>
<a v-for="c in secondaryClusters" :href="'/clusters/cluster?clusterId=' + c.id" class="ui label basic" :class="labelSize" title="从集群" style="margin-bottom: 0.3em;"><span class="grey" style="text-decoration: none">{{c.name}}</span></a>
</div>`
})

View File

@@ -0,0 +1,98 @@
// 一个节点的多个集群选择器
Vue.component("node-clusters-selector", {
props: ["v-primary-cluster", "v-secondary-clusters"],
data: function () {
let primaryCluster = this.vPrimaryCluster
let secondaryClusters = this.vSecondaryClusters
if (secondaryClusters == null) {
secondaryClusters = []
}
return {
primaryClusterId: (primaryCluster == null) ? 0 : primaryCluster.id,
secondaryClusterIds: secondaryClusters.map(function (v) {
return v.id
}),
primaryCluster: primaryCluster,
secondaryClusters: secondaryClusters
}
},
methods: {
addPrimary: function () {
let that = this
let selectedClusterIds = [this.primaryClusterId].concat(this.secondaryClusterIds)
teaweb.popup("/clusters/selectPopup?selectedClusterIds=" + selectedClusterIds.join(",") + "&mode=single", {
height: "38em",
width: "50em",
callback: function (resp) {
if (resp.data.cluster != null) {
that.primaryCluster = resp.data.cluster
that.primaryClusterId = that.primaryCluster.id
that.notifyChange()
}
}
})
},
removePrimary: function () {
this.primaryClusterId = 0
this.primaryCluster = null
this.notifyChange()
},
addSecondary: function () {
let that = this
let selectedClusterIds = [this.primaryClusterId].concat(this.secondaryClusterIds)
teaweb.popup("/clusters/selectPopup?selectedClusterIds=" + selectedClusterIds.join(",") + "&mode=multiple", {
height: "38em",
width: "50em",
callback: function (resp) {
if (resp.data.cluster != null) {
that.secondaryClusterIds.push(resp.data.cluster.id)
that.secondaryClusters.push(resp.data.cluster)
that.notifyChange()
}
}
})
},
removeSecondary: function (index) {
this.secondaryClusterIds.$remove(index)
this.secondaryClusters.$remove(index)
this.notifyChange()
},
notifyChange: function () {
this.$emit("change", {
clusterId: this.primaryClusterId
})
}
},
template: `<div>
<input type="hidden" name="primaryClusterId" :value="primaryClusterId"/>
<input type="hidden" name="secondaryClusterIds" :value="JSON.stringify(secondaryClusterIds)"/>
<table class="ui table">
<tr>
<td class="title">主集群</td>
<td>
<div v-if="primaryCluster != null">
<div class="ui label basic small">{{primaryCluster.name}} &nbsp; <a href="" title="删除" @click.prevent="removePrimary"><i class="icon remove small"></i></a> </div>
</div>
<div style="margin-top: 0.6em" v-if="primaryClusterId == 0">
<button class="ui button tiny" type="button" @click.prevent="addPrimary">+</button>
</div>
<p class="comment">多个集群配置有冲突时,优先使用主集群配置。</p>
</td>
</tr>
<tr>
<td>从集群</td>
<td>
<div v-if="secondaryClusters.length > 0">
<div class="ui label basic small" v-for="(cluster, index) in secondaryClusters"><span class="grey">{{cluster.name}}</span> &nbsp; <a href="" title="删除" @click.prevent="removeSecondary(index)"><i class="icon remove small"></i></a> </div>
</div>
<div style="margin-top: 0.6em">
<button class="ui button tiny" type="button" @click.prevent="addSecondary">+</button>
</div>
</td>
</tr>
</table>
</div>`
})

View File

@@ -8,7 +8,7 @@ Vue.component("dns-route-selector", {
return {
routes: routes,
routeCodes: routes.$map(function (k, v) {
return v.code
return v.code + "@" + v.domainId
}),
isAdding: false,
routeCode: ""
@@ -31,7 +31,7 @@ Vue.component("dns-route-selector", {
}
let that = this
let route = this.vAllRoutes.$find(function (k, v) {
return v.code == that.routeCode
return v.code + "@" + v.domainId == that.routeCode
})
if (route == null) {
return
@@ -53,8 +53,8 @@ Vue.component("dns-route-selector", {
template: `<div>
<input type="hidden" name="dnsRoutesJSON" :value="JSON.stringify(routeCodes)"/>
<div v-if="routes.length > 0">
<tiny-basic-label v-for="route in routes" :key="route.code">
{{route.name}} <a href="" @click.prevent="remove(route)"><i class="icon remove"></i></a>
<tiny-basic-label v-for="route in routes" :key="route.code + '@' + route.domainId">
{{route.name}} <span class="grey small">{{route.domainName}}</span><a href="" @click.prevent="remove(route)"><i class="icon remove"></i></a>
</tiny-basic-label>
<div class="ui divider"></div>
</div>
@@ -64,7 +64,7 @@ Vue.component("dns-route-selector", {
<div class="ui field">
<select class="ui dropdown auto-width" v-model="routeCode">
<option value="">[请选择]</option>
<option v-for="route in vAllRoutes" :value="route.code">{{route.name}}</option>
<option v-for="route in vAllRoutes" :value="route.code + '@' + route.domainId">{{route.name}}{{route.domainName}}</option>
</select>
</div>
<div class="ui field">

View File

@@ -317,6 +317,27 @@ window.teaweb = {
Swal.fire(config);
},
toast: function (message, timeout, callback) {
if (timeout == null) {
timeout = 2000
}
var width = "20em";
if (message.length > 30) {
width = "30em";
}
Swal.fire({
text: message,
icon: "info",
width: width,
timer: timeout,
showConfirmButton: false,
onAfterClose: function () {
if (typeof callback == "function") {
callback()
}
}
});
},
successToast: function (message, timeout, callback) {
if (timeout == null) {
timeout = 2000

File diff suppressed because one or more lines are too long

View File

@@ -440,6 +440,7 @@ body.expanded .main {
.main-menu.theme1 {
background: #14539A !important;
.menu {
background: #14539A !important;
}
@@ -447,6 +448,7 @@ body.expanded .main {
.main-menu.theme2 {
background: #276AC6 !important;
.menu {
background: #276AC6 !important;
}
@@ -454,6 +456,7 @@ body.expanded .main {
.main-menu.theme3 {
background: #007D9C !important;
.menu {
background: #007D9C !important;
}

View File

@@ -1,5 +1,5 @@
<second-menu>
<menu-item :href="'/clusters/cluster/nodes?clusterId=' + clusterId">节点列表</menu-item>
<menu-item :href="'/clusters/cluster/nodes?clusterId=' + clusterId">节点列表</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id" code="node"
v-if="!teaIsPlus">"{{node.name}}"节点详情</menu-item>
@@ -7,6 +7,6 @@
<menu-item :href="'/clusters/cluster/node/detail?clusterId=' + clusterId + '&nodeId=' + node.id" code="node" v-if="teaIsPlus">节点详情</menu-item>
<menu-item :href="'/clusters/cluster/node/thresholds?clusterId=' + clusterId + '&nodeId=' + node.id" code="threshold" v-if="teaIsPlus">阈值设置</menu-item>
<menu-item :href="'/clusters/cluster/node/logs?clusterId=' + clusterId + '&nodeId=' + node.id" code="log">运行日志</menu-item>
<menu-item :href="'/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id" code="install">安装节点</menu-item>
<menu-item :href="'/clusters/cluster/node/update?clusterId=' + clusterId + '&nodeId=' + node.id" code="update">修改设置</menu-item>
<menu-item :href="'/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id" code="install">安装节点</menu-item>
</second-menu>

View File

@@ -12,6 +12,12 @@
<td>状态</td>
<td><label-on :v-is-on="node.isOn"></label-on></td>
</tr>
<tr>
<td>所属集群</td>
<td>
<node-clusters-labels :v-primary-cluster="node.cluster" :v-secondary-clusters="node.secondaryClusters"></node-clusters-labels>
</td>
</tr>
<tr>
<td>IP地址</td>
<td>
@@ -29,38 +35,34 @@
</div>
</td>
</tr>
<tr v-if="dnsRoutes.length > 0 && dnsRoutes[0].name.length > 0">
<tr v-if="node.routes.length > 0">
<td>DNS线路</td>
<td>
<span class="ui label tiny basic" v-for="route in dnsRoutes">{{route.name}}</span>
<span class="ui label tiny basic" v-for="route in node.routes">{{route.name}} <span class="grey small">{{route.domainName}}</span></span>
</td>
</tr>
<tr v-if="dnsRecordName.length > 0 && dnsRecordValue.length > 0">
<tr v-if="node.records.length > 0">
<td>DNS记录</td>
<td>
<table class="ui table celled">
<thead class="full-width">
<tr>
<th>记录名</th>
<th>记录类型</th>
<th>线路</th>
<th>记录值</th>
</tr>
<tr>
<th>记录名</th>
<th>记录类型</th>
<th>线路</th>
<th>记录值</th>
</tr>
</thead>
<tbody v-for="address in node.ipAddresses" v-if="address.canAccess">
<tr v-for="route in dnsRoutes">
<td>{{dnsRecordName}}</td>
<td>
<span v-if="address.ip.indexOf(':') > -1">AAAA</span>
<span v-else>A</span>
</td>
<td>
<span v-if="route.name.length > 0">{{route.name}}</span>
<span v-else class="disabled">默认</span>
</td>
<td>{{address.ip}}</td>
</tr>
</tbody>
<tr v-for="record in node.records">
<td>{{record.name}}</td>
<td>{{record.type}}</td>
<td>
<span v-if="record.route.length > 0">{{record.route}}</span>
<span v-else class="disabled">默认</span>
</td>
<td>{{record.value}}</td>
</tr>
</table>
<p class="comment">通过设置A记录可以将集群上的服务请求转发到不同线路的节点上。</p>
</td>

View File

@@ -13,6 +13,12 @@
<input type="text" name="name" maxlength="50" ref="focus" v-model="node.name"/>
</td>
</tr>
<tr>
<td>所属集群</td>
<td>
<node-clusters-selector :v-primary-cluster="node.primaryCluster" :v-secondary-clusters="node.secondaryClusters" @change="changeClusters"></node-clusters-selector>
</td>
</tr>
<tr>
<td>IP地址 *</td>
<td>
@@ -20,22 +26,13 @@
<p class="comment">用于访问节点和域名解析等。</p>
</td>
</tr>
<tr v-if="allDNSRoutes.length > 0">
<tr v-if="allDNSRoutes.length > 0">
<td>DNS线路</td>
<td>
<input type="hidden" name="dnsDomainId" :value="dnsDomainId"/>
<dns-route-selector :v-all-routes="allDNSRoutes" :v-routes="dnsRoutes"></dns-route-selector>
<p class="comment">当前节点对应的DNS线路可用线路是根据集群设置的域名获取的注意DNS服务商可能对这些线路有其他限制。</p>
</td>
</tr>
<tr>
<td>所属集群</td>
<td>
<select class="ui dropdown" name="clusterId" style="width:10em" v-model="clusterId">
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
</td>
</tr>
<tr>
<td>所属区域</td>
<td>

View File

@@ -1,27 +1,32 @@
Tea.context(function () {
this.clusterId = 0;
this.clusterId = 0
if (this.node.cluster != null && this.node.cluster.id > 0) {
this.clusterId = this.node.cluster.id;
this.clusterId = this.node.cluster.id
}
this.success = NotifySuccess("保存成功", "/clusters/cluster/node?clusterId=" + this.clusterId + "&nodeId=" + this.node.id);
this.success = function () {
let that = this
teaweb.success("保存成功", function () {
window.location = "/clusters/cluster/node/detail?clusterId=" + that.clusterId + "&nodeId=" + that.node.id
})
}
// IP地址相关
this.ipAddresses = this.node.ipAddresses;
this.ipAddresses = this.node.ipAddresses
// 认证相关
this.grant = null;
this.grant = null
this.sshHost = "";
this.sshPort = "";
this.loginId = 0;
this.sshHost = ""
this.sshPort = ""
this.loginId = 0
if (this.node.login != null) {
this.loginId = this.node.login.id;
this.loginId = this.node.login.id
if (this.node.login.params != null) {
this.sshHost = this.node.login.params.host;
this.sshHost = this.node.login.params.host
if (this.node.login.params.port > 0) {
this.sshPort = this.node.login.params.port;
this.sshPort = this.node.login.params.port
}
}
@@ -31,7 +36,11 @@ Tea.context(function () {
name: this.node.login.grant.name,
method: this.node.login.grant.method,
methodName: this.node.login.grant.methodName
};
}
}
}
});
this.changeClusters = function (info) {
this.clusterId = info.clusterId
}
})

View File

@@ -68,10 +68,13 @@
<tr v-for="node in nodes">
<td>{{node.name}}
<div style="margin-top: 0.5em" v-if="node.region != null">
<tiny-basic-label>区域:{{node.region.name}}</tiny-basic-label>
<tiny-basic-label class="olive">区域:{{node.region.name}}</tiny-basic-label>
</div>
<div style="margin-top: 0.5em" v-if="node.group != null">
<tiny-basic-label>分组:{{node.group.name}}</tiny-basic-label>
<tiny-basic-label class="olive">分组:{{node.group.name}}</tiny-basic-label>
</div>
<div style="margin-top: 0.5em">
<node-clusters-labels :v-primary-cluster="node.cluster" :v-secondary-clusters="node.secondaryClusters" size="tiny"></node-clusters-labels>
</div>
</td>
<td>

View File

@@ -2,9 +2,10 @@ Tea.context(function () {
this.teaweb = teaweb
this.deleteNode = function (nodeId) {
teaweb.confirm("确定要删除这个节点吗?", function () {
teaweb.confirm("确定要从当前集群中删除这个节点吗?", function () {
this.$post("/nodes/delete")
.params({
clusterId: this.clusterId,
nodeId: nodeId
})
.refresh();

View File

@@ -9,14 +9,14 @@
<table class="ui table selectable definition">
<tr v-if="hasDomains">
<td>选择主域名</td>
<td>选择主域名 *</td>
<td>
<dns-domain-selector :v-domain-id="domainId" :v-domain-name="domainName"></dns-domain-selector>
<p class="comment">用于生成集群节点和网站服务的DNS解析记录。</p>
</td>
</tr>
<tr>
<td class="title">DNS子域名</td>
<td class="title">DNS子域名 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="dnsName" maxlength="64" style="width:10em" v-model="dnsName"/>

View File

@@ -0,0 +1,4 @@
h4 span {
font-size: 0.8em;
}
/*# sourceMappingURL=create.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["create.less"],"names":[],"mappings":"AAAA,EAAG;EACF,gBAAA","file":"create.css"}

View File

@@ -13,17 +13,19 @@
<td>默认缓存设置 *</td>
<td>
<http-cache-policy-selector></http-cache-policy-selector>
<p class="comment">此全局设置不会强制应用到每个网站服务。</p>
</td>
</tr>
<tr>
<td>默认WAF设置 *</td>
<td>
<http-firewall-policy-selector></http-firewall-policy-selector>
<p class="comment">此全局设置不会强制应用到每个网站服务。</p>
</td>
</tr>
</table>
<h4>节点安装选项</h4>
<h4>节点安装选项 &nbsp;<span class="grey small">(可选)</span></h4>
<table class="ui table selectable definition">
<tr>
<td class="title">默认SSH登录方式</td>
@@ -49,7 +51,7 @@
</tr>
</table>
<h4>DNS设置选项</h4>
<h4>DNS设置选项 &nbsp;<span class="grey small">(可选)</span></h4>
<table class="ui table selectable definition">
<tr v-if="hasDomains">
<td>选择主域名</td>

View File

@@ -0,0 +1,3 @@
h4 span {
font-size: 0.8em;
}

View File

@@ -93,7 +93,7 @@
<tr v-for="node in nodes">
<td><keyword :v-word="keyword">{{node.name}}</keyword>
<div style="margin-top: 0.5em">
<span class="ui label tiny">集群:{{node.cluster.name}}</span>
<node-clusters-labels :v-primary-cluster="node.cluster" :v-secondary-clusters="node.secondaryClusters" size="tiny"></node-clusters-labels>
</div>
</td>
<td>

View File

@@ -0,0 +1,25 @@
{$layout "layout_popup"}
<h3>选择集群</h3>
<table class="ui table celled selectable">
<thead>
<tr>
<th>集群名称</th>
<th>节点数</th>
<th class="two wide">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="cluster in clusters">
<td>{{cluster.name}}</td>
<td>{{cluster.countNodes}}</td>
<td><label-on :v-is-on="cluster.isOn"></label-on></td>
<td>
<span class="disabled" v-if="cluster.isSelected">已选择</span>
<a href="" @click.prevent="select(cluster)" v-if="!cluster.isSelected">选择</a>
</td>
</tr>
</table>
<div v-html="page"></div>

View File

@@ -0,0 +1,11 @@
Tea.context(function () {
this.select = function (cluster) {
NotifyPopup({
code: 200,
message: "",
data: {
cluster: cluster
}
})
}
})

View File

@@ -71,7 +71,7 @@
<link-red @click.prevent="updateCluster(issue.targetId)">修复</link-red>
</div>
<div v-if="issue.type == 'node'">
<link-red @click.prevent="updateNode(issue.targetId)">修复</link-red>
<link-red @click.prevent="updateNode(issue.params.clusterId, issue.targetId)">修复</link-red>
</div>
</td>
</tr>
@@ -108,18 +108,18 @@
</td>
<td>
<span v-if="node.ipAddr.length > 0">{{node.ipAddr}}</span>
<link-red title="点击设置" v-else @click.prevent="updateNode(node.id)">没有设置</link-red>
<link-red title="点击设置" v-else @click.prevent="updateNode(node.clusterId, node.id)">没有设置</link-red>
</td>
<td>
<span v-if="node.route.code.length > 0">{{node.route.name}}</span>
<link-red v-else title="点击设置" @click.prevent="updateNode(node.id)">没有设置</link-red>
<link-red v-else title="点击设置" @click.prevent="updateNode(node.clusterId, node.id)">没有设置</link-red>
</td>
<td>
<span class="green" v-if="node.isResolved">已解析</span>
<span v-else class="red">未解析</span>
</td>
<td>
<link-popup @click.prevent="updateNode(node.id)">修改</link-popup>
<link-popup @click.prevent="updateNode(node.clusterId, node.id)">修改</link-popup>
</td>
</tr>
</table>

View File

@@ -10,8 +10,8 @@ Tea.context(function () {
})
}
this.updateNode = function (nodeId) {
teaweb.popup("/dns/issues/updateNodePopup?nodeId=" + nodeId, {
this.updateNode = function (clusterId, nodeId) {
teaweb.popup("/dns/issues/updateNodePopup?clusterId=" + clusterId + "&nodeId=" + nodeId, {
height: "26em",
callback: function () {
teaweb.success("保存成功", function () {

View File

@@ -43,7 +43,7 @@
<link-red @click.prevent="updateCluster(issue.targetId)">修复</link-red>
</div>
<div v-if="issue.type == 'node'">
<link-red @click.prevent="updateNode(issue.targetId)">修复</link-red>
<link-red @click.prevent="updateNode(issue.params.clusterId, issue.targetId)">修复</link-red>
</div>
</td>
</tr>

View File

@@ -17,9 +17,9 @@ Tea.context(function () {
})
}
this.updateNode = function (nodeId) {
this.updateNode = function (clusterId, nodeId) {
let that = this
teaweb.popup("/dns/issues/updateNodePopup?nodeId=" + nodeId, {
teaweb.popup("/dns/issues/updateNodePopup?clusterId=" + clusterId + "&nodeId=" + nodeId, {
callback: function () {
teaweb.success("保存成功", function () {
that.reload()