优化域名服务相关功能

This commit is contained in:
GoEdgeLab
2021-06-01 16:42:25 +08:00
parent df5cac87b4
commit 35cc12cdb8
15 changed files with 204 additions and 41 deletions

1
go.sum
View File

@@ -9,6 +9,7 @@ github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiU
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=

View File

@@ -1,26 +1,38 @@
package domainutils package domainutils
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"net"
"regexp" "regexp"
"strings" "strings"
) )
// 校验域名格式 // ValidateDomainFormat 校验域名格式
func ValidateDomainFormat(domain string) bool { func ValidateDomainFormat(domain string) bool {
pieces := strings.Split(domain, ".") pieces := strings.Split(domain, ".")
for _, piece := range pieces { for _, piece := range pieces {
if !regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(piece) { if piece == "-" ||
strings.HasPrefix(piece, "-") ||
strings.HasSuffix(piece, "-") ||
strings.Contains(piece, "--") ||
len(piece) > 63 ||
!regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(piece) {
return false return false
} }
} }
// 最后一段不能是全数字
if regexp.MustCompile(`^(\d+)$`).MatchString(pieces[len(pieces)-1]) {
return false
}
return true return true
} }
// 转换线路列表 // ConvertRoutesToMaps 转换线路列表
func ConvertRoutesToMaps(routes []*pb.DNSRoute) []maps.Map { func ConvertRoutesToMaps(routes []*pb.DNSRoute) []maps.Map {
result := []maps.Map{} result := []maps.Map{}
for _, route := range routes { for _, route := range routes {
@@ -32,7 +44,7 @@ func ConvertRoutesToMaps(routes []*pb.DNSRoute) []maps.Map {
return result return result
} }
// 筛选线路 // FilterRoutes 筛选线路
func FilterRoutes(routes []*pb.DNSRoute, allRoutes []*pb.DNSRoute) []*pb.DNSRoute { func FilterRoutes(routes []*pb.DNSRoute, allRoutes []*pb.DNSRoute) []*pb.DNSRoute {
routeCodes := []string{} routeCodes := []string{}
for _, route := range allRoutes { for _, route := range allRoutes {
@@ -46,3 +58,84 @@ func FilterRoutes(routes []*pb.DNSRoute, allRoutes []*pb.DNSRoute) []*pb.DNSRout
} }
return result return result
} }
// ValidateRecordName 校验记录名
func ValidateRecordName(name string) bool {
if name == "*" || name == "@" || len(name) == 0 {
return true
}
pieces := strings.Split(name, ".")
for _, piece := range pieces {
if piece == "-" ||
strings.HasPrefix(piece, "-") ||
strings.HasSuffix(piece, "-") ||
strings.Contains(piece, "--") ||
len(piece) > 63 ||
!regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(piece) {
return false
}
}
return true
}
// ValidateRecordValue 校验记录值
func ValidateRecordValue(recordType dnsconfigs.RecordType, value string) (message string, ok bool) {
switch recordType {
case dnsconfigs.RecordTypeA:
if !regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`).MatchString(value) {
message = "请输入正确格式的IP"
return
}
if net.ParseIP(value) == nil {
message = "请输入正确格式的IP"
return
}
case dnsconfigs.RecordTypeCNAME:
if strings.HasSuffix(value, ".") {
value = value[:len(value)-1]
}
if !strings.Contains(value, ".") || !ValidateDomainFormat(value) {
message = "请输入正确的域名"
return
}
case dnsconfigs.RecordTypeAAAA:
if !strings.Contains(value, ":") {
message = "请输入正确格式的IPv6地址"
return
}
if net.ParseIP(value) == nil {
message = "请输入正确格式的IPv6地址"
return
}
case dnsconfigs.RecordTypeNS:
if strings.HasSuffix(value, ".") {
value = value[:len(value)-1]
}
if !strings.Contains(value, ".") || !ValidateDomainFormat(value) {
message = "请输入正确的DNS服务器域名"
return
}
case dnsconfigs.RecordTypeMX:
if strings.HasSuffix(value, ".") {
value = value[:len(value)-1]
}
if !strings.Contains(value, ".") || !ValidateDomainFormat(value) {
message = "请输入正确的邮件服务器域名"
return
}
case dnsconfigs.RecordTypeTXT:
if len(value) > 512 {
message = "文本长度不能超出512字节"
return
}
}
if len(value) > 512 {
message = "记录值长度不能超出512字节"
return
}
ok = true
return
}

View File

@@ -0,0 +1,61 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package domainutils
import (
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestValidateRecordValue(t *testing.T) {
a := assert.NewAssertion(t)
// A
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeA, "1.2")
a.IsFalse(ok)
}
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeA, "1.2.3.400")
a.IsFalse(ok)
}
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeA, "1.2.3.4")
a.IsTrue(ok)
}
// CNAME
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeCNAME, "example.com")
a.IsTrue(ok)
}
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeCNAME, "example.com.")
a.IsTrue(ok)
}
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeCNAME, "hello, world")
a.IsFalse(ok)
}
// AAAA
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeAAAA, "1.2.3.4")
a.IsFalse(ok)
}
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeAAAA, "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
a.IsTrue(ok)
}
// NS
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeNS, "1.2.3.4")
a.IsFalse(ok)
}
{
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeNS, "example.com")
a.IsTrue(ok)
}
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"strings"
) )
type CreateAction struct { type CreateAction struct {
@@ -34,6 +35,8 @@ func (this *CreateAction) RunPost(params struct {
this.CreateLogInfo("创建域名 %d", domainId) this.CreateLogInfo("创建域名 %d", domainId)
}() }()
params.Name = strings.ToLower(params.Name)
params.Must. params.Must.
Field("name", params.Name). Field("name", params.Name).
Require("请输入域名"). Require("请输入域名").

View File

@@ -4,6 +4,7 @@ package records
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
@@ -63,6 +64,17 @@ func (this *CreatePopupAction) RunPost(params struct {
this.CreateLogInfo("创建域名记录 %d", recordId) this.CreateLogInfo("创建域名记录 %d", recordId)
}() }()
// 校验记录名
if !domainutils.ValidateRecordName(params.Name) {
this.FailField("name", "请输入正确的记录名")
}
// 校验记录值
message, ok := domainutils.ValidateRecordValue(params.Type, params.Value)
if !ok {
this.FailField("value", "记录值错误:"+message)
}
createResp, err := this.RPC().NSRecordRPC().CreateNSRecord(this.AdminContext(), &pb.CreateNSRecordRequest{ createResp, err := this.RPC().NSRecordRPC().CreateNSRecord(this.AdminContext(), &pb.CreateNSRecordRequest{
NsDomainId: params.DomainId, NsDomainId: params.DomainId,
Description: params.Description, Description: params.Description,

View File

@@ -4,6 +4,7 @@ package records
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
@@ -87,6 +88,17 @@ func (this *UpdatePopupAction) RunPost(params struct {
}) { }) {
this.CreateLogInfo("修改域名记录 %d", params.RecordId) this.CreateLogInfo("修改域名记录 %d", params.RecordId)
// 校验记录名
if !domainutils.ValidateRecordName(params.Name) {
this.FailField("name", "请输入正确的记录名")
}
// 校验记录值
message, ok := domainutils.ValidateRecordValue(params.Type, params.Value)
if !ok {
this.FailField("value", "记录值错误:"+message)
}
_, err := this.RPC().NSRecordRPC().UpdateNSRecord(this.AdminContext(), &pb.UpdateNSRecordRequest{ _, err := this.RPC().NSRecordRPC().UpdateNSRecord(this.AdminContext(), &pb.UpdateNSRecordRequest{
NsRecordId: params.RecordId, NsRecordId: params.RecordId,
Description: params.Description, Description: params.Description,

View File

@@ -4,7 +4,6 @@ package domains
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -57,7 +56,6 @@ func (this *UpdateAction) RunGet(params struct {
func (this *UpdateAction) RunPost(params struct { func (this *UpdateAction) RunPost(params struct {
DomainId int64 DomainId int64
Name string
ClusterId int64 ClusterId int64
UserId int64 UserId int64
IsOn bool IsOn bool
@@ -68,15 +66,6 @@ func (this *UpdateAction) RunPost(params struct {
this.CreateLogInfo("修改域名 %d", params.DomainId) this.CreateLogInfo("修改域名 %d", params.DomainId)
params.Must. params.Must.
Field("name", params.Name).
Require("请输入域名").
Expect(func() (message string, success bool) {
success = domainutils.ValidateDomainFormat(params.Name)
if !success {
message = "请输入正确的域名"
}
return
}).
Field("clusterId", params.ClusterId). Field("clusterId", params.ClusterId).
Gt(0, "请选择所属集群") Gt(0, "请选择所属集群")
@@ -84,7 +73,6 @@ func (this *UpdateAction) RunPost(params struct {
NsDomainId: params.DomainId, NsDomainId: params.DomainId,
NsClusterId: params.ClusterId, NsClusterId: params.ClusterId,
UserId: params.UserId, UserId: params.UserId,
Name: params.Name,
IsOn: params.IsOn, IsOn: params.IsOn,
}) })
if err != nil { if err != nil {

View File

@@ -51,8 +51,10 @@ Vue.component("ns-route-ranges-box", {
this.ranges.push({ this.ranges.push({
type: "ipRange", type: "ipRange",
ipFrom: this.ipRangeFrom, params: {
ipTo: this.ipRangeTo ipFrom: this.ipRangeFrom,
ipTo: this.ipRangeTo
}
}) })
this.cancelIPRange() this.cancelIPRange()
}, },
@@ -76,7 +78,7 @@ Vue.component("ns-route-ranges-box", {
<div v-if="ranges.length > 0"> <div v-if="ranges.length > 0">
<div class="ui label tiny basic" v-for="(range, index) in ranges"> <div class="ui label tiny basic" v-for="(range, index) in ranges">
<span v-if="range.type == 'ipRange'">IP范围</span> <span v-if="range.type == 'ipRange'">IP范围</span>
{{range.ipFrom}} - {{range.ipTo}} &nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a> {{range.params.ipFrom}} - {{range.params.ipTo}} &nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
</div> </div>

View File

@@ -1,5 +1,5 @@
{$layout} {$layout}
{$template "../menu"} {$template "menu"}
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success"> <form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="clusterId" :value="clusterId"/> <input type="hidden" name="clusterId" :value="clusterId"/>
@@ -17,11 +17,6 @@
<p class="comment">用于访问节点和域名解析等。</p> <p class="comment">用于访问节点和域名解析等。</p>
</td> </td>
</tr> </tr>
<tr>
<td colspan="2">
<more-options-indicator></more-options-indicator>
</td>
</tr>
</table> </table>
<submit-btn></submit-btn> <submit-btn></submit-btn>
</form> </form>

View File

@@ -39,12 +39,12 @@
<td> <td>
<div v-if="node.status.isActive"> <div v-if="node.status.isActive">
<span class="green">运行中</span> &nbsp; <span class="green">运行中</span> &nbsp;
<a href="" @click.prevent="stopNode()" v-if="!isStopping"><span>[通过SSH停止]</span></a> <!--<a href="" @click.prevent="stopNode()" v-if="!isStopping"><span>[通过SSH停止]</span></a>-->
<span v-if="isStopping">[停止中...]</span> <span v-if="isStopping">[停止中...]</span>
</div> </div>
<div v-else> <div v-else>
<span class="red">已断开</span> &nbsp; <span class="red">已断开</span> &nbsp;
<a href="" @click.prevent="startNode()" v-if="node.isInstalled && !isStarting"><span>[通过SSH启动]</span></a> <!--<a href="" @click.prevent="startNode()" v-if="node.isInstalled && !isStarting"><span>[通过SSH启动]</span></a>-->
<span v-if="node.isInstalled && isStarting">[启动中...]</span> <span v-if="node.isInstalled && isStarting">[启动中...]</span>
<a v-if="!node.isInstalled" :href="'/ns/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id" ><span>去安装&gt;</span></a> <a v-if="!node.isInstalled" :href="'/ns/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id" ><span>去安装&gt;</span></a>
</div> </div>
@@ -59,20 +59,10 @@
<td>内存用量</td> <td>内存用量</td>
<td>{{node.status.memUsageText}}</td> <td>{{node.status.memUsageText}}</td>
</tr> </tr>
<tr>
<td>连接数</td>
<td>{{node.status.connectionCount}}</td>
</tr>
<tr> <tr>
<td>负载</td> <td>负载</td>
<td>{{node.status.load1m}} &nbsp; {{node.status.load5m}} &nbsp; {{node.status.load15m}} &nbsp; <tip-icon content="三个数字分别代表1分钟、5分钟、15分钟平均负载"></tip-icon></td> <td>{{node.status.load1m}} &nbsp; {{node.status.load5m}} &nbsp; {{node.status.load15m}} &nbsp; <tip-icon content="三个数字分别代表1分钟、5分钟、15分钟平均负载"></tip-icon></td>
</tr> </tr>
<tr>
<td>缓存用量</td>
<td>
磁盘:{{node.status.cacheTotalDiskSize}} &nbsp; 内存:{{node.status.cacheTotalMemorySize}}
</td>
</tr>
<tr> <tr>
<td>版本</td> <td>版本</td>
<td>v{{node.status.buildVersion}} <td>v{{node.status.buildVersion}}
@@ -105,7 +95,7 @@
<td>是否已安装</td> <td>是否已安装</td>
<td> <td>
<span v-if="node.isInstalled" class="green">已安装</span> <span v-if="node.isInstalled" class="green">已安装</span>
<a v-else :href="'/ns/clusters/cluster/installNode?clusterId=' + clusterId + '&nodeId=' + nodeId" class="underline" title="点击进入安装界面"><span class="red">未安装</span></a> <a v-else :href="'/ns/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + nodeId" class="underline" title="点击进入安装界面"><span class="red">未安装</span></a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@@ -14,12 +14,14 @@
<td class="title">所属集群 *</td> <td class="title">所属集群 *</td>
<td> <td>
<ns-cluster-selector></ns-cluster-selector> <ns-cluster-selector></ns-cluster-selector>
<p class="comment">需要部署域名服务的集群。</p>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>所属用户</td> <td>所属用户</td>
<td> <td>
<ns-user-selector></ns-user-selector> <ns-user-selector></ns-user-selector>
<p class="comment">当前域名所属的平台用户。</p>
</td> </td>
</tr> </tr>
</table> </table>

View File

@@ -26,7 +26,7 @@
<tr> <tr>
<td>记录值</td> <td>记录值</td>
<td> <td>
<input type="text" name="value" maxlength="1024"/> <input type="text" name="value" maxlength="512"/>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -1,6 +1,7 @@
Tea.context(function () { Tea.context(function () {
this.createRecord = function () { this.createRecord = function () {
teaweb.popup("/ns/domains/records/createPopup?domainId=" + this.domain.id, { teaweb.popup("/ns/domains/records/createPopup?domainId=" + this.domain.id, {
height: "32em",
callback: function () { callback: function () {
teaweb.success("保存成功", function () { teaweb.success("保存成功", function () {
teaweb.reload() teaweb.reload()
@@ -11,6 +12,7 @@ Tea.context(function () {
this.updateRecord = function (recordId) { this.updateRecord = function (recordId) {
teaweb.popup("/ns/domains/records/updatePopup?recordId=" + recordId, { teaweb.popup("/ns/domains/records/updatePopup?recordId=" + recordId, {
height: "32em",
callback: function () { callback: function () {
teaweb.success("保存成功", function () { teaweb.success("保存成功", function () {
teaweb.reload() teaweb.reload()

View File

@@ -26,7 +26,7 @@
<tr> <tr>
<td>记录值</td> <td>记录值</td>
<td> <td>
<input type="text" name="value" maxlength="1024" v-model="record.value"/> <input type="text" name="value" maxlength="512" v-model="record.value"/>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -8,19 +8,21 @@
<tr> <tr>
<td>域名 *</td> <td>域名 *</td>
<td> <td>
<input type="text" name="name" maxlength="255" ref="focus" v-model="domain.name"/> {{domain.name}}
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="title">所属集群 *</td> <td class="title">所属集群 *</td>
<td> <td>
<ns-cluster-selector :v-cluster-id="domain.clusterId"></ns-cluster-selector> <ns-cluster-selector :v-cluster-id="domain.clusterId"></ns-cluster-selector>
<p class="comment">需要部署域名服务的集群。</p>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>所属用户</td> <td>所属用户</td>
<td> <td>
<ns-user-selector :v-user-id="domain.userId"></ns-user-selector> <ns-user-selector :v-user-id="domain.userId"></ns-user-selector>
<p class="comment">当前域名所属的平台用户。</p>
</td> </td>
</tr> </tr>
<tr> <tr>