Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
<second-menu>
<a :href="'/clusters/cluster/nodes?clusterId=' + clusterId" class="item">{{currentClusterName}}</a>
<raquo-item></raquo-item>
<a :href="'/clusters/cluster/nodes?clusterId=' + clusterId + '&groupId=' + node.group.id" v-if="node.group != null" class="item">{{node.group.name}}</a>
<raquo-item v-if="node.group != null"></raquo-item>
<menu-item :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">
{{node.name}}
<sup v-if="node.level > 1">&nbsp; <span class="blue">L{{node.level}}</span></sup>
</menu-item>
<raquo-item></raquo-item>
<menu-item :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id" code="node" v-if="!teaIsPlus">节点详情</menu-item>
<menu-item :href="'/clusters/cluster/node/boards?clusterId=' + clusterId + '&nodeId=' + node.id" code="board" v-if="teaIsPlus">节点看板</menu-item>
<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/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>
</second-menu>

View File

@@ -0,0 +1,16 @@
.message.loading {
padding: 1.5em;
}
.chart-box {
height: 14em;
}
h4 span {
font-size: 0.8em;
color: grey;
}
.tabular.menu a span {
font-size: 0.8em;
font-weight: normal;
color: grey;
}
/*# sourceMappingURL=index.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,QAAQ;EACP,cAAA;;AAGD;EACC,YAAA;;AAGD,EACC;EACC,gBAAA;EACA,WAAA;;AAIF,QAAQ,KACP,EACC;EACC,gBAAA;EACA,mBAAA;EACA,WAAA","file":"index.css"}

View File

@@ -0,0 +1,168 @@
{$layout}
{$template "../node_menu"}
{$template "/echarts"}
<!-- 加载中 -->
<div>
<div class="ui message loading" v-if="isLoading">
<div class="ui active inline loader small"></div> &nbsp; 数据加载中...
</div>
</div>
<!-- 概况 -->
<columns-grid v-if="!isLoading">
<div class="ui column">
<h4>在线状态</h4>
<div class="value">
<span class="green" v-if="board.isActive">在线</span>
<span class="red" v-else-if="node.isUp">离线</span>
<span class="red" v-else style="font-size: 1.5em" title="因健康检查失败而离线">健康离线</span>
<a href="" v-if="node.isOn && !node.isUp" @click.prevent="upNode(node.id)">[上线]</a>
</div>
</div>
<div class="ui column">
<h4>当前下行带宽</h4>
<div class="value"><span>{{board.trafficOutBytes[0]}}</span>{{board.trafficOutBytes[1]}}</div>
</div>
<div class="ui column">
<h4>当前上行带宽</h4>
<div class="value"><span>{{board.trafficInBytes[0]}}</span>{{board.trafficInBytes[1]}}</div>
</div>
<div class="ui column with-border">
<h4>当前连接数</h4>
<div class="value"><span>{{board.countConnections}}</span>/分钟</div>
</div>
<div class="ui column">
<h4>当前访问量</h4>
<div class="value"><span>{{board.countRequests}}</span>/s</div>
</div>
<div class="ui column">
<h4>当前攻击访问量</h4>
<div class="value"><span :class="{red: board.countAttackRequests != '0'}">{{board.countAttackRequests}}</span>/分钟</div>
</div>
<div class="ui column">
<h4>缓存硬盘用量</h4>
<div class="value"><span>{{board.cacheDiskSize}}</span><span class="small" :class="{grey: !cacheDirAvailWarning && !cacheDirOverCapacity, red: cacheDirAvailWarning || cacheDirOverCapacity}" style="font-size: 0.9em" v-if="cacheDirAvail.length > 0"><span style="font-size: 0.9em" v-if="!cacheDirOverCapacity">剩余{{cacheDirAvail}}</span><span v-if="cacheDirOverCapacity" style="font-size: 0.9em">超出用量限制</span></span></div>
</div>
<div class="ui column">
<h4>硬盘预估写入最大速度</h4>
<div class="value"><span v-if="diskWritingSpeedMB == 0">-</span><span v-if="diskWritingSpeedMB > 0">&gt; {{diskWritingSpeedMB}}</span>
<span class="small" v-if="diskWritingSpeedMB > 1000" style="font-size: 0.9em">MiB/s 极快</span>
<span class="small" v-else-if="diskWritingSpeedMB > 150" style="font-size: 0.9em">MiB/s 快</span>
<span class="small" v-else-if="diskWritingSpeedMB > 0" style="font-size: 0.9em">MiB/s 一般</span>
</div>
</div>
<div class="ui column with-border">
<h4>内存缓存用量</h4>
<div class="value"><span>{{board.cacheMemorySize}}</span></div>
</div>
<div class="ui column">
<h4>CPU</h4>
<div class="value"><span :class="{red: board.cpuUsage > 80}">{{board.cpuUsage}}</span>%</div>
</div>
<div class="ui column">
<h4>内存</h4>
<div class="value"><span :class="{red: board.memoryUsage > 80}">{{board.memoryUsage}}</span>%</div>
</div>
<div class="ui column">
<h4>总内存</h4>
<div class="value"><span>{{board.memoryTotalSize}}</span>G</div>
</div>
<div class="ui column with-border">
<h4>负载</h4>
<div class="value"><span :class="{red: board.load > 20}">{{board.load}}</span>/分钟</div>
</div>
<div class="ui column">
<h4>当月流量</h4>
<div class="value"><span>{{monthlyTrafficFormat[0]}}</span>{{monthlyTrafficFormat[1]}}</div>
</div>
<div class="ui column">
<h4>昨日流量</h4>
<div class="value"><span>{{yesterdayTrafficFormat[0]}}</span>{{yesterdayTrafficFormat[1]}}</div>
</div>
<div class="ui column with-border">
<h4>今日流量</h4>
<div class="value"><span>{{todayTrafficFormat[0]}}</span>{{todayTrafficFormat[1]}}</div>
</div>
</columns-grid>
<chart-columns-grid>
<div class="ui column">
<div class="ui menu text blue" v-show="!isLoading">
<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="ui divider"></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>
<div class="ui column">
<div class="ui menu text blue" v-show="!isLoading">
<a href="" class="item" :class="{active: requestsTab == 'hourly'}" @click.prevent="selectRequestsTab('hourly')">24小时访问量趋势</a>
<a href="" class="item" :class="{active: requestsTab == 'daily'}" @click.prevent="selectRequestsTab('daily')">15天访问量趋势</a>
</div>
<div class="ui divider"></div>
<!-- 按小时统计访问量 -->
<div class="chart-box" id="hourly-requests-chart" v-show="requestsTab == 'hourly'"></div>
<!-- 按日统计访问量 -->
<div class="chart-box" id="daily-requests-chart" v-show="requestsTab == 'daily'"></div>
</div>
<div class="ui column">
<!-- 域名排行 -->
<h4 v-show="!isLoading">域名访问排行 <span>24小时</span></h4>
<div class="ui divider"></div>
<div class="chart-box" id="top-domains-chart"><loading-message>数据加载中...</loading-message></div>
</div>
<div class="ui column">
<!-- 网络数据包 -->
<div class="ui menu text blue" v-show="!isLoading">
<a href="" class="item" :class="{active: networkPacketsTab == 'tcp'}" @click.prevent="selectNetworkPacketsTab('tcp')">TCP数据包<span>pps</span></a>
<a href="" class="item" :class="{active: networkPacketsTab == 'udp'}" @click.prevent="selectNetworkPacketsTab('udp')">UDP数据包<span>pps</span></a>
<a href="" class="item" :class="{active: networkPacketsTab == 'icmp'}" @click.prevent="selectNetworkPacketsTab('icmp')">ICMP数据包<span>pps</span></a>
</div>
<div class="ui divider"></div>
<div class="chart-box" id="network-packets-chart" v-show="networkPacketsValues.length > 0"><loading-message>数据加载中...</loading-message></div>
<div class="chart-box" v-if="!isLoading && networkPacketsValues.length == 0">尚未产生数据或未启用相关功能(集群设置 -- 网络安全)或节点服务器性能不足以支持此功能。</div>
</div>
<div class="ui column">
<!-- CPU、内存、负载 -->
<div class="ui menu text blue" v-show="!isLoading">
<a href="" class="item" :class="{active: nodeStatusTab == 'cpu'}" @click.prevent="selectNodeStatusTab('cpu')">节点CPU</a>
<a href="" class="item" :class="{active: nodeStatusTab == 'memory'}" @click.prevent="selectNodeStatusTab('memory')">节点内存</a>
<a href="" class="item" :class="{active: nodeStatusTab == 'load'}" @click.prevent="selectNodeStatusTab('load')">节点负载</a>
</div>
<div class="ui divider"></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>
</div>
<div class="ui column">
<!-- 缓存 -->
<h4 v-show="!isLoading">缓存目录用量<span v-if="cacheDirUsed.length > 0">(使用:{{cacheDirUsed}}/总量:{{cacheDirTotal}}/剩余:{{cacheDirAvail}}</span></h4>
<div class="ui divider"></div>
<div class="chart-box" id="cache-dirs-chart"></div>
</div>
<!-- 指标 -->
<metric-chart v-for="chart in metricCharts"
:key="chart.id"
:v-chart="chart.chart"
:v-stats="chart.stats"
:v-item="chart.item"
:v-column="true">
</metric-chart>
</chart-columns-grid>

View File

@@ -0,0 +1,594 @@
Tea.context(function () {
this.isLoading = true
this.board = {}
this.metricCharts = []
this.monthlyTrafficFormat = ["0", "B"]
this.todayTrafficFormat = ["0", "B"]
this.yesterdayTrafficFormat = ["0", "B"]
this.networkPacketsValues = []
this.upNode = function (nodeId) {
teaweb.confirm("确定要手动上线此节点吗?", function () {
this.$post("/clusters/cluster/node/up")
.params({
nodeId: nodeId
})
.refresh()
})
}
this.formatCount = function (count) {
if (count < 1000) {
return count.toString()
}
if (count < 1000 * 1000) {
return (Math.round(count / 1000 * 100) / 100) + "K"
}
return (Math.round(count / 1000 / 1000 * 100) / 100) + "M"
}
this.loadBoard = function () {
this.board.trafficInBytes = teaweb.splitFormat(teaweb.formatBits(this.board.trafficInBytes * 8 / 60))
this.board.trafficOutBytes = teaweb.splitFormat(teaweb.formatBits(this.board.trafficOutBytes * 8 / 60))
this.board.countConnections = this.formatCount(this.board.countConnections)
this.board.countRequests = this.formatCount(Math.ceil(this.board.countRequests / 60))
this.board.countAttackRequests = this.formatCount(this.board.countAttackRequests)
this.board.cpuUsage = Math.round(this.board.cpuUsage * 100 * 100) / 100
this.board.memoryUsage = Math.round(this.board.memoryUsage * 100 * 100) / 100
this.board.memoryTotalSize = Math.round(this.board.memoryTotalSize * 10 / 1024 / 1024 / 1024) / 10
this.board.load = Math.round(this.board.load * 100) / 100
this.board.cacheDiskSize = teaweb.formatBytes(this.board.cacheDiskSize)
this.board.cacheMemorySize = teaweb.formatBytes(this.board.cacheMemorySize)
this.monthlyTrafficFormat = teaweb.splitFormat(teaweb.formatBytes(this.board.monthlyTrafficBytes))
this.yesterdayTrafficFormat = teaweb.splitFormat(teaweb.formatBytes(this.board.yesterdayTrafficBytes))
this.todayTrafficFormat = teaweb.splitFormat(teaweb.formatBytes(this.board.todayTrafficBytes))
}
/**
* 流量统计
*/
this.trafficTab = "hourly"
this.$delay(function () {
this.$post("$")
.params({
clusterId: this.clusterId,
nodeId: this.node.id
})
.timeout(60)
.success(function (resp) {
for (let k in resp.data) {
this[k] = resp.data[k]
}
this.loadBoard()
this.isLoading = false
this.$delay(function () {
this.reloadHourlyTrafficChart()
this.reloadHourlyRequestsChart()
this.reloadNetworkPacketsChart()
this.reloadCPUChart()
this.reloadCacheDirsChart()
this.renderCacheDirData()
this.refreshBoard()
})
// 域名统计
this.$post(".domainStats")
.params({
nodeId: this.node.id
})
.success(function (resp) {
for (let k in resp.data) {
this[k] = resp.data[k]
}
this.reloadTopDomainsChart()
})
})
})
this.refreshBoard = function () {
this.$post(".data")
.params({
clusterId: this.clusterId,
nodeId: this.node.id
})
.success(function (resp) {
this.board = resp.data.board
this.loadBoard()
})
.done(function () {
this.$delay(function () {
this.refreshBoard()
}, 30000)
})
}
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) {
let index = args.dataIndex
let cachedRatio = 0
let attackRatio = 0
if (stats[index].bytes > 0) {
cachedRatio = Math.round(stats[index].cachedBytes * 10000 / stats[index].bytes) / 100
attackRatio = Math.round(stats[index].attackBytes * 10000 / stats[index].bytes) / 100
}
return stats[index].day + " " + stats[index].hour + "时<br/>总流量:" + teaweb.formatBytes(stats[index].bytes) + "<br/>缓存流量:" + teaweb.formatBytes(stats[index].cachedBytes) + "<br/>缓存命中率:" + cachedRatio + "%<br/>拦截攻击流量:" + teaweb.formatBytes(stats[index].attackBytes) + "<br/>拦截比例:" + attackRatio + "%"
})
}
this.reloadDailyTrafficChart = function () {
let stats = this.dailyStats
this.reloadTrafficChart("daily-traffic-chart", "流量统计", stats, function (args) {
let index = args.dataIndex
let cachedRatio = 0
let attackRatio = 0
if (stats[index].bytes > 0) {
cachedRatio = Math.round(stats[index].cachedBytes * 10000 / stats[index].bytes) / 100
attackRatio = Math.round(stats[index].attackBytes * 10000 / stats[index].bytes) / 100
}
return stats[index].day + "<br/>总流量:" + teaweb.formatBytes(stats[index].bytes) + "<br/>缓存流量:" + teaweb.formatBytes(stats[index].cachedBytes) + "<br/>缓存命中率:" + cachedRatio + "%<br/>拦截攻击流量:" + teaweb.formatBytes(stats[index].attackBytes) + "<br/>拦截比例:" + attackRatio + "%"
})
}
this.reloadTrafficChart = function (chartId, name, stats, tooltipFunc) {
let chartBox = document.getElementById(chartId)
if (chartBox == null) {
return
}
let axis = teaweb.bytesAxis(stats, function (v) {
return Math.max(v.bytes, v.cachedBytes)
})
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: teaweb.DefaultChartColor
},
areaStyle: {
color: teaweb.DefaultChartColor
},
smooth: true
},
{
name: "缓存流量",
type: "line",
data: stats.map(function (v) {
return v.cachedBytes / axis.divider
}),
itemStyle: {
color: "#61A0A8"
},
areaStyle: {
color: "#61A0A8"
},
smooth: true
},
{
name: "攻击流量",
type: "line",
data: stats.map(function (v) {
return v.attackBytes / axis.divider;
}),
itemStyle: {
color: "#F39494"
},
areaStyle: {
color: "#F39494"
},
smooth: true
}
],
legend: {
data: ["流量", "缓存流量", "攻击流量"]
},
animation: true
}
chart.setOption(option)
chart.resize()
}
/**
* 请求数统计
*/
this.requestsTab = "hourly"
this.selectRequestsTab = function (tab) {
this.requestsTab = tab
if (tab == "hourly") {
this.$delay(function () {
this.reloadHourlyRequestsChart()
})
} else if (tab == "daily") {
this.$delay(function () {
this.reloadDailyRequestsChart()
})
}
}
this.reloadHourlyRequestsChart = function () {
let stats = this.hourlyStats
this.reloadRequestsChart("hourly-requests-chart", "请求数统计", stats, function (args) {
let index = args.dataIndex
let cachedRatio = 0
let attackRatio = 0
if (stats[index].countRequests > 0) {
cachedRatio = Math.round(stats[index].countCachedRequests * 10000 / stats[index].countRequests) / 100
attackRatio = Math.round(stats[index].countAttackRequests * 10000 / stats[index].countRequests) / 100
}
return stats[index].day + " " + stats[index].hour + "时<br/>总请求数:" + teaweb.formatNumber(stats[index].countRequests) + "<br/>缓存请求数:" + teaweb.formatNumber(stats[index].countCachedRequests) + "<br/>缓存命中率:" + cachedRatio + "%<br/>拦截攻击数:" + teaweb.formatNumber(stats[index].countAttackRequests) + "<br/>拦截比例:" + attackRatio + "%"
})
}
this.reloadDailyRequestsChart = function () {
let stats = this.dailyStats
this.reloadRequestsChart("daily-requests-chart", "请求数统计", stats, function (args) {
let index = args.dataIndex
let cachedRatio = 0
let attackRatio = 0
if (stats[index].countRequests > 0) {
cachedRatio = Math.round(stats[index].countCachedRequests * 10000 / stats[index].countRequests) / 100
attackRatio = Math.round(stats[index].countAttackRequests * 10000 / stats[index].countRequests) / 100
}
return stats[index].day + "<br/>总请求数:" + teaweb.formatNumber(stats[index].countRequests) + "<br/>缓存请求数:" + teaweb.formatNumber(stats[index].countCachedRequests) + "<br/>缓存命中率:" + cachedRatio + "%<br/>拦截攻击数:" + teaweb.formatNumber(stats[index].countAttackRequests) + "<br/>拦截比例:" + attackRatio + "%"
})
}
this.reloadRequestsChart = function (chartId, name, stats, tooltipFunc) {
let chartBox = document.getElementById(chartId)
if (chartBox == null) {
return
}
let axis = teaweb.countAxis(stats, function (v) {
return Math.max(v.countRequests, v.countCachedRequests)
})
let chart = teaweb.initChart(chartBox)
let option = {
xAxis: {
data: stats.map(function (v) {
if (v.hour != null) {
return v.hour
}
if (v.day != null) {
return v.day
}
return ""
})
},
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.countRequests / axis.divider
}),
itemStyle: {
color: teaweb.DefaultChartColor
},
areaStyle: {
color: teaweb.DefaultChartColor
},
smooth: true
},
{
name: "缓存请求数",
type: "line",
data: stats.map(function (v) {
return v.countCachedRequests / axis.divider
}),
itemStyle: {
color: "#61A0A8"
},
areaStyle: {
color: "#61A0A8"
},
smooth: true
},
{
name: "攻击请求数",
type: "line",
data: stats.map(function (v) {
return v.countAttackRequests / axis.divider;
}),
itemStyle: {
color: "#F39494"
},
areaStyle: {
color: "#F39494"
},
smooth: true
}
],
legend: {
data: ["请求数", "缓存请求数", "攻击请求数"]
},
animation: true
}
chart.setOption(option)
chart.resize()
}
// 域名排行
this.reloadTopDomainsChart = function () {
let axis = teaweb.countAxis(this.topDomainStats, function (v) {
return v.countRequests
})
teaweb.renderBarChart({
id: "top-domains-chart",
name: "域名",
values: this.topDomainStats,
x: function (v) {
return v.domain
},
tooltip: function (args, stats) {
return stats[args.dataIndex].domain + "<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 = "/servers/server?serverId=" + stats[args.dataIndex].serverId
}
})
}
/**
* 网络数据包
*/
this.networkPacketsTab = "tcp"
this.selectNetworkPacketsTab = function (tab) {
this.networkPacketsTab = tab
this.reloadNetworkPacketsChart()
}
this.reloadNetworkPacketsChart = function () {
let values = this.networkPacketsValues
let tab = this.networkPacketsTab
let valueFunc = function (v) {
switch (tab) {
case "tcp":
return v.tcpInPPS
case "udp":
return v.udpInPPS
case "icmp":
return v.icmpInPPS
default:
return 0
}
}
let axis = teaweb.countAxis(values, valueFunc)
teaweb.renderLineChart({
id: "network-packets-chart",
name: "网络数据包速率",
values: values,
x: function (v) {
return v.time
},
tooltip: function (args, stats) {
return stats[args.dataIndex].time + "" + teaweb.formatNumber(valueFunc(stats[args.dataIndex])) + " 个/秒"
},
value: function (v) {
return valueFunc(v) / axis.divider
},
axis: axis
})
}
/**
* 系统信息
*/
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
})
}
this.cacheDirUsed = ""
this.cacheDirTotal = ""
this.cacheDirAvail = ""
this.cacheDirAvailWarning = false
this.cacheDirOverCapacity = false
this.renderCacheDirData = function () {
if (this.cacheDirValues.length > 0) {
let lastStat = this.cacheDirValues.$last()
if (lastStat.value != null && lastStat.value.dirs != null && lastStat.value.dirs.length > 0) {
this.cacheDirUsed = teaweb.formatBytes(this.cacheDirValues.$last().value.dirs[0].used)
this.cacheDirTotal = teaweb.formatBytes(this.cacheDirValues.$last().value.dirs[0].total)
this.cacheDirAvail = teaweb.formatBytes(this.cacheDirValues.$last().value.dirs[0].avail)
this.cacheDirAvailWarning = (this.cacheDirValues.$last().value.dirs[0].avail < 1024 * 1024 * 1024 * 10)
this.cacheDirOverCapacity = (this.cacheDiskCapacityBytes > 0 && this.cacheDirValues.$last().value.dirs[0].used >= this.cacheDiskCapacityBytes)
}
}
}
this.reloadCacheDirsChart = function () {
let axis = {unit: "%", divider: 1}
teaweb.renderLineChart({
id: "cache-dirs-chart",
name: "缓存目录用量",
values: this.cacheDirValues,
x: function (v) {
return v.time
},
tooltip: function (args, stats) {
var v = stats[args.dataIndex].value.dirs[0]
return stats[args.dataIndex].time + "<br/>使用:" + teaweb.formatBytes(v.used) + "<br/>总量:" + teaweb.formatBytes(v.total) + "<br/>比例:" + (Math.ceil(v.used * 100 / v.total * 100) / 100) + "%"
},
value: function (v) {
if (v.value == null || v.value.dirs == null || v.value.dirs.length == 0) {
return 0
}
v = v.value.dirs[0]
return (v.used * 100 / v.total);
},
axis: axis,
max: 100
})
}
})

View File

@@ -0,0 +1,24 @@
.message.loading {
padding: 1.5em;
}
.chart-box {
height: 14em;
}
h4 {
span {
font-size: 0.8em;
color: grey;
}
}
.tabular.menu {
a {
span {
font-size: 0.8em;
font-weight: normal;
color: grey;
}
}
}

View File

@@ -0,0 +1,4 @@
a.underline {
border-bottom: 1px #db2828 dashed;
}
/*# sourceMappingURL=detail.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["detail.less"],"names":[],"mappings":"AAAA,CAAC;EACA,iCAAA","file":"detail.css"}

View File

@@ -0,0 +1,332 @@
{$layout}
{$template "node_menu"}
<table class="ui table definition selectable">
<tr>
<td class="title">节点名称</td>
<td>{{node.name}}</td>
</tr>
<tr>
<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 v-show="node.isBackupForCluster || node.isBackupForGroup">
<td>备用节点</td>
<td>
<span class="ui label basic small" v-if="node.isBackupForCluster">集群备用节点</span>
<span class="ui label basic small" v-if="node.isBackupForGroup">分组备用节点</span>
&nbsp; <a :href="'/clusters/cluster/node/settings/schedule?clusterId=' + clusterId + '&nodeId=' + node.id" style="font-size: 0.8em">[修改]</a>
</td>
</tr>
<tr v-show="node.offlineDay.length > 0">
<td>租期结束日期</td>
<td>
{{node.offlineDay.substring(0, 4)}}-{{node.offlineDay.substring(4, 6)}}-{{node.offlineDay.substring(6, 8)}} &nbsp; <a :href="'/clusters/cluster/node/settings/schedule?clusterId=' + clusterId + '&nodeId=' + node.id" style="font-size: 0.8em">[修改]</a>
<p class="comment" v-if="node.isOffline"><span class="red">已到期</span></p>
</td>
</tr>
<tr>
<td>IP地址</td>
<td>
<div v-if="node.ipAddresses.length > 0">
<div>
<div v-for="(address, index) in node.ipAddresses" class="ui label small basic">
<span v-if="address.ip.indexOf(':') > -1" class="grey">[IPv6]</span> {{address.ip}}
<span class="small red" v-if="address.originIP != null && address.originIP.length > 0 && address.originIP != address.ip">(原:{{address.originIP}}</span>
<span class="small grey" v-if="address.name.length > 0">(备注:{{address.name}}<span v-if="!address.canAccess">,不公开访问</span></span>
<span class="small grey" v-if="address.name.length == 0 && !address.canAccess">(不公开访问)</span>
<span class="small red" v-if="!address.isOn">[off]</span>
<span class="small red" v-if="!address.isUp">[down]</span>
<span class="small" v-if="address.thresholds != null && address.thresholds.length > 0">[阈值]</span>
<span v-if="address.clusters.length > 0">
&nbsp; <span class="small grey">专属集群:[</span><span v-for="(cluster, index) in address.clusters" class="small grey">{{cluster.name}}<span v-if="index < address.clusters.length - 1"></span></span><span class="small grey">]</span>
</span>
</div>
</div>
</div>
<div v-else>
<span class="disabled">暂时还没有填写IP地址。</span>
</div>
</td>
</tr>
<tr v-if="node.routes.length > 0">
<td>DNS线路</td>
<td>
<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="node.records.length > 0">
<td>DNS记录</td>
<td>
<div :style="{opacity: dnsIsExcludingLnNode ? 0.5 : 1.0}">
<table class="ui table celled">
<thead class="full-width">
<tr>
<th>集群</th>
<th>记录名</th>
<th>记录类型</th>
<th>线路</th>
<th>记录值</th>
<th>状态</th>
</tr>
</thead>
<tr v-for="record in node.records">
<td>{{record.clusterName}}</td>
<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>
<td>
<span v-if="record.isBackup" class="red">备用节点</span>
<span v-else-if="record.isOffline" class="red">已下线</span>
<span v-else>正常</span>
</td>
</tr>
</table>
<p class="comment" v-if="!dnsIsExcludingLnNode">通过设置A记录可以将集群上的服务请求转发到不同线路的节点上。</p>
</div>
<p class="comment" v-if="dnsIsExcludingLnNode"><span class="red">当前集群DNS已设置不解析Ln节点所以当前节点不会加入DNS解析如需加入请修改"集群设置" -- "DNS设置" -- "更多选项" -- "包含Ln节点"。</span></p>
<p class="comment" v-if="!node.isInstalled"><span class="red">当前节点尚未完成安装DNS解析记录将不会生效<a :href="'/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id">[点此安装]</a></span></p>
</td>
</tr>
<tr>
<td>所属区域</td>
<td>
<span v-if="node.region != null" class="ui label small basic">{{node.region.name}}</span>
<span v-else class="disabled">没有设置区域。</span>
</td>
</tr>
<tr>
<td>所属分组</td>
<td>
<span v-if="node.group != null" class="ui label small basic">{{node.group.name}}</span>
<span v-else class="disabled">没有设置分组。</span>
</td>
</tr>
<tr v-if="teaIsPlus">
<td>级别</td>
<td>
<span :class="{blue: node.levelInfo.code > 1}">{{node.levelInfo.name}}</span>
&nbsp; &nbsp; <span v-if="node.level > 1 && node.lnAddrs != null && node.lnAddrs.length > 0">
<span v-for="lnAddr in node.lnAddrs" class="ui label tiny basic">{{lnAddr}}</span>
</span>
<p class="comment" v-if="node.level > 1 && node.lnAddrs != null && node.lnAddrs.length > 0">指定了Ln节点访问地址。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator>更多选项</more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>同步IP名单</td>
<td>
<span v-if="node.enableIPLists" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
<tr>
<td>SSH主机地址</td>
<td>
<div v-if="node.login != null && node.login.params != null && node.login.params.host != null">
<span v-if="node.login.params.host.length > 0">{{node.login.params.host}}</span>
<span v-else class="disabled">尚未设置</span>
</div>
<div v-else class="disabled">
尚未设置
</div>
</td>
</tr>
<tr>
<td>SSH主机端口</td>
<td>
<div v-if="node.login != null && node.login.params != null && node.login.params.host != null">
<span v-if="node.login.params.port > 0">{{node.login.params.port}}</span>
<span v-else class="disabled">尚未设置</span>
</div>
<span v-else class="disabled">
尚未设置
</span>
</td>
</tr>
<tr>
<td>SSH登录认证</td>
<td>
<div v-if="node.login != null && node.login.grant != null && node.login.grant.id > 0">
<a :href="'/clusters/grants/grant?grantId=' + node.login.grant.id">{{node.login.grant.name}}<span class="small grey">{{node.login.grant.methodName}}</span><span class="small grey" v-if="node.login.grant.username.length > 0">{{node.login.grant.username}}</span></a>
</div>
<span v-else class="disabled">
尚未设置
</span>
</td>
</tr>
<tr>
<td>缓存硬盘容量</td>
<td>
<span v-if="node.maxCacheDiskCapacity == null || node.maxCacheDiskCapacity.count <= 0" class="disabled">没有限制</span>
<div v-else>
<size-capacity-view :v-value="node.maxCacheDiskCapacity"></size-capacity-view>
</div>
</td>
</tr>
<tr>
<td>硬盘预估写入最大速度</td>
<td>
<span v-if="node.status.diskWritingSpeedMB == 0">-</span><span v-if="node.status.diskWritingSpeedMB > 0">&gt; {{node.status.diskWritingSpeedMB}}</span>
<span v-if="node.status.diskWritingSpeedMB > 1000">MiB/s 极快</span>
<span v-else-if="node.status.diskWritingSpeedMB > 150">MiB/s 快</span>
<span v-else-if="node.status.diskWritingSpeedMB > 0">MiB/s 一般</span>
</td>
</tr>
<tr>
<td>缓存内存容量</td>
<td>
<span v-if="node.maxCacheMemoryCapacity == null || node.maxCacheMemoryCapacity.count <= 0" class="disabled">没有限制</span>
<div v-else>
<size-capacity-view :v-value="node.maxCacheMemoryCapacity"></size-capacity-view>
</div>
</td>
</tr>
<tr>
<td>CPU线程数</td>
<td>
<span v-if="node.maxCPU > 0">{{node.maxCPU}}线程</span>
<span v-else class="disabled">没有限制。</span>
</td>
</tr>
<tr>
<td>API节点地址</td>
<td>
<div v-if="node.apiNodeAddrs != null && node.apiNodeAddrs.length > 0">
<span v-for="addr in node.apiNodeAddrs" class="ui label basic small">{{addr}}</span>
</div>
<span v-else class="disabled">使用全局设置</span>
</td>
</tr>
</tbody>
</table>
<div class="ui divider"></div>
<h3>运行状态</h3>
<table class="ui table definition selectable">
<tr>
<td class="title">运行状态</td>
<td>
<div v-if="node.status.isActive">
<span class="green">运行中</span> &nbsp;
<a href="" @click.prevent="stopNode()" v-if="!isStopping"><span>[通过SSH停止]</span></a>
<span v-if="isStopping">[停止中...]</span>
</div>
<div v-else>
<span class="red">已断开</span> &nbsp;
<a href="" @click.prevent="startNode()" v-if="node.isInstalled && !isStarting"><span>[通过SSH启动]</span></a>
<span v-if="node.isInstalled && isStarting">[启动中...]</span>
<a v-if="!node.isInstalled" :href="'/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id" ><span>去安装&gt;</span></a>
</div>
</td>
</tr>
<tbody v-show="node.status.isActive">
<tr>
<td>CPU用量</td>
<td>{{node.status.cpuUsageText}} &nbsp; <span v-if="node.status.cpuPhysicalCount > 0" class="small grey">{{node.status.cpuPhysicalCount}}核心/{{node.status.cpuLogicalCount}}线程)</span></td>
</tr>
<tr>
<td>内存用量</td>
<td>{{node.status.memUsageText}}</td>
</tr>
<tr>
<td>连接数</td>
<td>{{node.status.connectionCount}}</td>
</tr>
<tr>
<td>负载</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>
<td>缓存用量</td>
<td>
硬盘:{{node.status.cacheTotalDiskSize}} &nbsp; 内存:{{node.status.cacheTotalMemorySize}}
</td>
</tr>
</tbody>
<tbody>
<tr v-if="node.status.buildVersion != null && node.status.buildVersion.length > 0">
<td>版本</td>
<td>v{{node.status.buildVersion}}
&nbsp; <a :href="'/clusters/cluster/upgradeRemote?clusterId=' + clusterId" v-if="shouldUpgrade"><span class="red">发现新版本v{{newVersion}} &raquo;</span></a>
</td>
</tr>
<tr v-if="node.status.exePath != null && node.status.exePath.length > 0">
<td>主程序位置</td>
<td>{{node.status.exePath}}</td>
</tr>
<tr>
<td>最近API连接状况</td>
<td>
<span v-if="node.status.apiSuccessPercent > 0 && node.status.apiAvgCostSeconds > 0">
<span v-if="node.status.apiSuccessPercent <= 50" class="red">连接错误异常严重({{round(100 - node.status.apiSuccessPercent)}}%失败请改善当前节点和API节点之间通讯</span>
<span v-else-if="node.status.apiSuccessPercent <= 80" class="red">连接错误较多({{round(100 - node.status.apiSuccessPercent)}}%失败请改善当前节点和API节点之间通讯</span>
<span v-else-if="node.status.apiSuccessPercent < 100" class="orange">有连接错误发生({{round(100 - node.status.apiSuccessPercent)}}%失败请改善当前节点和API节点之间通讯</span>
<span v-else>
<span v-if="node.status.apiAvgCostSeconds <= 1" class="green">连接良好</span>
<span v-else-if="node.status.apiAvgCostSeconds <= 5" class="orange">连接基本稳定(平均{{round(node.status.apiAvgCostSeconds)}}秒)</span>
<span v-else-if="node.status.apiAvgCostSeconds <= 10" class="orange">连接速度较慢(平均{{round(node.status.apiAvgCostSeconds)}}秒)</span>
<span v-else class="red">连接非常慢(平均{{round(node.status.apiAvgCostSeconds)}}秒请改善当前节点和API节点之间通讯</span>
</span>
</span>
<span v-else class="disabled">尚未上报数据</span>
</td>
</tr>
<tr v-if="nodeDatetime.length > 0">
<td>上次更新时间</td>
<td>
{{nodeDatetime}}
<p class="comment" v-if="nodeTimeDiff > 30"><span class="red">当前节点时间与API节点时间相差 {{nodeTimeDiff}} 秒,请同步节点时间。</span></p>
</td>
</tr>
</tbody>
</table>
<p class="comment" v-if="node.status.isActive">每隔30秒钟更新一次运行状态。</p>
<div class="ui divider"></div>
<h3>安装信息</h3>
<table class="ui table definition selectable">
<tr>
<td>节点ID<em>id</em></td>
<td>{{node.uniqueId}}</td>
</tr>
<tr>
<td>密钥<em>secret</em></td>
<td>{{node.secret}}</td>
</tr>
<tr>
<td class="title">安装目录</td>
<td>
<div v-if="node.installDir.length == 0">使用集群设置<span v-if="node.cluster != null && node.cluster.installDir.length > 0">{{node.cluster.installDir}}</span></div>
<span v-else>{{node.installDir}}</span>
</td>
</tr>
<tr>
<td>已安装</td>
<td>
<span v-if="node.isInstalled" class="green">已安装 &nbsp; <a href="" @click.prevent="showMoreOps"><span class="small">[操作]</span></a></span>
<a v-else :href="'/clusters/cluster/installNode?clusterId=' + clusterId + '&nodeId=' + nodeId" class="underline" title="点击进入安装界面"><span class="red">未安装</span></a>
</td>
</tr>
<tr v-show="moreOpsVisible && node.isInstalled">
<td>操作</td>
<td><a href="" @click.prevent="uninstallNode">[卸载]</a> </td>
</tr>
</table>

View File

@@ -0,0 +1,59 @@
Tea.context(function () {
this.isStarting = false
this.startNode = function () {
this.isStarting = true
this.$post("/clusters/cluster/node/start")
.params({
nodeId: this.node.id
})
.success(function () {
teaweb.success("启动成功", function () {
teaweb.reload()
})
})
.done(function () {
this.isStarting = false
})
}
this.isStopping = false
this.stopNode = function () {
this.isStopping = true
this.$post("/clusters/cluster/node/stop")
.params({
nodeId: this.node.id
})
.success(function () {
teaweb.success("执行成功", function () {
teaweb.reload()
})
})
.done(function () {
this.isStopping = false
})
}
this.round = function (f) {
return Math.round(f * 100) / 100
}
this.moreOpsVisible = false
this.showMoreOps = function () {
this.moreOpsVisible = !this.moreOpsVisible
}
this.uninstallNode = function () {
let that = this
teaweb.confirm("html:确定要卸载当前节点吗?<br/>此操作将会删除节点上除缓存以外的相关文件。", function () {
that.$post("/clusters/cluster/node/uninstall")
.params({
nodeId: that.node.id
})
.success(function () {
teaweb.success("执行成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -0,0 +1,3 @@
a.underline {
border-bottom: 1px #db2828 dashed;
}

View File

@@ -0,0 +1,7 @@
.installing-box {
line-height: 1.8;
}
.installing-box .blue {
color: #2185d0;
}
/*# sourceMappingURL=install.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["install.less"],"names":[],"mappings":"AAAA;EACC,gBAAA;;AADD,eAGC;EACC,cAAA","file":"install.css"}

View File

@@ -0,0 +1,132 @@
{$layout}
{$template "node_menu"}
{$template "/code_editor"}
<!-- 未安装 -->
<div v-if="!node.isInstalled">
<div>
<span class="red">在当前节点完成安装前相关DNS解析记录将不会生效<link-red href="" @click.prevent="updateNodeIsInstalled(true)">已完成安装</link-red></span>
<div class="ui divider"></div>
</div>
<h4>方法1通过SSH自动安装</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">SSH地址</td>
<td>
<span v-if="sshAddr.length > 0">{{sshAddr}} &nbsp; <a href="" @click.prevent="showSSHPopup(nodeId)">[修改]</a></span>
<span v-else><span class="red">尚未设置</span> &nbsp; <a href="" @click.prevent="showSSHPopup(nodeId)">[设置]</a></span>
</td>
</tr>
</table>
<div v-if="installStatus != null && (installStatus.isRunning || installStatus.isFinished)"
class="ui segment installing-box">
<div v-if="installStatus.isRunning" class="blue">安装中...</div>
<div v-if="installStatus.isFinished">
<span v-if="installStatus.isOk" class="green">已安装成功</span>
<span v-if="!installStatus.isOk" class="red">安装过程中发生错误:{{installStatus.error}}</span>
</div>
</div>
<div v-if="installStatus != null && installStatus.isFinished">
<button class="ui button small" type="button" @click.prevent="install()">重新安装</button>
</div>
<div v-if="installStatus == null || (!installStatus.isFinished && !installStatus.isRunning)">
<button class="ui button small primary" type="button" @click.prevent="install()">开始安装</button>
</div>
<h4>方法2手动安装</h4>
<table class="ui table definition selectable">
<tr v-if="installerFiles != null && installerFiles.length > 0">
<td class="title">下载安装文件</td>
<td>
<table class="ui table celled">
<thead class="full-width">
<tr>
<th>文件名</th>
<th>操作系统</th>
<th>CPU架构</th>
<th>版本</th>
</tr>
</thead>
<tr v-for="installerFile in installerFiles">
<td>
<a :href="'/clusters/cluster/downloadInstaller?name=' + installerFile.name" target="_blank" style="font-weight: normal">{{installerFile.name}}</a>
</td>
<td>{{installerFile.os}}</td>
<td>{{installerFile.arch}}</td>
<td>v{{installerFile.version}}</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>配置文件</td>
<td>
configs/api_node.yaml &nbsp;
<download-link :v-element="'rpc-code'" :v-file="'api_node.yaml'">[下载]</download-link >
</td>
</tr>
<tr>
<td>配置内容</td>
<td>
<source-code-box id="rpc-code" type="text/yaml">rpc.endpoints: [ {{apiEndpoints}} ]
nodeId: "{{node.uniqueId}}"
secret: "{{node.secret}}"</source-code-box>
<p class="comment">手动替换edge-node安装目录下的<code-label>configs/api_node.yaml</code-label>文件,然后重新启动生效;如果此文件不存在,则需要创建。</p>
</td>
</tr>
<tr>
<td class="title">安装目录</td>
<td>
<div v-if="node.installDir.length == 0">
<span v-if="node.cluster != null && node.cluster.installDir.length > 0">建议使用集群设置({{node.cluster.installDir}}</span>
<span v-else>建议安装在 /usr/local/goedge/edge-node</span>
</div>
<span v-else>{{node.installDir}}</span>
</td>
</tr>
</table>
<div class="ui divider"></div>
<a href="" @click.prevent="updateNodeIsInstalled(true)">[修改当前节点为已安装状态]</a>
</div>
<!-- 已安装 -->
<div v-if="node.isInstalled">
<h4>配置文件</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">配置文件</td>
<td>
configs/api_node.yaml &nbsp; <download-link :v-element="'rpc-code'" :v-file="'api_node.yaml'">[下载]</download-link >
</td>
</tr>
<tr>
<td class="title">配置内容<td>
<source-code-box id="rpc-code" type="text/yaml">rpc.endpoints: [ {{apiEndpoints}} ]
nodeId: "{{node.uniqueId}}"
secret: "{{node.secret}}"</source-code-box>
<p class="comment">每个节点的配置文件内容均不相同,不能混用。</p>
</td>
</tr>
<tr>
<td class="title">安装目录</td>
<td>
<div v-if="node.installDir.length == 0">使用集群设置<span
v-if="node.cluster != null && node.cluster.installDir.length > 0">{{node.cluster.installDir}}</span>
</div>
<span v-else>{{node.installDir}}</span>
</td>
</tr>
<tr v-if="exeRoot.length > 0">
<td>最近运行目录</td>
<td>{{exeRoot}}</td>
</tr>
</table>
<div>
<div class="ui message success">当前节点为已安装状态。</div>
<a href="" @click.prevent="updateNodeIsInstalled(false)">[重新安装]</a>
</div>
</div>

View File

@@ -0,0 +1,117 @@
Tea.context(function () {
let isInstalling = false
this.$delay(function () {
this.reloadStatus(this.nodeId)
})
// 开始安装
this.install = function () {
isInstalling = true
this.$post("$")
.params({
nodeId: this.nodeId
})
.success(function () {
})
}
// 设置节点安装状态
this.updateNodeIsInstalled = function (isInstalled) {
let msg = isInstalled ? "html:确定要将当前节点修改为<strong>已安装</strong>状态?" : "html:确定要将当前节点修改为<strong>未安装</strong>状态?"
teaweb.confirm(msg, function () {
this.$post("/clusters/cluster/node/updateInstallStatus")
.params({
nodeId: this.nodeId,
isInstalled: isInstalled ? 1 : 0
})
.refresh()
})
}
// 刷新状态
this.reloadStatus = function (nodeId) {
let that = this
this.$post("/clusters/cluster/node/status")
.params({
nodeId: nodeId
})
.success(function (resp) {
this.installStatus = resp.data.installStatus
this.node.isInstalled = resp.data.isInstalled
if (!isInstalling) {
return
}
let nodeId = this.node.id
let errMsg = this.installStatus.error
if (this.installStatus.errorCode.length > 0) {
isInstalling = false
}
switch (this.installStatus.errorCode) {
case "EMPTY_LOGIN":
case "EMPTY_SSH_HOST":
case "EMPTY_SSH_PORT":
case "EMPTY_GRANT":
teaweb.warn("需要填写SSH登录信息", function () {
teaweb.popup("/clusters/cluster/updateNodeSSH?nodeId=" + nodeId, {
height: "30em",
callback: function () {
that.install()
}
})
})
return
case "SSH_LOGIN_FAILED":
teaweb.warn("SSH登录失败请检查设置", function () {
teaweb.popup("/clusters/cluster/updateNodeSSH?nodeId=" + nodeId, {
height: "30em",
callback: function () {
that.install()
}
})
})
return
case "CREATE_ROOT_DIRECTORY_FAILED":
teaweb.warn("创建根目录失败,请检查目录权限或者手工创建:" + errMsg)
return
case "INSTALL_HELPER_FAILED":
teaweb.warn("安装助手失败:" + errMsg)
return
case "TEST_FAILED":
teaweb.warn("环境测试失败:" + errMsg)
return
case "RPC_TEST_FAILED":
teaweb.confirm("html:要安装的节点到API服务之间的RPC通讯测试失败具体错误" + errMsg + "<br/>现在修改API信息", function () {
window.location = "/settings/api"
})
return
default:
shouldReload = true
//teaweb.warn("安装失败:" + errMsg)
}
})
.done(function () {
this.$delay(function () {
this.reloadStatus(nodeId)
}, 1000)
});
}
this.showSSHPopup = function (nodeId) {
teaweb.popup("/clusters/cluster/updateNodeSSH?nodeId=" + nodeId, {
height: "30em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
})

View File

@@ -0,0 +1,7 @@
.installing-box {
line-height: 1.8;
.blue {
color: #2185d0;
}
}

View File

@@ -0,0 +1,5 @@
pre.log-box {
margin: 0;
padding: 0;
}
/*# sourceMappingURL=logs.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["logs.less"],"names":[],"mappings":"AAAA,GAAG;EACF,SAAA;EACA,UAAA","file":"logs.css"}

View File

@@ -0,0 +1,57 @@
{$layout}
{$template "node_menu"}
{$template "/datepicker"}
<form method="get" action="/clusters/cluster/node/logs" class="ui form" autocomplete="off">
<input type="hidden" name="clusterId" :value="clusterId"/>
<input type="hidden" name="nodeId" :value="nodeId"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="dayFrom" placeholder="开始日期" v-model="dayFrom" value="" style="width:8em" id="day-from-picker"/>
</div>
<div class="ui field">
<input type="text" name="dayTo" placeholder="结束日期" v-model="dayTo" value="" style="width:8em" id="day-to-picker"/>
</div>
<div class="ui field">
<select class="ui dropdown" name="level" v-model="level">
<option value="">[级别]</option>
<option value="error">错误</option>
<option value="warning">警告</option>
<option value="info">信息</option>
<option value="success">成功</option>
</select>
</div>
<div class="ui field">
<select class="ui dropdown" name="tag" v-model="tag">
<option value="">[标签]</option>
<option v-for="tag in tags" :value="tag.code">{{tag.name}}</option>
</select>
</div>
<div class="ui field">
<input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/>
</div>
<div class="ui field">
<button type="submit" class="ui button">查询</button>
</div>
<div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0 || tag.length > 0">
<a :href="'/clusters/cluster/node/logs?clusterId=' + clusterId + '&nodeId=' + nodeId">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="logs.length == 0">暂时还没有日志。</p>
<table class="ui table selectable" v-if="logs.length > 0">
<thead>
<tr>
</tr>
</thead>
<tr v-for="log in logs">
<td>
<node-log-row :v-log="log" :v-keyword="keyword"></node-log-row>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,6 @@
Tea.context(function () {
this.$delay(function () {
teaweb.datepicker("day-from-picker")
teaweb.datepicker("day-to-picker")
})
})

View File

@@ -0,0 +1,4 @@
pre.log-box {
margin: 0;
padding: 0;
}

View File

@@ -0,0 +1,41 @@
{$layout}
{$template "/clusters/cluster/node/node_menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="nodeId" :value="node.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">缓存硬盘容量</td>
<td>
<size-capacity-box :v-value="node.maxCacheDiskCapacity" :v-name="'maxCacheDiskCapacityJSON'"></size-capacity-box>
<p class="comment">缓存所在硬盘的最大用量超出此用量或硬盘接近用尽时将会自动尝试清理旧数据0表示不限制。</p>
</td>
</tr>
<tr>
<td>硬盘缓存主目录</td>
<td>
<input type="text" name="cacheDiskDir" maxlength="500" value="" v-model="node.cacheDiskDir"/>
<p class="comment">存放文件缓存的主目录,通常填写绝对路径。不填则表示使用集群缓存策略中定义的目录。</p>
</td>
</tr>
<tr>
<td>其他硬盘缓存目录</td>
<td>
<node-cache-disk-dirs-box name="cacheDiskSubDirsJSON" v-model="node.cacheDiskSubDirs"></node-cache-disk-dirs-box>
<p class="comment">除了主目录外,可以在这里添加别的可用于缓存的目录;缓存目录有变更时(添加、删除或修改)需要手动将这些缓存目录清空。</p>
</td>
</tr>
<tr>
<td>缓存内存容量</td>
<td>
<size-capacity-box :v-value="node.maxCacheMemoryCapacity" :v-name="'maxCacheMemoryCapacityJSON'"></size-capacity-box>
<p class="comment">缓存能使用的内存的最大容量0表示不限制。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>
</div>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -0,0 +1,17 @@
{$layout}
{$template "/clusters/cluster/node/node_menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="nodeId" :value="nodeId"/>
<node-ddos-protection-config-box :v-ddos-protection-config="config" :v-default-configs="defaultConfigs" :v-is-node="true" :v-cluster-is-on="clusterDDoSProtectionIsOn"></node-ddos-protection-config-box>
<p class="ui message green" v-if="checkResult != null && checkResult.isOk">节点检查结果:{{checkResult.message}}</p>
<p class="ui message red" v-if="checkResult != null && !checkResult.isOk">节点检查结果:有错误发生:{{checkResult.message}}</p>
<submit-btn></submit-btn>
</form>
</div>

View File

@@ -0,0 +1,15 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
this.checkResult = null
this.$post(".status")
.params({
nodeId: this.nodeId
})
.success(function (resp) {
let results = resp.data.results
if (results.length > 0) {
this.checkResult = results[0]
}
})
})

View File

@@ -0,0 +1,26 @@
{$layout}
{$template "/clusters/cluster/node/node_menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="nodeId" :value="node.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">DNS线路</td>
<td>
<div v-if="allDNSRoutes.length > 0">
<dns-route-selector :v-all-routes="allDNSRoutes" :v-routes="dnsRoutes"></dns-route-selector>
<p class="comment">当前节点对应的DNS线路可用线路是根据集群设置的域名获取的注意DNS服务商可能对这些线路有其他限制。</p>
</div>
<div v-else>
<span class="disabled">暂时没有可选的线路。</span>
</div>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>
</div>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -0,0 +1,35 @@
{$layout "layout_popup"}
<h3>添加调度动作</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="nodeId" :value="nodeId"/>
<table class="ui table definition selectable">
<tr>
<td class="title">执行条件 *</td>
<td>
<node-schedule-conds-box :v-params="params" :v-operators="operators" @changeparam="changeParam"></node-schedule-conds-box>
</td>
</tr>
<tr>
<td>执行动作 *</td>
<td>
<node-schedule-action-box :v-actions="actions"></node-schedule-action-box>
</td>
</tr>
<tr v-show="param != null && param.hasDuration">
<td>持续时间 *</td>
<td>
<div class="ui input right labeled">
<input type="text" maxlength="3" style="width: 4em" value="60" name="durationMinutes"/>
<span class="ui label">分钟</span>
</div>
<p class="comment">表示当前动作执行后多久自动还原为原始状态。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,9 @@
Tea.context(function () {
this.param = null
this.changeParam = function (param) {
if (param != null) {
this.param = param
}
}
})

View File

@@ -0,0 +1,39 @@
{$layout "layout_popup"}
<h3>修改调度动作</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="actionId" :value="action.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">执行条件 *</td>
<td>
<node-schedule-conds-box :v-params="params" :v-operators="operators" v-model="action.conds" @changeparam="changeParam"></node-schedule-conds-box>
</td>
</tr>
<tr>
<td>执行动作 *</td>
<td>
<node-schedule-action-box :v-actions="actions" v-model="action.action"></node-schedule-action-box>
</td>
</tr>
<tr v-show="param != null && param.hasDuration">
<td>持续时间 *</td>
<td>
<div class="ui input right labeled">
<input type="text" maxlength="3" style="width: 4em" value="60" name="durationMinutes" v-model="action.durationMinutes"/>
<span class="ui label">分钟</span>
</div>
<p class="comment">表示当前动作执行后多久自动还原为原始状态。</p>
</td>
</tr>
<tr>
<td>启用当前动作</td>
<td><checkbox name="isOn" v-model="action.isOn"></checkbox></td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,18 @@
Tea.context(function () {
this.param = null
if (this.action.conds.conds != null) {
let that = this
this.action.conds.conds.forEach(function (cond) {
that.param = that.params.$find(function (k, v) {
return v.code == cond.param
})
})
}
this.changeParam = function (param) {
if (param != null) {
this.param = param
}
}
})

View File

@@ -0,0 +1,160 @@
{$layout}
{$template "/clusters/cluster/node/node_menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<h4>基础设置 &nbsp; <a href="" @click.prevent="editSchedule" style="font-size: 0.8em"><span v-if="!scheduleIsEditing">[修改]</span><span v-else>[取消修改]</span></a> </h4>
<form class="ui form">
<table class="ui table definition selectable" v-show="!scheduleIsEditing">
<tr>
<td class="title">当前节点租期结束日期</td>
<td>
<span v-if="schedule.offlineDay.length > 0">{{schedule.offlineDay}}</span>
<span v-else class="disabled">没有设置</span>
<p class="comment" v-if="schedule.isOffline"><span class="red">已到期</span></p>
<p class="comment" v-else-if="schedule.isNearlyOffline"><span class="red">即将到期</span></p>
</td>
</tr>
<tr>
<td>当前节点为集群备用节点</td>
<td>
<span v-if="schedule.isBackupForCluster" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
<tr>
<td>当前节点为分组备用节点</td>
<td>
<span v-if="schedule.isBackupForGroup" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
<tr>
<td>当前节点备用IP</td>
<td>
<div v-if="schedule.backupIPs != null && schedule.backupIPs.length > 0">
<span v-for="backupIP in schedule.backupIPs" class="ui basic tiny label">{{backupIP}}</span>
</div>
</td>
</tr>
<tr>
<td>当前触发动作</td>
<td>
<div v-if="schedule.actionStatus != null && schedule.actionStatus.actionId > 0">
<div class="ui fields inline">
<div class="ui field">
<node-schedule-conds-viewer :v-params="params" :v-operators="operators" v-model="schedule.actionStatus.conds"></node-schedule-conds-viewer>
</div>
<div class="ui field"><span class="red">-&gt;</span></div>
<div class="ui field">
<span class="red">{{actionMap[schedule.actionStatus.action.code].name}}</span>
</div>
<div class="ui field" v-if="schedule.actionStatusExpiresTime.length > 0">
<span class="red">| &nbsp; &nbsp; 有效期至{{schedule.actionStatusExpiresTime}}</span>
</div>
<div class="ui field">
<a href="" @click.prevent="resetActionStatus">[重置]</a>
</div>
</div>
</div>
<div v-else>
<span class="disabled"></span>
</div>
</td>
</tr>
</table>
</form>
<form class="ui form" data-tea-success="success" data-tea-action="$" v-show="scheduleIsEditing">
<csrf-token></csrf-token>
<input type="hidden" name="nodeId" :value="schedule.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">当前节点租期结束日期</td>
<td>
<datepicker name="offlineDay" v-model="schedule.offlineDay"></datepicker>
<p class="comment">当前节点租期结束日期,到此日期后当前节点将会被自动下线;如果为空,则表示永远不下线。</p>
</td>
</tr>
<tr>
<td>当前节点为集群备用节点</td>
<td>
<checkbox name="isBackupForCluster" v-model="schedule.isBackupForCluster"></checkbox>
<p class="comment">选中后,标识当前节点为所在集群<span v-if="schedule.nodeClusterName.length > 0"><code-label>{{schedule.nodeClusterName}}</code-label></span>的备用节点同集群的其他节点可以通过调度动作切换到此节点。备用节点未被调度触发时不会加入DNS域名解析。</p>
</td>
</tr>
<tr>
<td>当前节点为分组备用节点</td>
<td>
<checkbox name="isBackupForGroup" v-model="schedule.isBackupForGroup"></checkbox>
<p class="comment">选中后,标识当前节点为所在分组<span v-if="schedule.nodeGroupName.length > 0"><code-label>{{schedule.nodeGroupName}}</code-label></span>的备用节点,同分组的其他节点可以通过调度动作切换到此节点<span v-if="schedule.nodeGroupName.length == 0">;如果暂时还没有分组,则当前设置不会起作用</span>。备用节点未被调度触发时不会加入DNS域名解析。</p>
</td>
</tr>
<tr>
<td>当前节点备用IP</td>
<td>
<values-box name="backupIPs" :v-values="schedule.backupIPs" placeholder="IP"></values-box>
<p class="comment">可以通过调度动作切换到这些备用IP。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>
<div v-show="!scheduleIsEditing">
<div class="ui margin"></div>
<h4>调度动作&nbsp;
<a href="" style="font-size: 0.8em" @click.prevent="createAction">[添加]</a> &nbsp;
<a href="" v-if="!showActionsCopyOps" @click.prevent="showActionsCopyOps = !showActionsCopyOps" title="更多操作" style="font-size: 0.8em">[更多操作]</a>
<span v-show="showActionsCopyOps">
<a href="" style="font-size: 0.8em" v-if="schedule.nodeGroupName.length > 0" @click.prevent="copyActionsToGroup">[复制到分组"{{schedule.nodeGroupName}}"]</a> &nbsp;
<a href="" style="font-size: 0.8em" @click.prevent="copyActionsToCluster">[复制到集群"{{schedule.nodeClusterName}}"]</a>
</span>
</h4>
<p class="comment" v-if="actions.length == 0">暂时还没有任何调度动作。</p>
<div v-if="actions.length > 0">
<p class="ui message warning" v-if="schedule.isBackupForCluster || schedule.isBackupForGroup">当前节点为备用节点,这里设置的调度动作将不会被触发。</p>
<table class="ui table celled selectable" id="sortable-table">
<thead>
<tr>
<th style="width:3em"></th>
<th>条件</th>
<th class="three wide">动作</th>
<th class="two wide">持续时间</th>
<th class="width5">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tbody v-for="action in actions" :v-id="action.id">
<tr>
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
<td>
<node-schedule-conds-viewer :v-params="params" :v-operators="operators" v-model="action.conds"></node-schedule-conds-viewer>
</td>
<td>
<span :class="{red: schedule.actionStatus != null && schedule.actionStatus.actionId > 0 && schedule.actionStatus.actionId == action.id}">{{action.actionName}}</span>
</td>
<td>{{action.durationDescription}}</td>
<td>
<div v-if="schedule.actionStatus != null && schedule.actionStatus.actionId > 0 && schedule.actionStatus.actionId == action.id">
<span class="red">已触发</span>
</div>
<div v-else>
<label-on :v-is-on="action.isOn"></label-on>
</div>
</td>
<td>
<a href="" @click.prevent="updateAction(action.id)">修改</a> &nbsp; <a href="" @click.prevent="deleteAction(action.id)">删除</a>
</td>
</tr>
</tbody>
</table>
<p v-if="actions.length > 0" class="comment">可以拖动左侧的<i class="icon bars"></i>排序。</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,96 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
this.scheduleIsEditing = false
this.editSchedule = function () {
this.scheduleIsEditing = !this.scheduleIsEditing
}
this.createAction = function () {
teaweb.popup(".actions.createPopup?nodeId=" + this.schedule.id, {
height: "26em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteAction = function (actionId) {
teaweb.confirm("确定要删除此调度动作吗?", function () {
this.$post(".actions.delete")
.params({
actionId: actionId
})
.success(function () {
teaweb.reload()
})
})
}
this.updateAction = function (actionId) {
teaweb.popup(".actions.updatePopup?actionId=" + actionId, {
height: "26em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.resetActionStatus = function () {
teaweb.confirm("确定要重置当前节点的状态吗?", function () {
this.$post(".resetActionStatus")
.params({
nodeId: this.schedule.id
})
.success(function () {
teaweb.successRefresh("重置成功")
})
})
}
this.showActionsCopyOps = false
this.copyActionsToGroup = function () {
teaweb.confirm("确定要复制当前节点的调度动作到所在分组吗?", function () {
this.$post(".actions.copyToGroup")
.params({
nodeId: this.schedule.id
})
.success(function () {
teaweb.successRefresh("同步成功")
})
})
}
this.copyActionsToCluster = function () {
teaweb.confirm("确定要复制当前节点的调度动作到所在分组吗?", function () {
this.$post(".actions.copyToCluster")
.params({
nodeId: this.schedule.id
})
.success(function () {
teaweb.successRefresh("同步成功")
})
})
}
// 排序
this.$delay(function () {
let that = this
sortTable(function (actionIds) {
that.$post(".actions.updateOrders")
.params({
nodeId: that.schedule.id,
actionIds: actionIds
})
.success(function () {
teaweb.success("保存成功")
})
})
})
})

View File

@@ -0,0 +1,40 @@
{$layout}
{$template "/clusters/cluster/node/node_menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="nodeId" :value="node.id"/>
<input type="hidden" name="loginId" :value="loginId"/>
<table class="ui table definition selectable">
<tr>
<td class="title">SSH主机地址</td>
<td>
<input type="text" name="sshHost" maxlength="64" v-model="sshHost"/>
<p class="comment"><span v-if="hostIsAutoFilled" class="red"><strong>已自动填充</strong>,需要点击"保存"按钮后生效。</span>比如192.168.1.100。</p>
</td>
</tr>
<tr>
<td>SSH主机端口</td>
<td>
<input type="text" name="sshPort" maxlength="5" v-model="sshPort"/>
<p class="comment">比如22。</p>
</td>
</tr>
<tr>
<td>SSH登录认证</td>
<td>
<grant-selector :v-grant="grant" :v-node-cluster-id="clusterId"></grant-selector>
</td>
</tr>
</table>
<div class="ui message" v-if="isTesting">正在测试连接 ...</div>
<div class="ui message green" v-if="resp != null && resp.isOk">连接成功!</div>
<div class="ui message red" v-if="resp != null && !resp.isOk">连接失败:{{resp.error}}</div>
<submit-btn></submit-btn>
</form>
</div>

View File

@@ -0,0 +1,53 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
// 认证相关
this.grant = null
this.sshHost = ""
this.sshPort = ""
this.loginId = 0
if (this.node.login != null) {
this.loginId = this.node.login.id
if (this.node.login.params != null) {
this.sshHost = this.node.login.params.host
if (this.node.login.params.port > 0) {
this.sshPort = this.node.login.params.port
}
}
if (this.node.login.grant != null && typeof this.node.login.grant.id != "undefined") {
this.grant = {
id: this.node.login.grant.id,
name: this.node.login.grant.name,
method: this.node.login.grant.method,
methodName: this.node.login.grant.methodName,
username: this.node.login.grant.username
}
}
}
// 测试相关
this.resp = null
this.isTesting = false
if (this.grant != null && this.grant.id > 0 && this.sshHost.length > 0 && this.sshPort.toString().length > 0) {
this.isTesting = true
this.$delay(function () {
this.$post(".test")
.params({
grantId: this.grant.id,
host: this.sshHost,
port: this.sshPort
})
.success(function (resp) {
this.resp = resp.data
})
.done(function () {
this.isTesting = false
})
}, 1000)
}
})

View File

@@ -0,0 +1,37 @@
{$layout}
{$template "/clusters/cluster/node/node_menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<form class="ui form" data-tea-success="success" data-tea-action="$">
<input type="hidden" name="nodeId" :value="node.id"/>
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">CPU线程数</td>
<td>
<input type="text" name="maxCPU" v-model="node.maxCPU" style="width:5em" maxlength="4"/>
<p class="comment">当前节点可以使用的最多的CPU线程数如果为0则默认为总的CPU线程数。<pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4>DNS解析</h4>
<dns-resolver-config-box :v-dns-resolver-config="dnsResolverConfig"></dns-resolver-config-box>
<h4>API相关</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">API节点地址</td>
<td>
<div style="margin-bottom: 0.5em">
<api-node-addresses-box :v-name="'apiNodeAddrsJSON'" :v-addrs="apiNodeAddrs"></api-node-addresses-box>
</div>
<p class="comment">当前节点单独使用的API节点设置。<pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>
</div>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -0,0 +1,41 @@
{$layout}
{$template "/clusters/cluster/node/node_menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<div>
<second-menu>
<menu-item @click.prevent="createThreshold">[添加阈值]</menu-item>
</second-menu>
</div>
<p class="comment" v-if="thresholds.length == 0">暂时还没有设置阈值。</p>
<table class="ui table selectable celled" v-if="thresholds.length > 0">
<thead>
<tr>
<th>监控项</th>
<th>参数</th>
<th>操作符</th>
<th>对比值</th>
<th>统计时间段</th>
<th class="two wide">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="threshold in thresholds">
<td>{{threshold.itemName}}</td>
<td>{{threshold.paramName}}</td>
<td>{{threshold.operatorName}}</td>
<td>{{threshold.value}}<span v-if="threshold.paramIsPercent">%</span></td>
<td>{{threshold.duration}}{{threshold.durationUnitName}}</td>
<td>
<label-on :v-is-on="threshold.isOn"></label-on>
</td>
<td>
<a href="" @click.prevent="updateThreshold(threshold.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteThreshold(threshold.id)">删除</a>
</td>
</tr>
</table>
</div>

View File

@@ -0,0 +1,43 @@
Tea.context(function () {
this.createThreshold = function () {
teaweb.popup(Tea.url("/clusters/cluster/settings/thresholds/createPopup", {
clusterId: this.clusterId,
nodeId: this.nodeId
}), {
height: "30em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateThreshold = function (thresholdId) {
teaweb.popup(Tea.url("/clusters/cluster/settings/thresholds/updatePopup", {
thresholdId: thresholdId
}), {
height: "30em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteThreshold = function (thresholdId) {
let that = this
teaweb.confirm("确定要删除这个阈值吗?", function () {
that.$post("/clusters/cluster/settings/thresholds/delete")
.params({
thresholdId: thresholdId
})
.success(function () {
teaweb.success("删除成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -0,0 +1,85 @@
{$layout}
{$template "node_menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="nodeId" :value="node.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">节点名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus" v-model="node.name"/>
</td>
</tr>
<tr>
<td>所属集群</td>
<td>
<span v-if="node.primaryCluster != null" class="ui label basic small">{{node.primaryCluster.name}}</span>
<span v-if="node.secondaryClusters.length > 0" v-for="cluster in node.secondaryClusters" class="ui label basic small grey">{{cluster.name}}</span> &nbsp; <a href="" @click.prevent="updateClusters">[修改]</a>
<div v-show="showClustersBox">
<node-clusters-selector :v-primary-cluster="node.primaryCluster" :v-secondary-clusters="node.secondaryClusters" @change="changeClusters"></node-clusters-selector>
</div>
</td>
</tr>
<tr>
<td>IP地址 *</td>
<td>
<node-ip-addresses-box :v-ip-addresses="ipAddresses" :v-node-id="node.id"></node-ip-addresses-box>
<p class="comment">用于访问节点和域名解析等。</p>
</td>
</tr>
<tr>
<td>所属分组</td>
<td>
<node-group-selector :v-cluster-id="clusterId" :v-group="node.group"></node-group-selector>
<p class="comment">用来筛选节点<span v-if="teaIsPlus">,同时可以在分组中设置二级缓存节点</span></p>
</td>
</tr>
<tr>
<td>所属区域</td>
<td>
<node-region-selector :v-region="node.region"></node-region-selector>
<p class="comment">设置区域后可以根据区域进行流量统计和计费。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr v-show="teaIsPlus && canUpdateLevel">
<td>级别</td>
<td>
<node-level-selector :v-node-level="node.level" @change="changeLevel"></node-level-selector>
</td>
</tr>
<tr v-show="teaIsPlus && canUpdateLevel && nodeLevel > 1">
<td>L2级别访问地址</td>
<td>
<values-box name="lnAddrs" :v-values="node.lnAddrs" placeholder="IP地址"></values-box>
<p class="comment">如果不为空边缘节点访问当前L2节点时将会使用这些IP地址如果没有设置将会使用当前节点已经填写的IP地址。</p>
</td>
</tr>
<tr>
<td>同步IP名单</td>
<td>
<checkbox name="enableIPLists" v-model="node.enableIPLists"></checkbox>
<p class="comment">选中后表示启用IP名单同步包括来自管理员、用户添加的IP名单以及其他节点系统自动拦截的IP名单。</p>
</td>
</tr>
<tr>
<td>启用节点</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="node.isOn"/>
<label></label>
</div>
<p class="comment">如果不启用此节点,此节点上的所有网站将不能访问。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>
</div>

View File

@@ -0,0 +1,37 @@
Tea.context(function () {
this.clusterId = 0
if (this.node.cluster != null && this.node.cluster.id > 0) {
this.clusterId = this.node.cluster.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.changeClusters = function (info) {
this.clusterId = info.clusterId
}
/**
* 集群相关
*/
this.showClustersBox = false
this.updateClusters = function () {
this.showClustersBox = !this.showClustersBox
}
/**
* 级别相关
*/
this.nodeLevel = this.node.level
this.changeLevel = function (level) {
this.nodeLevel = level
}
})

View File

@@ -0,0 +1,41 @@
{$layout "layout_popup"}
<h3>修改节点DNS设置</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="nodeId" :value="nodeId"/>
<input type="hidden" name="domainId" :value="domainId"/>
<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>
<input type="text" name="ipAddr" maxlength="64" ref="focus" v-model="ipAddr"/>
<p class="comment">用于域名解析的节点IP地址。</p>
</td>
</tr>
<tr v-if="domainId > 0">
<td>线路</td>
<td>
<p class="comment" v-if="allRoutes.length == 0">没有可选的线路。<a href="" @click.prevent="syncDomain(domainId)">[获取线路]</a></p>
<div v-if="allRoutes.length > 0">
<dns-route-selector :v-all-routes="allRoutes" :v-routes="routes"></dns-route-selector>
<p class="comment">当前节点IP对应的线路。<a href="" @click.prevent="syncDomain(domainId)">[重新获取线路]</a></p>
</div>
</td>
</tr>
<tr v-if="domainId == 0">
<td>线路</td>
<td><span class="disabled">当前集群没有选择域名。</span></td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,13 @@
Tea.context(function () {
this.syncDomain = function (domainId) {
this.$post(".syncDomain")
.params({
domainId: domainId
})
.success(function () {
teaweb.success("从服务商获取线路成功", function () {
window.location.reload()
})
})
}
})