实现完整的集群、域名同步

This commit is contained in:
刘祥超
2020-11-14 21:28:14 +08:00
parent a4417f5367
commit b86315a68c
17 changed files with 279 additions and 61 deletions

View File

@@ -212,6 +212,10 @@ func (this *RPCClient) DNSDomainRPC() pb.DNSDomainServiceClient {
return pb.NewDNSDomainServiceClient(this.pickConn())
}
func (this *RPCClient) DNSRPC() pb.DNSServiceClient {
return pb.NewDNSServiceClient(this.pickConn())
}
// 构造Admin上下文
func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background()

View File

@@ -68,10 +68,11 @@ func (this *ClusterAction) RunGet(params struct {
nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes {
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddr": node.IpAddr,
"route": node.Route,
"id": node.Id,
"name": node.Name,
"ipAddr": node.IpAddr,
"route": node.Route,
"clusterId": node.ClusterId,
})
}
this.Data["nodes"] = nodeMaps
@@ -92,5 +93,13 @@ func (this *ClusterAction) RunGet(params struct {
}
this.Data["servers"] = serverMaps
// 检查解析记录是否有变化
checkChangesResp, err := this.RPC().NodeClusterRPC().CheckNodeClusterDNSChanges(this.AdminContext(), &pb.CheckNodeClusterDNSChangesRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dnsHasChanges"] = checkChangesResp.IsChanged
this.Show()
}

View File

@@ -0,0 +1,46 @@
package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type SyncAction struct {
actionutils.ParentAction
}
func (this *SyncAction) RunPost(params struct {
ClusterId int64
}) {
// 记录日志
this.CreateLog(oplogs.LevelInfo, "同步集群 %d 的DNS设置", params.ClusterId)
dnsInfoResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
domain := dnsInfoResp.Domain
if domain == nil || domain.Id <= 0 {
this.Fail("此集群尚未设置域名")
}
syncResp, err := this.RPC().DNSDomainRPC().SyncDNSDomainData(this.AdminContext(), &pb.SyncDNSDomainDataRequest{
DnsDomainId: domain.Id,
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
if syncResp.ShouldFix {
this.Fail("请先修改当前页面中红色标记的问题")
}
if !syncResp.IsOk {
this.Fail(syncResp.Error)
}
this.Success()
}

View File

@@ -23,6 +23,7 @@ func init() {
// 集群
Prefix("/dns/clusters").
Get("/cluster", new(clusters.ClusterAction)).
Post("/sync", new(clusters.SyncAction)).
// 服务商
Prefix("/dns/providers").
@@ -47,9 +48,8 @@ func init() {
// 问题修复
Prefix("/dns/issues").
Data("teaSubMenu", "issue").
Get("", new(issues.IndexAction)).
GetPost("", new(issues.IndexAction)).
GetPost("/updateNodePopup", new(issues.UpdateNodePopupAction)).
GetPost("/updateServerPopup", new(issues.UpdateServerPopupAction)).
EndData().
EndAll()

View File

@@ -1,6 +1,10 @@
package issues
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
@@ -11,5 +15,27 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct{}) {
this.Data["issues"] = []interface{}{}
this.Show()
}
func (this *IndexAction) RunPost(params struct{}) {
issuesResp, err := this.RPC().DNSRPC().FindAllDNSIssues(this.AdminContext(), &pb.FindAllDNSIssuesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
issueMaps := []maps.Map{}
for _, issue := range issuesResp.Issues {
issueMaps = append(issueMaps, maps.Map{
"target": issue.Target,
"targetId": issue.TargetId,
"type": issue.Type,
"description": issue.Description,
"params": issue.Params,
})
}
this.Data["issues"] = issueMaps
this.Success()
}

View File

@@ -35,6 +35,7 @@ func (this *UpdateNodePopupAction) RunGet(params struct {
this.Data["ipAddr"] = dnsInfo.IpAddr
this.Data["route"] = dnsInfo.Route
this.Data["domainId"] = dnsInfo.DnsDomainId
this.Data["domainName"] = dnsInfo.DnsDomainName
// 读取所有线路
routes := []string{}

View File

@@ -1,15 +0,0 @@
package issues
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type UpdateServerPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateServerPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateServerPopupAction) RunGet(params struct{}) {
this.Show()
}

View File

@@ -60,15 +60,15 @@ func (this *ProviderAction) RunGet(params struct {
dataUpdatedTime = timeutil.FormatTime("Y-m-d H:i:s", domain.DataUpdatedAt)
}
domainMaps = append(domainMaps, maps.Map{
"id": domain.Id,
"name": domain.Name,
"isOn": domain.IsOn,
"dataUpdatedTime": dataUpdatedTime,
"countRoutes": len(domain.Routes),
"countServerRecords": domain.ServerRecords,
"allServersResolved": domain.AllServersResolved,
"countClusterRecords": domain.ClusterRecords,
"allClustersResolved": domain.AllClustersResolved,
"id": domain.Id,
"name": domain.Name,
"isOn": domain.IsOn,
"dataUpdatedTime": dataUpdatedTime,
"countRoutes": len(domain.Routes),
"countServerRecords": domain.CountServerRecords,
"serversChanged": domain.ServersChanged,
"countNodeRecords": domain.CountNodeRecords,
"nodesChanged": domain.NodesChanged,
})
}
this.Data["domains"] = domainMaps

View File

@@ -35,6 +35,17 @@ Vue.component("link-popup", {
template: `<a href="" :title="title" @click.prevent="clickPrevent"><slot></slot></a>`
})
// 小提示
Vue.component("tip-icon", {
props: ["content"],
methods: {
showTip: function () {
teaweb.popupTip(this.content)
}
},
template: `<a href="" title="查看帮助" @click.prevent="showTip"><i class="icon question circle"></i></a>`
})
// 提交点击事件
function emitClick(obj, arguments) {
let event = "click"

View File

@@ -131,6 +131,16 @@ window.teaweb = {
window.POPUP_CALLBACK.apply(window, arguments);
}
},
popupTip: function (html) {
Swal.fire({
html: '<i class="icon question circle"></i><span style="line-height: 1.7">' + html + "</span>",
width: "30em",
padding: "5em",
showConfirmButton: false,
showCloseButton: true,
focusConfirm: false
});
},
isPopup: function () {
var hash = window.location.hash;
return hash != null && hash.startsWith("#popup");

View File

@@ -17,7 +17,7 @@
<span v-if="dnsInfo.domainName.length > 0"><var>{{dnsInfo.dnsName}}</var>.{{dnsInfo.domainName}}</span>
<span v-else class="disabled">没有设置</span>
&nbsp; <a href="" @click.prevent="updateClusterDNS(cluster.id)">[修改]</a>
&nbsp; <a href="" @click.prevent="updateCluster(cluster.id)">[修改]</a>
</td>
</tr>
<tr>
@@ -30,6 +30,16 @@
<span v-else class="disabled">没有设置</span>
</td>
</tr>
<tr v-if="dnsInfo.domainName.length > 0">
<td>操作</td>
<td>
<div v-if="!isSyncing">
<link-red v-if="dnsHasChanges" @click.prevent="syncCluster(cluster.id)">检测到解析记录有变化,需要同步</link-red>
<a href="" @click.prevent="syncCluster(cluster.id)" v-else>DNS服务商同步</a>
</div>
<span v-else>DNS服务商同步中...</span>
</td>
</tr>
</table>
<p class="comment">下面的DNS解析记录也可以手工在DNS服务商提供的管理平台添加。</p>
@@ -48,10 +58,10 @@
</tr>
</thead>
<tr v-for="node in nodes">
<td><link-icon :href="'/clusters/cluster/node?nodeId=' + node.id">{{node.name}}</link-icon></td>
<td><link-icon :href="'/clusters/cluster/node?clusterId=' + node.clusterId + '&nodeId=' + node.id">{{node.name}}</link-icon></td>
<td>
<span v-if="dnsInfo.dnsName.length > 0">{{dnsInfo.dnsName}}</span>
<link-red v-else @click.prevent="updateClusterDNS(cluster.id)">没有设置</link-red>
<link-red v-else @click.prevent="updateCluster(cluster.id)">没有设置</link-red>
</td>
<td>A</td>
<td>
@@ -85,7 +95,7 @@
<td>CNAME</td>
<td>
<span v-if="dnsInfo.domainName.length > 0"><var>{{dnsInfo.dnsName}}</var>.{{dnsInfo.domainName}}.</span>
<link-red title="点击设置" v-else @click.prevent="updateClusterDNS(cluster.id)">没有设置</link-red>
<link-red title="点击设置" v-else @click.prevent="updateCluster(cluster.id)">没有设置</link-red>
</td>
</tr>
</table>

View File

@@ -1,5 +1,5 @@
Tea.context(function () {
this.updateClusterDNS = function (clusterId) {
this.updateCluster = function (clusterId) {
teaweb.popup("/dns/updateClusterPopup?clusterId=" + clusterId, {
height: "22em",
callback: function () {
@@ -19,4 +19,22 @@ Tea.context(function () {
}
})
}
this.isSyncing = false
this.syncCluster = function (clusterId) {
let that = this
teaweb.confirm("确定要执行数据同步吗?", function () {
that.isSyncing = true
that.$post(".sync")
.params({clusterId: clusterId})
.done(function () {
that.isSyncing = false
})
.success(function () {
teaweb.success("同步成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -1,3 +1,50 @@
{$layout}
<p class="ui message">此功能暂未开放,敬请期待。</p>
<first-menu>
<span class="item" v-if="issues.length > 0"><span class="red">{{issues.length}}</span>个问题</span>
<span class="item" v-if="issues.length > 0">|</span>
<a href="/dns/issues" title="刷新" class="item" @click.prevent="reload">刷新</a>
<span class="item">|</span>
<span class="item"><tip-icon content="这里是一个全局的DNS解析问题发现页方便我们诊断并修复问题。"></tip-icon></span>
</first-menu>
<div v-if="isRequesting">
<div class="margin"></div>
正在检查系统问题,请耐心等待...
</div>
<div v-if="issues.length == 0 && !isRequesting">
<div class="margin"></div>
<p class="comment">暂时没有发现问题。</p>
</div>
<table class="ui table selectable" v-if="issues.length > 0">
<thead>
<tr>
<th style="width: 50%">问题对象</th>
<th>问题描述</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="issue in issues">
<td>
<div v-if="issue.type == 'cluster'">
集群 "{{issue.target}}" <link-icon :href="'/clusters/cluster?clusterId=' + issue.targetId"></link-icon>
</div>
<div v-if="issue.type == 'node'">
集群 "{{issue.params.clusterName}}" 节点 "{{issue.target}}" <link-icon :href="'/clusters/cluster/node?clusterId=' + issue.params.clusterId + '&nodeId=' + issue.targetId"></link-icon>
</div>
</td>
<td>
<span>{{issue.description}}</span>
</td>
<td>
<div v-if="issue.type == 'cluster'">
<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>
</div>
</td>
</tr>
</table>

View File

@@ -0,0 +1,41 @@
Tea.context(function () {
this.isRequesting = true
this.$delay(function () {
this.reload()
})
this.updateCluster = function (clusterId) {
let that = this
teaweb.popup("/dns/updateClusterPopup?clusterId=" + clusterId, {
height: "22em",
callback: function () {
teaweb.success("保存成功", function () {
that.reload()
})
}
})
}
this.updateNode = function (nodeId) {
let that = this
teaweb.popup("/dns/issues/updateNodePopup?nodeId=" + nodeId, {
callback: function () {
teaweb.success("保存成功", function () {
that.reload()
})
}
})
}
this.reload = function () {
this.isRequesting = true
this.$post("$")
.success(function (resp) {
this.issues = resp.data.issues;
})
.done(function () {
this.isRequesting = false
})
}
})

View File

@@ -8,6 +8,12 @@
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr v-if="domainName.length > 0">
<td>主域名</td>
<td>{{domainName}}
<p class="comment">由当前节点所属集群设置。</p>
</td>
</tr>
<tr>
<td class="title">IP地址 *</td>
<td>

View File

@@ -40,7 +40,7 @@
<tr>
<th>域名</th>
<th>线路</th>
<th>集群域名</th>
<th>节点域名</th>
<th>服务域名</th>
<th>数据更新时间</th>
<th>状态</th>
@@ -54,11 +54,11 @@
<span v-else class="disabled">0个</span>
</td>
<td>
<a href="" v-if="domain.countClusterRecords > 0">{{domain.countClusterRecords}}个</a>
<span v-if="domain.countNodeRecords > 0">{{domain.countNodeRecords}}个</span>
<span v-else class="disabled">0个</span>
</td>
<td>
<a href="" v-if="domain.countServerRecords > 0">{{domain.countServerRecords}}个</a>
<span v-if="domain.countServerRecords > 0">{{domain.countServerRecords}}个</span>
<span v-else class="disabled">0个</span>
</td>
<td>
@@ -66,7 +66,7 @@
<span v-else class="disabled">尚未更新</span>
</td>
<td>
<div v-if="domain.countRoutes == 0 || !domain.allClustersResolved || !domain.allServersResolved">
<div v-if="domain.countRoutes == 0 || domain.nodesChanged || domain.serversChanged">
<a href="" style="border-bottom: 1px #db2828 dashed" title="点击和DNS服务商系统同步" @click.prevent="syncDomain(index,domain)" v-if="!domain.isSyncing"><span class="red">需要同步</span></a>
<span v-else>正在同步...</span>
</div>

View File

@@ -43,29 +43,33 @@ Tea.context(function () {
}
this.syncDomain = function (index, domain) {
domain.isSyncing = true
Vue.set(this.domains, index, domain)
let that = this
teaweb.confirm("确定要同步此域名下的所有解析记录吗?", function () {
domain.isSyncing = true
Vue.set(that.domains, index, domain)
this.$post("/dns/domains/sync")
.params({
domainId: domain.id
})
.success(function () {
teaweb.success("同步成功", function () {
teaweb.reload()
this.$post("/dns/domains/sync")
.params({
domainId: domain.id
})
})
.fail(function (resp) {
teaweb.warn(resp.message, function () {
if (resp.data.shouldFix) {
window.location = "/dns/issues"
}
.success(function () {
teaweb.success("同步成功", function () {
teaweb.reload()
})
})
})
.done(function () {
domain.isSyncing = false
Vue.set(this.domains, index, domain)
})
.fail(function (resp) {
teaweb.warn(resp.message, function () {
if (resp.data.shouldFix) {
window.location = "/dns/issues"
}
})
})
.done(function () {
domain.isSyncing = false
that.dnsHasChanges = false
Vue.set(that.domains, index, domain)
})
})
}
this.showRoutes = function (domainId) {