mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-06 06:40:27 +08:00
优化自建DNS交互
This commit is contained in:
96
internal/web/actions/default/ns/domains/index.go
Normal file
96
internal/web/actions/default/ns/domains/index.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package domains
|
||||||
|
|
||||||
|
import (
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IndexAction) Init() {
|
||||||
|
this.FirstMenu("domain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IndexAction) RunGet(params struct {
|
||||||
|
ClusterId int64
|
||||||
|
UserId int64
|
||||||
|
Keyword string
|
||||||
|
}) {
|
||||||
|
if !teaconst.IsPlus {
|
||||||
|
this.RedirectURL("/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Data["clusterId"] = params.ClusterId
|
||||||
|
this.Data["userId"] = params.UserId
|
||||||
|
this.Data["keyword"] = params.Keyword
|
||||||
|
|
||||||
|
// 集群数量
|
||||||
|
countClustersResp, err := this.RPC().NSClusterRPC().CountAllEnabledNSClusters(this.AdminContext(), &pb.CountAllEnabledNSClustersRequest{})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Data["countClusters"] = countClustersResp.Count
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
countResp, err := this.RPC().NSDomainRPC().CountAllEnabledNSDomains(this.AdminContext(), &pb.CountAllEnabledNSDomainsRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
NsClusterId: params.ClusterId,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
page := this.NewPage(countResp.Count)
|
||||||
|
|
||||||
|
// 列表
|
||||||
|
domainsResp, err := this.RPC().NSDomainRPC().ListEnabledNSDomains(this.AdminContext(), &pb.ListEnabledNSDomainsRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
NsClusterId: params.ClusterId,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
domainMaps := []maps.Map{}
|
||||||
|
for _, domain := range domainsResp.NsDomains {
|
||||||
|
// 集群信息
|
||||||
|
var clusterMap maps.Map
|
||||||
|
if domain.NsCluster != nil {
|
||||||
|
clusterMap = maps.Map{
|
||||||
|
"id": domain.NsCluster.Id,
|
||||||
|
"name": domain.NsCluster.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
var userMap maps.Map
|
||||||
|
if domain.User != nil {
|
||||||
|
userMap = maps.Map{
|
||||||
|
"id": domain.User.Id,
|
||||||
|
"username": domain.User.Username,
|
||||||
|
"fullname": domain.User.Fullname,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
domainMaps = append(domainMaps, maps.Map{
|
||||||
|
"id": domain.Id,
|
||||||
|
"name": domain.Name,
|
||||||
|
"isOn": domain.IsOn,
|
||||||
|
"cluster": clusterMap,
|
||||||
|
"user": userMap,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["domains"] = domainMaps
|
||||||
|
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
package ns
|
package ns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexAction struct {
|
type IndexAction struct {
|
||||||
@@ -12,85 +15,113 @@ type IndexAction struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) Init() {
|
func (this *IndexAction) Init() {
|
||||||
this.FirstMenu("index")
|
this.Nav("", "", "dns")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) RunGet(params struct {
|
func (this *IndexAction) RunGet(params struct{}) {
|
||||||
ClusterId int64
|
resp, err := this.RPC().NSRPC().ComposeNSBoard(this.AdminContext(), &pb.ComposeNSBoardRequest{})
|
||||||
UserId int64
|
|
||||||
Keyword string
|
|
||||||
}) {
|
|
||||||
if !teaconst.IsPlus {
|
|
||||||
this.RedirectURL("/")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Data["clusterId"] = params.ClusterId
|
|
||||||
this.Data["userId"] = params.UserId
|
|
||||||
this.Data["keyword"] = params.Keyword
|
|
||||||
|
|
||||||
// 集群数量
|
|
||||||
countClustersResp, err := this.RPC().NSClusterRPC().CountAllEnabledNSClusters(this.AdminContext(), &pb.CountAllEnabledNSClustersRequest{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.Data["countClusters"] = countClustersResp.Count
|
this.Data["board"] = maps.Map{
|
||||||
|
"countDomains": resp.CountNSDomains,
|
||||||
// 分页
|
"countRecords": resp.CountNSRecords,
|
||||||
countResp, err := this.RPC().NSDomainRPC().CountAllEnabledNSDomains(this.AdminContext(), &pb.CountAllEnabledNSDomainsRequest{
|
"countClusters": resp.CountNSClusters,
|
||||||
UserId: params.UserId,
|
"countNodes": resp.CountNSNodes,
|
||||||
NsClusterId: params.ClusterId,
|
"countOfflineNodes": resp.CountOfflineNSNodes,
|
||||||
Keyword: params.Keyword,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
page := this.NewPage(countResp.Count)
|
|
||||||
|
|
||||||
// 列表
|
// 流量排行
|
||||||
domainsResp, err := this.RPC().NSDomainRPC().ListEnabledNSDomains(this.AdminContext(), &pb.ListEnabledNSDomainsRequest{
|
{
|
||||||
UserId: params.UserId,
|
var statMaps = []maps.Map{}
|
||||||
NsClusterId: params.ClusterId,
|
for _, stat := range resp.HourlyTrafficStats {
|
||||||
Keyword: params.Keyword,
|
statMaps = append(statMaps, maps.Map{
|
||||||
Offset: page.Offset,
|
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
|
||||||
Size: page.Size,
|
"hour": stat.Hour[8:],
|
||||||
})
|
"countRequests": stat.CountRequests,
|
||||||
if err != nil {
|
"bytes": stat.Bytes,
|
||||||
this.ErrorPage(err)
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
domainMaps := []maps.Map{}
|
|
||||||
for _, domain := range domainsResp.NsDomains {
|
|
||||||
// 集群信息
|
|
||||||
var clusterMap maps.Map
|
|
||||||
if domain.NsCluster != nil {
|
|
||||||
clusterMap = maps.Map{
|
|
||||||
"id": domain.NsCluster.Id,
|
|
||||||
"name": domain.NsCluster.Name,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.Data["hourlyStats"] = statMaps
|
||||||
// 用户信息
|
}
|
||||||
var userMap maps.Map
|
|
||||||
if domain.User != nil {
|
{
|
||||||
userMap = maps.Map{
|
var statMaps = []maps.Map{}
|
||||||
"id": domain.User.Id,
|
for _, stat := range resp.DailyTrafficStats {
|
||||||
"username": domain.User.Username,
|
statMaps = append(statMaps, maps.Map{
|
||||||
"fullname": domain.User.Fullname,
|
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
|
||||||
}
|
"countRequests": stat.CountRequests,
|
||||||
}
|
"bytes": stat.Bytes,
|
||||||
|
})
|
||||||
domainMaps = append(domainMaps, maps.Map{
|
}
|
||||||
"id": domain.Id,
|
this.Data["dailyStats"] = statMaps
|
||||||
"name": domain.Name,
|
}
|
||||||
"isOn": domain.IsOn,
|
|
||||||
"cluster": clusterMap,
|
// 域名排行
|
||||||
"user": userMap,
|
{
|
||||||
})
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.TopNSDomainStats {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"domainId": stat.NsDomainId,
|
||||||
|
"domainName": stat.NsDomainName,
|
||||||
|
"countRequests": stat.CountRequests,
|
||||||
|
"bytes": stat.Bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["topDomainStats"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点排行
|
||||||
|
{
|
||||||
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.TopNSNodeStats {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"clusterId": stat.NsClusterId,
|
||||||
|
"nodeId": stat.NsNodeId,
|
||||||
|
"nodeName": stat.NsNodeName,
|
||||||
|
"countRequests": stat.CountRequests,
|
||||||
|
"bytes": stat.Bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["topNodeStats"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
{
|
||||||
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.CpuNodeValues {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
|
||||||
|
"value": types.Float32(string(stat.ValueJSON)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["cpuValues"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory
|
||||||
|
{
|
||||||
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.MemoryNodeValues {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
|
||||||
|
"value": types.Float32(string(stat.ValueJSON)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["memoryValues"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load
|
||||||
|
{
|
||||||
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.LoadNodeValues {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
|
||||||
|
"value": types.Float32(string(stat.ValueJSON)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["loadValues"] = statMaps
|
||||||
}
|
}
|
||||||
this.Data["domains"] = domainMaps
|
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ func init() {
|
|||||||
|
|
||||||
// 域名相关
|
// 域名相关
|
||||||
Prefix("/ns/domains").
|
Prefix("/ns/domains").
|
||||||
|
Data("teaSubMenu", "domain").
|
||||||
|
Get("", new(domains.IndexAction)).
|
||||||
GetPost("/create", new(domains.CreateAction)).
|
GetPost("/create", new(domains.CreateAction)).
|
||||||
Post("/delete", new(domains.DeleteAction)).
|
Post("/delete", new(domains.DeleteAction)).
|
||||||
Get("/domain", new(domains.DomainAction)).
|
Get("/domain", new(domains.DomainAction)).
|
||||||
@@ -28,6 +30,7 @@ func init() {
|
|||||||
|
|
||||||
// 域名密钥
|
// 域名密钥
|
||||||
Prefix("/ns/domains/keys").
|
Prefix("/ns/domains/keys").
|
||||||
|
Data("teaSubMenu", "domain").
|
||||||
Get("", new(keys.IndexAction)).
|
Get("", new(keys.IndexAction)).
|
||||||
GetPost("/createPopup", new(keys.CreatePopupAction)).
|
GetPost("/createPopup", new(keys.CreatePopupAction)).
|
||||||
GetPost("/updatePopup", new(keys.UpdatePopupAction)).
|
GetPost("/updatePopup", new(keys.UpdatePopupAction)).
|
||||||
|
|||||||
@@ -261,13 +261,17 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "ns",
|
"code": "ns",
|
||||||
"module": configloaders.AdminModuleCodeNS,
|
"module": configloaders.AdminModuleCodeNS,
|
||||||
"name": "自建DNS",
|
"name": "自建DNS",
|
||||||
"subtitle": "域名列表",
|
"icon": "cubes",
|
||||||
"icon": "cubes",
|
"isOn": teaconst.IsPlus,
|
||||||
"isOn": teaconst.IsPlus,
|
|
||||||
"subItems": []maps.Map{
|
"subItems": []maps.Map{
|
||||||
|
{
|
||||||
|
"name": "域名管理",
|
||||||
|
"url": "/ns/domains",
|
||||||
|
"code": "domain",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "集群管理",
|
"name": "集群管理",
|
||||||
"url": "/ns/clusters",
|
"url": "/ns/clusters",
|
||||||
|
|||||||
20
web/public/js/world.js
Normal file
20
web/public/js/world.js
Normal file
File diff suppressed because one or more lines are too long
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
<div class="ui four columns grid">
|
<div class="ui four columns grid">
|
||||||
<div class="ui column">
|
<div class="ui column">
|
||||||
<h4>域名<link-icon href="/ns"></link-icon></h4>
|
<h4>域名<link-icon href="/ns/domains"></link-icon></h4>
|
||||||
<div class="value"><span>{{board.countDomains}}</span>个</div>
|
<div class="value"><span>{{board.countDomains}}</span>个</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui column">
|
<div class="ui column">
|
||||||
<h4>记录<link-icon href="/ns"></link-icon></h4>
|
<h4>记录<link-icon href="/ns/domains"></link-icon></h4>
|
||||||
<div class="value"><span>{{board.countRecords}}</span>个</div>
|
<div class="value"><span>{{board.countRecords}}</span>个</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui column">
|
<div class="ui column">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<first-menu>
|
<first-menu>
|
||||||
<menu-item href="/ns">所有域名</menu-item>
|
<menu-item href="/ns/domains">所有域名</menu-item>
|
||||||
<span class="item disabled">|</span>
|
<span class="item disabled">|</span>
|
||||||
<menu-item :href="'/ns/domains/domain?domainId=' + domain.id" code="index">详情<span class="grey small">({{domain.name}})</span></menu-item>
|
<menu-item :href="'/ns/domains/domain?domainId=' + domain.id" code="index">详情<span class="grey small">({{domain.name}})</span></menu-item>
|
||||||
<menu-item :href="'/ns/domains/records?domainId=' + domain.id" code="record">记录({{domain.countRecords}})</menu-item>
|
<menu-item :href="'/ns/domains/records?domainId=' + domain.id" code="record">记录({{domain.countRecords}})</menu-item>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<first-menu>
|
<first-menu>
|
||||||
<menu-item href="/ns" code="index">域名列表</menu-item>
|
<menu-item href="/ns/domains" code="index">域名列表</menu-item>
|
||||||
<menu-item href="/ns/domains/create" code="create">添加域名</menu-item>
|
<menu-item href="/ns/domains/create" code="create">添加域名</menu-item>
|
||||||
</first-menu>
|
</first-menu>
|
||||||
<div class="margin"></div>
|
<div class="margin"></div>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
{$layout}
|
{$layout}
|
||||||
{$template "../menu"}
|
{$template "menu"}
|
||||||
|
|
||||||
|
|
||||||
<div v-if="countClusters == 0">
|
<div v-if="countClusters == 0">
|
||||||
<not-found-box>
|
<not-found-box>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
Tea.context(function () {
|
Tea.context(function () {
|
||||||
this.success = NotifySuccess("保存成功", "/ns")
|
this.success = NotifySuccess("保存成功", "/ns/domains")
|
||||||
})
|
})
|
||||||
61
web/views/@default/ns/domains/index.html
Normal file
61
web/views/@default/ns/domains/index.html
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{$layout}
|
||||||
|
{$template "menu"}
|
||||||
|
|
||||||
|
<div v-if="countClusters == 0">
|
||||||
|
<not-found-box>
|
||||||
|
暂时还没有集群,请先 <a href="/ns/clusters/create">创建集群</a>。
|
||||||
|
</not-found-box>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="countClusters > 0">
|
||||||
|
<form class="ui form" method="get" action="/ns/domains">
|
||||||
|
<div class="ui fields inline">
|
||||||
|
<div class="ui field">
|
||||||
|
<ns-cluster-selector :v-cluster-id="clusterId"></ns-cluster-selector>
|
||||||
|
</div>
|
||||||
|
<div class="ui field">
|
||||||
|
<ns-user-selector :v-user-id="userId"></ns-user-selector>
|
||||||
|
</div>
|
||||||
|
<div class="ui field">
|
||||||
|
<input type="text" name="keyword" v-model="keyword" placeholder="域名..."/>
|
||||||
|
</div>
|
||||||
|
<div class="ui field">
|
||||||
|
<button class="ui button" type="submit">搜索</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div v-if="domains.length == 0">
|
||||||
|
<div class="margin"></div>
|
||||||
|
<p class="comment">暂时还没有域名。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 域名列表 -->
|
||||||
|
<table class="ui table selectable celled" v-if="domains.length > 0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>域名</th>
|
||||||
|
<th>集群</th>
|
||||||
|
<th>用户</th>
|
||||||
|
<th class="two wide">状态</th>
|
||||||
|
<th class="two op">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr v-for="domain in domains">
|
||||||
|
<td><keyword :v-word="keyword">{{domain.name}}</keyword></td>
|
||||||
|
<td>
|
||||||
|
{{domain.cluster.name}}<link-icon :href="'/ns/clusters/cluster?clusterId=' + domain.cluster.id"></link-icon>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="domain.user != null">
|
||||||
|
{{domain.user.fullname}} ({{domain.user.username}})
|
||||||
|
</span>
|
||||||
|
<span v-else class="disabled">-</span>
|
||||||
|
</td>
|
||||||
|
<td><label-on :v-is-on="domain.isOn"></label-on></td>
|
||||||
|
<td>
|
||||||
|
<a :href="'/ns/domains/domain?domainId=' + domain.id">详情</a> <a href="" @click.prevent="deleteDomain(domain.id)">删除</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
14
web/views/@default/ns/domains/index.js
Normal file
14
web/views/@default/ns/domains/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
Tea.context(function () {
|
||||||
|
this.deleteDomain = function (domainId) {
|
||||||
|
let that = this
|
||||||
|
teaweb.confirm("确定要删除此域名吗?", function () {
|
||||||
|
that.$post("/ns/domains/delete")
|
||||||
|
.params({
|
||||||
|
domainId: domainId
|
||||||
|
})
|
||||||
|
.success(function () {
|
||||||
|
teaweb.reload()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
33
web/views/@default/ns/index.css
Normal file
33
web/views/@default/ns/index.css
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
.ui.message .icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1.8em;
|
||||||
|
}
|
||||||
|
.grid {
|
||||||
|
margin-top: 2em !important;
|
||||||
|
margin-left: 2em !important;
|
||||||
|
}
|
||||||
|
.grid .column {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
border-right: 1px #eee solid;
|
||||||
|
}
|
||||||
|
.grid .column div.value {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
.grid .column div.value span {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
|
.grid .column.no-border {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
.grid h4 a {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.grid .column:hover a {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.chart-box {
|
||||||
|
height: 20em;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=index.css.map */
|
||||||
1
web/views/@default/ns/index.css.map
Normal file
1
web/views/@default/ns/index.css.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,GAAG,QACF;EACC,kBAAA;EACA,UAAA;EACA,UAAA;;AAIF;EACC,0BAAA;EACA,2BAAA;;AAFD,KAIC;EACC,kBAAA;EACA,4BAAA;;AANF,KAIC,QAIC,IAAG;EACF,iBAAA;;AATH,KAIC,QAIC,IAAG,MAGF;EACC,cAAA;EACA,mBAAA;;AAbJ,KAkBC,QAAO;EACN,eAAA;;AAnBF,KAsBC,GACC;EACC,aAAA;;AAxBH,KA4BC,QAAO,MACN;EACC,eAAA;;AAKH;EACC,YAAA","file":"index.css"}
|
||||||
@@ -1,61 +1,60 @@
|
|||||||
{$layout}
|
{$layout}
|
||||||
{$template "menu"}
|
{$template "/echarts"}
|
||||||
|
|
||||||
<div v-if="countClusters == 0">
|
<div class="ui four columns grid">
|
||||||
<not-found-box>
|
<div class="ui column">
|
||||||
暂时还没有集群,请先 <a href="/ns/clusters/create">创建集群</a>。
|
<h4>域名<link-icon href="/ns/domains"></link-icon></h4>
|
||||||
</not-found-box>
|
<div class="value"><span>{{board.countDomains}}</span>个</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ui column">
|
||||||
<div v-if="countClusters > 0">
|
<h4>记录<link-icon href="/ns/domains"></link-icon></h4>
|
||||||
<form class="ui form" method="get" action="/ns">
|
<div class="value"><span>{{board.countRecords}}</span>个</div>
|
||||||
<div class="ui fields inline">
|
</div>
|
||||||
<div class="ui field">
|
<div class="ui column">
|
||||||
<ns-cluster-selector :v-cluster-id="clusterId"></ns-cluster-selector>
|
<h4>集群<link-icon href="/ns/clusters"></link-icon></h4>
|
||||||
</div>
|
<div class="value"><span>{{board.countClusters}}</span>个</div>
|
||||||
<div class="ui field">
|
</div>
|
||||||
<ns-user-selector :v-user-id="userId"></ns-user-selector>
|
<div class="ui column no-border">
|
||||||
</div>
|
<h4>节点<link-icon href="/ns/clusters"></link-icon></h4>
|
||||||
<div class="ui field">
|
<div class="value"><span>{{board.countNodes}}</span>
|
||||||
<input type="text" name="keyword" v-model="keyword" placeholder="域名..."/>
|
<span v-if="board.countOfflineNodes > 0" class="red" style="font-size: 1em">{{board.countOfflineNodes}}离线</span>
|
||||||
</div>
|
<span v-else style="font-size: 1em">个</span>
|
||||||
<div class="ui field">
|
</div>
|
||||||
<button class="ui button" type="submit">搜索</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div v-if="domains.length == 0">
|
|
||||||
<div class="margin"></div>
|
|
||||||
<p class="comment">暂时还没有域名。</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 域名列表 -->
|
|
||||||
<table class="ui table selectable celled" v-if="domains.length > 0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>域名</th>
|
|
||||||
<th>集群</th>
|
|
||||||
<th>用户</th>
|
|
||||||
<th class="two wide">状态</th>
|
|
||||||
<th class="two op">操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tr v-for="domain in domains">
|
|
||||||
<td><keyword :v-word="keyword">{{domain.name}}</keyword></td>
|
|
||||||
<td>
|
|
||||||
{{domain.cluster.name}}<link-icon :href="'/ns/clusters/cluster?clusterId=' + domain.cluster.id"></link-icon>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span v-if="domain.user != null">
|
|
||||||
{{domain.user.fullname}} ({{domain.user.username}})
|
|
||||||
</span>
|
|
||||||
<span v-else class="disabled">-</span>
|
|
||||||
</td>
|
|
||||||
<td><label-on :v-is-on="domain.isOn"></label-on></td>
|
|
||||||
<td>
|
|
||||||
<a :href="'/ns/domains/domain?domainId=' + domain.id">详情</a> <a href="" @click.prevent="deleteDomain(domain.id)">删除</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 流量统计 -->
|
||||||
|
<div class="ui menu tabular">
|
||||||
|
<a href="" class="item" :class="{active: trafficTab == 'hourly'}" @click.prevent="selectTrafficTab('hourly')">24小时流量趋势</a>
|
||||||
|
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天流量趋势</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 按小时统计流量 -->
|
||||||
|
<div class="chart-box" id="hourly-traffic-chart" v-show="trafficTab == 'hourly'"></div>
|
||||||
|
|
||||||
|
<!-- 按日统计流量 -->
|
||||||
|
<div class="chart-box" id="daily-traffic-chart" v-show="trafficTab == 'daily'"></div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
<!-- 域名排行 -->
|
||||||
|
<h4>域名访问排行 <span>(24小时)</span></h4>
|
||||||
|
<div class="chart-box" id="top-domains-chart"></div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
<!-- 节点排行 -->
|
||||||
|
<h4>节点访问排行 <span>(24小时)</span></h4>
|
||||||
|
<div class="chart-box" id="top-nodes-chart"></div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
<!-- 系统信息 -->
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui menu tabular">
|
||||||
|
<a href="" class="item" :class="{active: nodeStatusTab == 'cpu'}" @click.prevent="selectNodeStatusTab('cpu')">DNS节点CPU</a>
|
||||||
|
<a href="" class="item" :class="{active: nodeStatusTab == 'memory'}" @click.prevent="selectNodeStatusTab('memory')">DNS节点内存</a>
|
||||||
|
<a href="" class="item" :class="{active: nodeStatusTab == 'load'}" @click.prevent="selectNodeStatusTab('load')">DNS节点负载</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-box" id="cpu-chart" v-show="nodeStatusTab == 'cpu'"></div>
|
||||||
|
<div class="chart-box" id="memory-chart" v-show="nodeStatusTab == 'memory'"></div>
|
||||||
|
<div class="chart-box" id="load-chart" v-show="nodeStatusTab == 'load'"></div>
|
||||||
@@ -1,14 +1,246 @@
|
|||||||
Tea.context(function () {
|
Tea.context(function () {
|
||||||
this.deleteDomain = function (domainId) {
|
this.$delay(function () {
|
||||||
|
this.reloadHourlyTrafficChart()
|
||||||
|
this.reloadTopDomainsChart()
|
||||||
|
this.reloadTopNodesChart()
|
||||||
|
this.reloadCPUChart()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流量统计
|
||||||
|
*/
|
||||||
|
this.trafficTab = "hourly"
|
||||||
|
|
||||||
|
this.selectTrafficTab = function (tab) {
|
||||||
|
this.trafficTab = tab
|
||||||
|
if (tab == "hourly") {
|
||||||
|
this.$delay(function () {
|
||||||
|
this.reloadHourlyTrafficChart()
|
||||||
|
})
|
||||||
|
} else if (tab == "daily") {
|
||||||
|
this.$delay(function () {
|
||||||
|
this.reloadDailyTrafficChart()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadHourlyTrafficChart = function () {
|
||||||
|
let stats = this.hourlyStats
|
||||||
|
this.reloadTrafficChart("hourly-traffic-chart", "流量统计", stats, function (args) {
|
||||||
|
return stats[args.dataIndex].day + " " + stats[args.dataIndex].hour + "时 流量: " + teaweb.formatBytes(stats[args.dataIndex].bytes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadDailyTrafficChart = function () {
|
||||||
|
let stats = this.dailyStats
|
||||||
|
this.reloadTrafficChart("daily-traffic-chart", "流量统计", stats, function (args) {
|
||||||
|
return stats[args.dataIndex].day + " 流量: " + teaweb.formatBytes(stats[args.dataIndex].bytes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadTrafficChart = function (chartId, name, stats, tooltipFunc) {
|
||||||
|
let chartBox = document.getElementById(chartId)
|
||||||
|
if (chartBox == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let axis = teaweb.bytesAxis(stats, function (v) {
|
||||||
|
return v.bytes
|
||||||
|
})
|
||||||
|
|
||||||
|
let chart = teaweb.initChart(chartBox)
|
||||||
|
let option = {
|
||||||
|
xAxis: {
|
||||||
|
data: stats.map(function (v) {
|
||||||
|
if (v.hour != null) {
|
||||||
|
return v.hour
|
||||||
|
}
|
||||||
|
return v.day
|
||||||
|
})
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisLabel: {
|
||||||
|
formatter: function (value) {
|
||||||
|
return value + axis.unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: "item",
|
||||||
|
formatter: tooltipFunc
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 50,
|
||||||
|
top: 40,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "流量",
|
||||||
|
type: "line",
|
||||||
|
data: stats.map(function (v) {
|
||||||
|
return v.bytes / axis.divider
|
||||||
|
}),
|
||||||
|
itemStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
smooth: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
animation: true
|
||||||
|
}
|
||||||
|
chart.setOption(option)
|
||||||
|
chart.resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 域名排行
|
||||||
|
this.reloadTopDomainsChart = function () {
|
||||||
let that = this
|
let that = this
|
||||||
teaweb.confirm("确定要删除此域名吗?", function () {
|
let axis = teaweb.countAxis(this.topDomainStats, function (v) {
|
||||||
that.$post("/ns/domains/delete")
|
return v.countRequests
|
||||||
.params({
|
})
|
||||||
domainId: domainId
|
teaweb.renderBarChart({
|
||||||
})
|
id: "top-domains-chart",
|
||||||
.success(function () {
|
name: "域名",
|
||||||
teaweb.reload()
|
values: this.topDomainStats,
|
||||||
})
|
x: function (v) {
|
||||||
|
return v.domainName
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].domainName + "<br/>请求数:" + " " + teaweb.formatNumber(stats[args.dataIndex].countRequests) + "<br/>流量:" + teaweb.formatBytes(stats[args.dataIndex].bytes)
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.countRequests / axis.divider;
|
||||||
|
},
|
||||||
|
axis: axis,
|
||||||
|
click: function (args, stats) {
|
||||||
|
window.location = "/ns/domains/domain?domainId=" + stats[args.dataIndex].domainId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点排行
|
||||||
|
this.reloadTopNodesChart = function () {
|
||||||
|
let that = this
|
||||||
|
let axis = teaweb.countAxis(this.topNodeStats, function (v) {
|
||||||
|
return v.countRequests
|
||||||
|
})
|
||||||
|
teaweb.renderBarChart({
|
||||||
|
id: "top-nodes-chart",
|
||||||
|
name: "节点",
|
||||||
|
values: this.topNodeStats,
|
||||||
|
x: function (v) {
|
||||||
|
return v.nodeName
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].nodeName + "<br/>请求数:" + " " + teaweb.formatNumber(stats[args.dataIndex].countRequests) + "<br/>流量:" + teaweb.formatBytes(stats[args.dataIndex].bytes)
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.countRequests / axis.divider;
|
||||||
|
},
|
||||||
|
axis: axis,
|
||||||
|
click: function (args, stats) {
|
||||||
|
window.location = "/ns/clusters/cluster/node?nodeId=" + stats[args.dataIndex].nodeId + "&clusterId=" + stats[args.dataIndex].clusterId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统信息
|
||||||
|
*/
|
||||||
|
this.nodeStatusTab = "cpu"
|
||||||
|
|
||||||
|
this.selectNodeStatusTab = function (tab) {
|
||||||
|
this.nodeStatusTab = tab
|
||||||
|
this.$delay(function () {
|
||||||
|
switch (tab) {
|
||||||
|
case "cpu":
|
||||||
|
this.reloadCPUChart()
|
||||||
|
break
|
||||||
|
case "memory":
|
||||||
|
this.reloadMemoryChart()
|
||||||
|
break
|
||||||
|
case "load":
|
||||||
|
this.reloadLoadChart()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadCPUChart = function () {
|
||||||
|
let axis = {unit: "%", divider: 1}
|
||||||
|
teaweb.renderLineChart({
|
||||||
|
id: "cpu-chart",
|
||||||
|
name: "CPU",
|
||||||
|
values: this.cpuValues,
|
||||||
|
x: function (v) {
|
||||||
|
return v.time
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].time + ":" + (Math.ceil(stats[args.dataIndex].value * 100 * 100) / 100) + "%"
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.value * 100;
|
||||||
|
},
|
||||||
|
axis: axis,
|
||||||
|
max: 100
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadMemoryChart = function () {
|
||||||
|
let axis = {unit: "%", divider: 1}
|
||||||
|
teaweb.renderLineChart({
|
||||||
|
id: "memory-chart",
|
||||||
|
name: "内存",
|
||||||
|
values: this.memoryValues,
|
||||||
|
x: function (v) {
|
||||||
|
return v.time
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].time + ":" + (Math.ceil(stats[args.dataIndex].value * 100 * 100) / 100) + "%"
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.value * 100;
|
||||||
|
},
|
||||||
|
axis: axis,
|
||||||
|
max: 100
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadLoadChart = function () {
|
||||||
|
let axis = {unit: "", divider: 1}
|
||||||
|
let max = this.loadValues.$map(function (k, v) {
|
||||||
|
return v.value
|
||||||
|
}).$max()
|
||||||
|
if (max < 10) {
|
||||||
|
max = 10
|
||||||
|
} else if (max < 20) {
|
||||||
|
max = 20
|
||||||
|
} else if (max < 100) {
|
||||||
|
max = 100
|
||||||
|
} else {
|
||||||
|
max = null
|
||||||
|
}
|
||||||
|
teaweb.renderLineChart({
|
||||||
|
id: "load-chart",
|
||||||
|
name: "负载",
|
||||||
|
values: this.loadValues,
|
||||||
|
x: function (v) {
|
||||||
|
return v.time
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].time + ":" + (Math.ceil(stats[args.dataIndex].value * 100) / 100)
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.value;
|
||||||
|
},
|
||||||
|
axis: axis,
|
||||||
|
max: max
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
46
web/views/@default/ns/index.less
Normal file
46
web/views/@default/ns/index.less
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
.ui.message {
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
margin-top: 2em !important;
|
||||||
|
margin-left: 2em !important;
|
||||||
|
|
||||||
|
.column {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
border-right: 1px #eee solid;
|
||||||
|
|
||||||
|
div.value {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column.no-border {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
a {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column:hover {
|
||||||
|
a {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-box {
|
||||||
|
height: 20em;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user