diff --git a/internal/web/actions/default/clusters/cluster/settings/dns/index.go b/internal/web/actions/default/clusters/cluster/settings/dns/index.go
index 50bc0bd0..43564fa5 100644
--- a/internal/web/actions/default/clusters/cluster/settings/dns/index.go
+++ b/internal/web/actions/default/clusters/cluster/settings/dns/index.go
@@ -14,7 +14,7 @@ type IndexAction struct {
 }
 
 func (this *IndexAction) Init() {
-	this.Nav("", "setting", "")
+	this.Nav("", "setting", "index")
 	this.SecondMenu("dns")
 }
 
diff --git a/internal/web/actions/default/clusters/cluster/settings/dns/records.go b/internal/web/actions/default/clusters/cluster/settings/dns/records.go
new file mode 100644
index 00000000..1add9963
--- /dev/null
+++ b/internal/web/actions/default/clusters/cluster/settings/dns/records.go
@@ -0,0 +1,303 @@
+// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
+
+package dns
+
+import (
+	"github.com/TeaOSLab/EdgeAdmin/internal/utils"
+	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+	"github.com/iwind/TeaGo/maps"
+	timeutil "github.com/iwind/TeaGo/utils/time"
+)
+
+type RecordsAction struct {
+	actionutils.ParentAction
+}
+
+func (this *RecordsAction) Init() {
+	this.Nav("", "setting", "records")
+	this.SecondMenu("dns")
+}
+
+func (this *RecordsAction) RunGet(params struct {
+	ClusterId int64
+}) {
+	// 集群信息
+	clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+	var cluster = clusterResp.NodeCluster
+	if cluster == nil {
+		this.NotFound("nodeCluster", params.ClusterId)
+		return
+	}
+	this.Data["cluster"] = maps.Map{
+		"id":   cluster.Id,
+		"name": cluster.Name,
+	}
+
+	// DNS信息
+	dnsResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+	var defaultRoute = dnsResp.DefaultRoute
+	var domainName = ""
+	var dnsMap = maps.Map{
+		"dnsName":          dnsResp.Name,
+		"domainId":         0,
+		"domainName":       "",
+		"providerId":       0,
+		"providerName":     "",
+		"providerTypeName": "",
+	}
+	if dnsResp.Domain != nil {
+		domainName = dnsResp.Domain.Name
+		dnsMap["domainId"] = dnsResp.Domain.Id
+		dnsMap["domainName"] = dnsResp.Domain.Name
+	}
+	if dnsResp.Provider != nil {
+		dnsMap["providerId"] = dnsResp.Provider.Id
+		dnsMap["providerName"] = dnsResp.Provider.Name
+		dnsMap["providerTypeName"] = dnsResp.Provider.TypeName
+	}
+
+	if len(dnsResp.CnameRecords) > 0 {
+		dnsMap["cnameRecords"] = dnsResp.CnameRecords
+	} else {
+		dnsMap["cnameRecords"] = []string{}
+	}
+
+	this.Data["dnsInfo"] = dnsMap
+
+	// 未安装的节点
+	notInstalledNodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
+		NodeClusterId: params.ClusterId,
+		IsInstalled:   false,
+	})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+	var allNodes = notInstalledNodesResp.Nodes
+
+	// 节点DNS解析记录
+	nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
+		NodeClusterId: params.ClusterId,
+		IsInstalled:   true,
+	})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+	var installedNodeIdsMap = map[int64]bool{}
+	for _, node := range nodesResp.Nodes {
+		installedNodeIdsMap[node.Id] = true
+	}
+
+	allNodes = append(allNodes, nodesResp.Nodes...)
+
+	var nodeMaps = []maps.Map{}
+	for _, node := range allNodes {
+		var isInstalled = installedNodeIdsMap[node.Id]
+
+		if len(node.Routes) > 0 {
+			for _, route := range node.Routes {
+				// 检查是否已解析
+				var isResolved = false
+				if isInstalled && cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 {
+					var recordType = "A"
+					if utils.IsIPv6(node.IpAddr) {
+						recordType = "AAAA"
+					}
+					checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
+						DnsDomainId: cluster.DnsDomainId,
+						Name:        cluster.DnsName,
+						Type:        recordType,
+						Route:       route.Code,
+						Value:       node.IpAddr,
+					})
+					if err != nil {
+						this.ErrorPage(err)
+						return
+					}
+					isResolved = checkResp.IsOk
+				}
+
+				nodeMaps = append(nodeMaps, maps.Map{
+					"id":       node.Id,
+					"name":     node.Name,
+					"ipAddr":   node.IpAddr,
+					"ipAddrId": node.NodeIPAddressId,
+					"route": maps.Map{
+						"name": route.Name,
+						"code": route.Code,
+					},
+					"clusterId":   node.NodeClusterId,
+					"isResolved":  isResolved,
+					"isInstalled": isInstalled,
+					"isBackup":    node.IsBackupForCluster || node.IsBackupForGroup,
+					"isOffline":   node.IsOffline,
+				})
+			}
+		} else {
+			// 默认线路
+			var isResolved = false
+			if isInstalled && len(defaultRoute) > 0 {
+				var recordType = "A"
+				if utils.IsIPv6(node.IpAddr) {
+					recordType = "AAAA"
+				}
+				checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
+					DnsDomainId: cluster.DnsDomainId,
+					Name:        cluster.DnsName,
+					Type:        recordType,
+					Route:       defaultRoute,
+					Value:       node.IpAddr,
+				})
+				if err != nil {
+					this.ErrorPage(err)
+					return
+				}
+				isResolved = checkResp.IsOk
+			}
+			nodeMaps = append(nodeMaps, maps.Map{
+				"id":       node.Id,
+				"name":     node.Name,
+				"ipAddr":   node.IpAddr,
+				"ipAddrId": node.NodeIPAddressId,
+				"route": maps.Map{
+					"name": "",
+					"code": "",
+				},
+				"clusterId":   node.NodeClusterId,
+				"isResolved":  isResolved,
+				"isInstalled": isInstalled,
+				"isBackup":    node.IsBackupForCluster || node.IsBackupForGroup,
+				"isOffline":   node.IsOffline,
+			})
+		}
+	}
+	this.Data["nodes"] = nodeMaps
+
+	// 代理服务解析记录
+	serversResp, err := this.RPC().ServerRPC().FindAllEnabledServersDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledServersDNSWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+	var serverMaps = []maps.Map{}
+	for _, server := range serversResp.Servers {
+		// 检查是否已解析
+		isResolved := false
+		if cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(server.DnsName) > 0 && len(domainName) > 0 {
+			checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
+				DnsDomainId: cluster.DnsDomainId,
+				Name:        server.DnsName,
+				Type:        "CNAME",
+				Value:       cluster.DnsName + "." + domainName,
+			})
+			if err != nil {
+				this.ErrorPage(err)
+				return
+			}
+			isResolved = checkResp.IsOk
+		}
+
+		serverMaps = append(serverMaps, maps.Map{
+			"id":         server.Id,
+			"name":       server.Name,
+			"dnsName":    server.DnsName,
+			"isResolved": isResolved,
+		})
+	}
+	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
+
+	// 需要解决的问题
+	issuesResp, err := this.RPC().DNSRPC().FindAllDNSIssues(this.AdminContext(), &pb.FindAllDNSIssuesRequest{
+		NodeClusterId: params.ClusterId,
+	})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+	var 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
+
+	// 当前正在执行的任务
+	resp, err := this.RPC().DNSTaskRPC().FindAllDoingDNSTasks(this.AdminContext(), &pb.FindAllDoingDNSTasksRequest{
+		NodeClusterId: params.ClusterId,
+	})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+	var taskMaps = []maps.Map{}
+	for _, task := range resp.DnsTasks {
+		var clusterMap maps.Map = nil
+		var nodeMap maps.Map = nil
+		var serverMap maps.Map = nil
+		var domainMap maps.Map = nil
+
+		if task.NodeCluster != nil {
+			clusterMap = maps.Map{
+				"id":   task.NodeCluster.Id,
+				"name": task.NodeCluster.Name,
+			}
+		}
+		if task.Node != nil {
+			nodeMap = maps.Map{
+				"id":   task.Node.Id,
+				"name": task.Node.Name,
+			}
+		}
+		if task.Server != nil {
+			serverMap = maps.Map{
+				"id":   task.Server.Id,
+				"name": task.Server.Name,
+			}
+		}
+		if task.DnsDomain != nil {
+			domainMap = maps.Map{
+				"id":   task.DnsDomain.Id,
+				"name": task.DnsDomain.Name,
+			}
+		}
+
+		taskMaps = append(taskMaps, maps.Map{
+			"id":          task.Id,
+			"type":        task.Type,
+			"isDone":      task.IsDone,
+			"isOk":        task.IsOk,
+			"error":       task.Error,
+			"updatedTime": timeutil.FormatTime("Y-m-d H:i:s", task.UpdatedAt),
+			"cluster":     clusterMap,
+			"node":        nodeMap,
+			"server":      serverMap,
+			"domain":      domainMap,
+		})
+	}
+	this.Data["tasks"] = taskMaps
+
+	this.Show()
+}
diff --git a/internal/web/actions/default/clusters/cluster/settings/init.go b/internal/web/actions/default/clusters/cluster/settings/init.go
index 36d98a73..fa6ed537 100644
--- a/internal/web/actions/default/clusters/cluster/settings/init.go
+++ b/internal/web/actions/default/clusters/cluster/settings/init.go
@@ -40,6 +40,7 @@ func init() {
 			// DNS
 			Prefix("/clusters/cluster/settings/dns").
 			GetPost("", new(dns.IndexAction)).
+			Get("/records", new(dns.RecordsAction)).
 			Post("/randomName", new(dns.RandomNameAction)).
 
 			// 系统服务设置
diff --git a/web/views/@default/clusters/cluster/settings/dns/@menu.html b/web/views/@default/clusters/cluster/settings/dns/@menu.html
new file mode 100644
index 00000000..d0c16223
--- /dev/null
+++ b/web/views/@default/clusters/cluster/settings/dns/@menu.html
@@ -0,0 +1,4 @@
+