Initial commit (code only without large binaries)
This commit is contained in:
7
EdgeAdmin/web/views/@default/clusters/@menu.html
Normal file
7
EdgeAdmin/web/views/@default/clusters/@menu.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<first-menu>
|
||||
<menu-item href="/clusters" code="index">集群 <span class="small">({{totalNodeClusters}})</span></menu-item>
|
||||
<menu-item href="/clusters/nodes" code="node">节点 <span class="small">({{totalNodes}})</span></menu-item>
|
||||
<span class="disabled item">|</span>
|
||||
<menu-item href="/clusters/create" code="create">[创建集群]</menu-item>
|
||||
<menu-item href="/clusters/createNode" code="createNode">[创建节点]</menu-item>
|
||||
</first-menu>
|
||||
@@ -0,0 +1,9 @@
|
||||
<first-menu>
|
||||
<menu-item href="/clusters/anti-ddos" code="index">高防产品</menu-item>
|
||||
<menu-item href="/clusters/anti-ddos/instances" code="instance">高防实例</menu-item>
|
||||
<span class="item disabled">|</span>
|
||||
<menu-item href="/clusters/anti-ddos/networks" code="network">线路选项</menu-item>
|
||||
<menu-item href="/clusters/anti-ddos/periods" code="period">有效期选项</menu-item>
|
||||
<span class="item disabled">|</span>
|
||||
<menu-item href="/clusters/anti-ddos/user-instances" code="userPackage">用户实例</menu-item>
|
||||
</first-menu>
|
||||
@@ -0,0 +1,53 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>添加高防产品</h3>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">所属线路 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="networkId">
|
||||
<option value="0">[选择线路]</option>
|
||||
<option v-for="network in networks" :value="network.id">{{network.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>防护带宽 *</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="protectionBandwidthSize" size="4" maxlength="4"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="protectionBandwidthUnit">
|
||||
<option v-for="unit in protectionUnitOptions" :value="unit.code">{{unit.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment">只作为展示作用。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>业务带宽 *</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="serverBandwidthSize" size="4" maxlength="4"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="serverBandwidthUnit">
|
||||
<option v-for="unit in serverUnitOptions" :value="unit.code">{{unit.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment">只作为展示作用。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
77
EdgeAdmin/web/views/@default/clusters/anti-ddos/index.html
Normal file
77
EdgeAdmin/web/views/@default/clusters/anti-ddos/index.html
Normal file
@@ -0,0 +1,77 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<div v-show="networks.length == 0">
|
||||
<not-found-box>还没有线路,请先<a href="/clusters/anti-ddos/networks">创建线路</a>。</not-found-box>
|
||||
</div>
|
||||
|
||||
<div v-show="networks.length > 0">
|
||||
<first-menu>
|
||||
<menu-item @click.prevent="createPackage()">[添加高防产品]</menu-item>
|
||||
</first-menu>
|
||||
|
||||
<div v-show="networks.length > 1">
|
||||
<div class="margin"></div>
|
||||
<form class="ui form" method="get" action="/clusters/anti-ddos">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="networkId" v-model="networkId">
|
||||
<option value="0">[所有线路]</option>
|
||||
<option v-for="network in networks" :value="network.id">{{network.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button" type="submit">搜索</button>
|
||||
|
||||
<a href="/clusters/anti-ddos" v-if="networkId > 0">[清除条件]</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-if="packages.length == 0">
|
||||
<p class="comment">暂时还没有高防产品。</p>
|
||||
</div>
|
||||
|
||||
<div v-if="packages.length > 0">
|
||||
<div class="margin"></div>
|
||||
<table class="ui table selectable celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>所属线路</th>
|
||||
<th>防护带宽</th>
|
||||
<th>业务带宽</th>
|
||||
<th>实例</th>
|
||||
<th class="width10">价格项</th>
|
||||
<th class="width5">状态</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="p in packages">
|
||||
<td :class="{disabled: !p.network.isOn || !p.isOn}">{{p.network.name}}
|
||||
<div v-if="!p.network.isOn">
|
||||
<span class="red small">已停用</span>
|
||||
</div>
|
||||
</td>
|
||||
<td :class="{disabled: !p.network.isOn || !p.isOn}">{{p.protectionBandwidthSize}}{{p.protectionBandwidthUnit.toBitUpper()}}</td>
|
||||
<td :class="{disabled: !p.network.isOn || !p.isOn}">{{p.serverBandwidthSize}}{{p.serverBandwidthUnit.toBitUpper()}}</td>
|
||||
<td>
|
||||
<a :href="'/clusters/anti-ddos/instances?selectedPackageId=' + p.id">{{p.countInstances}}</a>
|
||||
</td>
|
||||
<td :class="{disabled: !p.network.isOn || !p.isOn}">
|
||||
<a href="" @click.prevent="updatePrices(p.id)">
|
||||
<span class="" v-if="p.countPrices > 0">{{p.countPrices}}<span class="grey">/{{totalPriceItems}}</span></span>
|
||||
<span class="" v-else>[设置价格]</span>
|
||||
</a>
|
||||
</td>
|
||||
<td><label-on :v-is-on="p.isOn"></label-on></td>
|
||||
<td>
|
||||
<a href="" @click.prevent="updatePackage(p.id)">修改</a>
|
||||
<a href="" @click.prevent="deletePackage(p.id)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<page-box></page-box>
|
||||
</div>
|
||||
</div>
|
||||
46
EdgeAdmin/web/views/@default/clusters/anti-ddos/index.js
Normal file
46
EdgeAdmin/web/views/@default/clusters/anti-ddos/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
Tea.context(function () {
|
||||
this.createPackage = function () {
|
||||
teaweb.popup("/clusters/anti-ddos/createPopup", {
|
||||
height: "26em",
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.updatePackage = function (packageId) {
|
||||
teaweb.popup("/clusters/anti-ddos/updatePopup?packageId=" + packageId, {
|
||||
height: "26em",
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.deletePackage = function (packageId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除此高防产品吗?", function () {
|
||||
that.$post(".delete")
|
||||
.params({
|
||||
packageId: packageId
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.successRefresh("删除成功")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.updatePrices = function (packageId) {
|
||||
teaweb.popup("/clusters/anti-ddos/updatePricesPopup?packageId=" + packageId, {
|
||||
height: "30em",
|
||||
onClose: function () {
|
||||
teaweb.reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
String.prototype.toBitUpper = function () {
|
||||
let unit = this
|
||||
return unit.replace(/bps$/, "").replace(/b$/, "").toUpperCase() + "bps"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>创建高防实例</h3>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<table class="ui table definition selectable">
|
||||
<tr v-if="packageId > 0">
|
||||
<td>所属产品</td>
|
||||
<td>{{packageSummary}}
|
||||
<input type="hidden" name="packageId" :value="packageId"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody v-if="packageId == 0">
|
||||
<tr>
|
||||
<td>所属线路 *</td>
|
||||
<td>
|
||||
<div v-if="networks.length > 0">
|
||||
<select class="ui dropdown auto-width" name="networkId" v-model="networkId">
|
||||
<option value="0">[选择线路]</option>
|
||||
<option v-for="network in networks" :value="network.id">{{network.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<span class="red" v-if="networks.length == 0">尚未创建线路和产品。</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>所属产品 *</td>
|
||||
<td>
|
||||
<div v-show="networkId > 0">
|
||||
<select class="ui dropdown auto-width" name="packageId">
|
||||
<option value="0">[选择产品]</option>
|
||||
<option v-for="p in packages" :value="p.id" v-if="p.networkId == networkId">{{p.summary}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<span v-if="networkId == 0" class="grey">请先选择线路</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td class="title">部署集群 *</td>
|
||||
<td>
|
||||
<node-cluster-combo-box></node-cluster-combo-box>
|
||||
<p class="comment">用于防护的网站部署的集群。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>高防IP *</td>
|
||||
<td>
|
||||
<values-box name="ipAddresses" placeholder="x.x.x.x" maxlength="64" :validator="ipValidator"></values-box>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<submit-btn>保存</submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,13 @@
|
||||
Tea.context(function () {
|
||||
this.ipValidator = function (ip) {
|
||||
if (teaweb.validateIP(ip)) {
|
||||
return {
|
||||
isOk: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
isOk: false,
|
||||
message: "请输入正确的IP"
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,92 @@
|
||||
{$layout}
|
||||
{$template "../menu"}
|
||||
|
||||
<second-menu>
|
||||
<menu-item v-if="selectedPackage != null && selectedPackage.id > 0"><span class="ui label" style="font-weight: normal">{{selectedPackage.summary}}</span> <a href="/clusters/anti-ddos/instances" title="清除条件"><i class="icon remove small"></i></a></menu-item>
|
||||
<span class="item disabled" v-if="selectedPackage != null && selectedPackage.id > 0" style="padding-left: 0">|</span>
|
||||
<menu-item @click.prevent="createInstance()">[创建高防实例]</menu-item>
|
||||
</second-menu>
|
||||
|
||||
<div v-show="selectedPackageId == 0">
|
||||
<form class="ui form" action="/clusters/anti-ddos/instances" method="get">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="networkId" v-model="networkId">
|
||||
<option value="0">[线路]</option>
|
||||
<option v-for="network in networks" :value="network.id">{{network.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<input type="text" name="ip" placeholder="高防IP" v-model="ip" maxlength="64"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button type="submit" class="ui button">搜索</button>
|
||||
|
||||
<a href="/clusters/anti-ddos/instances" v-if="networkId > 0 || ip.length > 0">[清除缓存]</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="margin"></div>
|
||||
</div>
|
||||
|
||||
<div v-if="instances.length == 0">
|
||||
<p class="comment">暂时还没有高防实例。</p>
|
||||
</div>
|
||||
|
||||
<div v-if="instances.length > 0">
|
||||
<table class="ui table selectable celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>所属线路</th>
|
||||
<th>防护带宽</th>
|
||||
<th>业务带宽</th>
|
||||
<th>部署集群</th>
|
||||
<th class="three wide">高防IP</th>
|
||||
<th class="three wide">租用用户</th>
|
||||
<th class="width5">状态</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="instance in instances">
|
||||
<td>
|
||||
<span v-if="instance.network != null && instance.network.id > 0">{{instance.network.name}}</span>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="instance.package != null && instance.package.id > 0">
|
||||
{{instance.package.protectionBandwidthSize}}
|
||||
{{instance.package.protectionBandwidthUnit.toBitUpper()}}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="instance.package != null && instance.package.id > 0">
|
||||
{{instance.package.serverBandwidthSize}}
|
||||
{{instance.package.serverBandwidthUnit.toBitUpper()}}
|
||||
</div>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="instance.cluster != null && instance.cluster.id > 0">{{instance.cluster.name}}</span>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
<td>
|
||||
<div v-for="ipAddr in instance.ipAddresses"><keyword :v-word="ip">{{ipAddr}}</keyword></div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="instance.user != null && instance.user.id > 0"><user-link :v-user="instance.user"></user-link>
|
||||
<div>
|
||||
<span class="small grey">到{{instance.userDayTo}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="disabled">暂无</span>
|
||||
</td>
|
||||
<td><label-on :v-is-on="instance.isOn"></label-on></td>
|
||||
<td>
|
||||
<a href="" @click.prevent="updateInstance(instance.id)">修改</a>
|
||||
<a href="" @click.prevent="deleteInstance(instance.id)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<page-box></page-box>
|
||||
</div>
|
||||
@@ -0,0 +1,44 @@
|
||||
Tea.context(function () {
|
||||
this.createInstance = function () {
|
||||
let packageId = 0
|
||||
if (this.selectedPackage != null) {
|
||||
packageId = this.selectedPackage.id
|
||||
} else if (this.packageId > 0) {
|
||||
packageId = this.packageId
|
||||
}
|
||||
|
||||
teaweb.popup(".createPopup?networkId=" + this.networkId + "&packageId=" + packageId, {
|
||||
height: "24em",
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.updateInstance = function (instanceId) {
|
||||
teaweb.popup(".instance.updatePopup?instanceId=" + instanceId, {
|
||||
height: "24em",
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.deleteInstance = function (instanceId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除当前实例吗?", function () {
|
||||
that.$post(".instance.delete")
|
||||
.params({
|
||||
instanceId: instanceId
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.successRefresh("删除成功")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
String.prototype.toBitUpper = function () {
|
||||
let unit = this
|
||||
return unit.replace(/bps$/, "").replace(/b$/, "").toUpperCase() + "bps"
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>修改高防实例</h3>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="instanceId" :value="instanceId"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td>所属产品</td>
|
||||
<td>
|
||||
<span v-if="instance.package != null">{{instance.package.summary}}</span>
|
||||
<span v-else class="red">已被删除</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">部署集群 *</td>
|
||||
<td>
|
||||
<node-cluster-combo-box :v-cluster-id="instance.clusterId"></node-cluster-combo-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>高防IP *</td>
|
||||
<td>
|
||||
<values-box name="ipAddresses" placeholder="x.x.x.x" maxlength="64" :validator="ipValidator" :v-values="instance.ipAddresses"></values-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr>
|
||||
<td>启用当前实例</td>
|
||||
<td>
|
||||
<checkbox name="isOn" v-model="instance.isOn"></checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<submit-btn>保存</submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,13 @@
|
||||
Tea.context(function () {
|
||||
this.ipValidator = function (ip) {
|
||||
if (teaweb.validateIP(ip)) {
|
||||
return {
|
||||
isOk: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
isOk: false,
|
||||
message: "请输入正确的IP"
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>创建线路</h3>
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">线路名称 *</td>
|
||||
<td>
|
||||
<input type="text" name="name" ref="focus" maxlength="50"/>
|
||||
<p class="comment">类似于BGP线路、全球线路等。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>线路描述</td>
|
||||
<td>
|
||||
<textarea rows="3" name="description" maxlength="100"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,37 @@
|
||||
{$layout}
|
||||
{$template "../menu"}
|
||||
|
||||
<second-menu>
|
||||
<menu-item @click.prevent="createNetwork">[创建线路]</menu-item>
|
||||
</second-menu>
|
||||
|
||||
<p class="comment" v-if="networks.length == 0">暂时还没有线路。</p>
|
||||
|
||||
<div v-if="networks.length > 0">
|
||||
<table class="ui table selectable celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="three wide">线路名称</th>
|
||||
<th>描述</th>
|
||||
<th class="width10">状态</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-for="network in networks">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="" @click.prevent="updateNetwork(network.id)">{{network.name}} <i class="icon expand small"></i></a>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="network.description.length > 0">{{network.description}}</span>
|
||||
<span v-else class="disabled">暂时没有描述</span>
|
||||
</td>
|
||||
<td><label-on :v-is-on="network.isOn"></label-on></td>
|
||||
<td>
|
||||
<a href="" @click.prevent="updateNetwork(network.id)">修改</a>
|
||||
<a href="" @click.prevent="deleteNetwork(network.id)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
Tea.context(function () {
|
||||
this.createNetwork = function () {
|
||||
teaweb.popup(".createPopup", {
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.updateNetwork = function (networkId) {
|
||||
teaweb.popup(".network.updatePopup?networkId=" + networkId, {
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.deleteNetwork = function (networkId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除此线路吗?", function () {
|
||||
that.$post(".network.delete")
|
||||
.params({
|
||||
networkId: networkId
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.successRefresh("删除成功")
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>修改线路</h3>
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
|
||||
<input type="hidden" name="networkId" :value="network.id"/>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">线路名称 *</td>
|
||||
<td>
|
||||
<input type="text" name="name" ref="focus" maxlength="50" v-model="network.name"/>
|
||||
<p class="comment">类似于BGP线路、全球线路等。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>线路描述</td>
|
||||
<td>
|
||||
<textarea rows="3" name="description" maxlength="100" v-model="network.description"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr>
|
||||
<td>启用线路</td>
|
||||
<td>
|
||||
<checkbox name="isOn" v-model="network.isOn"></checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,26 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>添加有效期选项</h3>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">有效期选项 *</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="count" size="4" maxlength="4" ref="focus" placeholder="数量"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="unit">
|
||||
<option value="month">月</option>
|
||||
<option value="year">年</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,30 @@
|
||||
{$layout}
|
||||
{$template "../menu"}
|
||||
|
||||
<second-menu>
|
||||
<menu-item @click.prevent="createPeriod">[添加有效期选项]</menu-item>
|
||||
</second-menu>
|
||||
|
||||
<p class="comment" v-if="periods.length == 0">暂时还没有有效期选项设置。</p>
|
||||
|
||||
<table class="ui table celled selectable" v-if="periods.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>有效期选项</th>
|
||||
<th class="width6">状态</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="period in periods">
|
||||
<td>
|
||||
<a href="" @click.prevent="updatePeriod(period.id)">{{period.count}}{{period.unitName}} <i class="icon expand small"></i></a>
|
||||
</td>
|
||||
<td>
|
||||
<label-on :v-is-on="period.isOn"></label-on>
|
||||
</td>
|
||||
<td>
|
||||
<a href="" @click.prevent="updatePeriod(period.id)">修改</a>
|
||||
<a href="" @click.prevent="deletePeriod(period.id)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,32 @@
|
||||
Tea.context(function () {
|
||||
this.createPeriod = function () {
|
||||
teaweb.popup("/clusters/anti-ddos/periods/createPopup", {
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.updatePeriod = function (periodId) {
|
||||
teaweb.popup("/clusters/anti-ddos/periods/period/updatePopup?periodId=" + periodId, {
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.deletePeriod = function (periodId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除此有效期选项吗?", function () {
|
||||
that.$post("/clusters/anti-ddos/periods/period/delete")
|
||||
.params({
|
||||
periodId: periodId
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.success("删除成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>修改有效期选项</h3>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="periodId" :value="period.id"/>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">有效期选项</td>
|
||||
<td>{{period.count}}{{period.unitName}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>启用</td>
|
||||
<td>
|
||||
<checkbox name="isOn" v-model="period.isOn"></checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,65 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>修改高防产品</h3>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="packageId" :value="package.id"/>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">所属线路 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="networkId" v-model="package.networkId">
|
||||
<option value="0">[选择线路]</option>
|
||||
<option v-for="network in networks" :value="network.id">{{network.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>防护带宽 *</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="protectionBandwidthSize" size="4" maxlength="4" v-model="package.protectionBandwidthSize"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="protectionBandwidthUnit" v-model="package.protectionBandwidthUnit">
|
||||
<option v-for="unit in protectionUnitOptions" :value="unit.code">{{unit.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment">只作为展示作用。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>业务带宽 *</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="serverBandwidthSize" size="4" maxlength="4" v-model="package.serverBandwidthSize"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="serverBandwidthUnit" v-model="package.serverBandwidthUnit">
|
||||
<option v-for="unit in serverUnitOptions" :value="unit.code">{{unit.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment">只作为展示作用。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr>
|
||||
<td>启用当前高防产品</td>
|
||||
<td>
|
||||
<checkbox name="isOn" v-model="package.isOn"></checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,39 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>高防产品"防护{{package.protectionBandwidthSize}}{{package.protectionBandwidthUnit.toBitUpper()}}/业务{{package.serverBandwidthSize}}{{package.serverBandwidthUnit.toBitUpper()}}"价格</h3>
|
||||
|
||||
<div v-if="periods.length == 0">
|
||||
<p class="comment">暂时还没有有效期选项。</p>
|
||||
</div>
|
||||
|
||||
<div v-if="periods.length > 0">
|
||||
<table class="ui table celled selectable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="two wide">有效期</th>
|
||||
<th>价格</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-for="period in periods">
|
||||
<tr>
|
||||
<td>{{period.name}}</td>
|
||||
<td>
|
||||
<span v-if="prices[period.id] > 0">{{prices[period.id]}}元</span>
|
||||
<span v-else class="disabled">[无]</span>
|
||||
<div>
|
||||
<a href="" @click.prevent="editPrice(period.id)"><span class="small">[设置]</span></a>
|
||||
<div v-show="editingPeriodId == period.id">
|
||||
<div class="ui input small right labeled">
|
||||
<input type="text" size="6" maxlength="6" :ref="'input' + period.id" placeholder="价格" @keyup.enter="savePrice(period.id)" @keypress.enter.prevent="1"/>
|
||||
<span class="ui label">元</span>
|
||||
</div>
|
||||
<div style="margin-top: 0.6em">
|
||||
<button class="ui button tiny primary" type="button" @click.prevent="savePrice(period.id)">保存</button> <a href="" title="取消" @click.prevent="cancelEditing"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,72 @@
|
||||
Tea.context(function () {
|
||||
this.editingPeriodId = 0
|
||||
|
||||
this.editPrice = function (periodId) {
|
||||
this.editingPeriodId = periodId
|
||||
|
||||
let refs = this.$refs
|
||||
if (typeof refs == "object") {
|
||||
for (let k in refs) {
|
||||
if (typeof k == "string" && k == "input" + periodId) {
|
||||
let inputs = refs[k]
|
||||
if (inputs.length > 0) {
|
||||
setTimeout(function () {
|
||||
inputs[0].focus()
|
||||
}, 10)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.cancelEditing = function () {
|
||||
this.editingPeriodId = 0
|
||||
}
|
||||
|
||||
this.savePrice = function (periodId) {
|
||||
let refs = this.$refs
|
||||
let price = -1
|
||||
if (typeof refs == "object") {
|
||||
for (let k in refs) {
|
||||
if (typeof k == "string" && k == "input" + periodId) {
|
||||
let inputs = refs[k]
|
||||
if (inputs.length > 0) {
|
||||
let input = inputs[0]
|
||||
let newPrice = parseFloat(input.value)
|
||||
if (isNaN(newPrice) || newPrice < 0) {
|
||||
teaweb.warn("请输入一个正确的数字", function () {
|
||||
input.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
price = newPrice
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (price < 0) {
|
||||
teaweb.warn("请输入一个正确的数字", function () {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.$post("/clusters/anti-ddos/updatePrice")
|
||||
.params({
|
||||
packageId: this.package.id,
|
||||
periodId: periodId,
|
||||
price: price
|
||||
})
|
||||
.success(function () {
|
||||
this.editingPeriodId = 0
|
||||
this.prices[periodId] = price
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
String.prototype.toBitUpper = function () {
|
||||
let unit = this
|
||||
return unit.replace(/bps$/, "").replace(/b$/, "").toUpperCase() + "bps"
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>添加高防实例</h3>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
|
||||
<input type="hidden" name="packageId" :value="selectedPackageId"/>
|
||||
<input type="hidden" name="periodId" :value="selectedPeriodId"/>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td>选择用户 *</td>
|
||||
<td>
|
||||
<user-selector></user-selector>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">选择线路 *</td>
|
||||
<td>
|
||||
<a v-for="network in allNetworks" class="ui label basic" :class="{blue: network.id == selectedNetworkId}" @click.prevent="selectNetwork(network.id)">{{network.name}}</a>
|
||||
<p class="comment" v-for="network in allNetworks" v-if="network.id == selectedNetworkId && network.description.length > 0">{{network.name}}:{{network.description}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>选择防护带宽 *</td>
|
||||
<td>
|
||||
<a v-for="protectionBandwidth in allProtectionBandwidthSizes" v-if="hasProtectionBandwidth(protectionBandwidth)" class="ui label basic" :class="{blue: protectionBandwidth == selectedProtectionBandwidth}" @click.prevent="selectProtectionBandwidth(protectionBandwidth)">{{protectionBandwidth}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>选择业务带宽 *</td>
|
||||
<td>
|
||||
<a v-for="serverBandwidth in allServerBandwidthSizes" v-if="hasServerBandwidth(serverBandwidth)" class="ui label basic" :class="{blue: serverBandwidth == selectedServerBandwidth}" @click.prevent="selectServerBandwidth(serverBandwidth)">{{serverBandwidth}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>选择有效期 *</td>
|
||||
<td>
|
||||
<a v-for="period in allPeriods" class="ui label basic" v-if="hasPeriod(period.id)" :class="{blue: period.id == selectedPeriodId}" @click.prevent="selectPeriod(period.id)">{{period.count}}{{period.unitName}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>选择实例数量 *</td>
|
||||
<td>
|
||||
<div class="ui input">
|
||||
<select class="ui dropdown" name="count" v-model="count" @change="changeCount(this.count)">
|
||||
<option v-for="i in max" :value="i">{{i}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>价格</td>
|
||||
<td>
|
||||
<span v-if="amount == 0" class="disabled">没有找到对应价格</span>
|
||||
<span v-if="amount > 0">{{amount}}元</span>
|
||||
<p class="comment">管理员操作时,此价格仅供展示,并不会从用户账户中扣款。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn :class="{disabled: amount <= 0}"></submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,213 @@
|
||||
Tea.context(function () {
|
||||
this.selectedNetworkId = 0
|
||||
this.selectedProtectionBandwidth = ""
|
||||
this.selectedServerBandwidth = ""
|
||||
this.selectedPackageId = 0
|
||||
this.selectedPeriodId = 0
|
||||
this.amount = -1
|
||||
this.max = 1
|
||||
this.count = 1
|
||||
this.selectedPrice = null
|
||||
|
||||
this.$delay(function () {
|
||||
if (this.allNetworks.length > 0) {
|
||||
this.selectNetwork(this.allNetworks[0].id)
|
||||
}
|
||||
})
|
||||
|
||||
this.selectNetwork = function (networkId) {
|
||||
this.selectedNetworkId = networkId
|
||||
this.selectedProtectionBandwidth = ""
|
||||
this.selectedServerBandwidth = ""
|
||||
this.selectedPeriodId = 0
|
||||
this.amount = -1
|
||||
|
||||
// 选择第一个防护带宽
|
||||
let that = this
|
||||
let found = false
|
||||
this.prices.sort(function (v1, v2) {
|
||||
return that.compareBits(v1.protectionBandwidth, v2.protectionBandwidth)
|
||||
}).forEach(function (v) {
|
||||
if (!found && v.networkId == that.selectedNetworkId) {
|
||||
that.selectProtectionBandwidth(v.protectionBandwidth)
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.selectProtectionBandwidth = function (protectionBandwidth) {
|
||||
this.selectedProtectionBandwidth = protectionBandwidth
|
||||
this.selectedServerBandwidth = ""
|
||||
this.selectedPeriodId = 0
|
||||
this.amount = -1
|
||||
|
||||
// 选择第一个业务带宽
|
||||
let that = this
|
||||
let found = false
|
||||
this.prices.sort(function (v1, v2) {
|
||||
return that.compareBits(v1.serverBandwidth, v2.serverBandwidth)
|
||||
}).forEach(function (v) {
|
||||
if (!found && v.networkId == that.selectedNetworkId && v.protectionBandwidth == protectionBandwidth) {
|
||||
that.selectServerBandwidth(v.serverBandwidth)
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.selectServerBandwidth = function (serverBandwidth) {
|
||||
this.selectedServerBandwidth = serverBandwidth
|
||||
this.selectedPeriodId = 0
|
||||
this.amount = -1
|
||||
|
||||
// 选择第一个有效期
|
||||
let that = this
|
||||
let found = false
|
||||
|
||||
this.prices.sort(function (v1, v2) {
|
||||
let periodId1 = v1.periodId
|
||||
let periodId2 = v2.periodId
|
||||
return (that.toPeriodMonths(that.findPeriodWithId(periodId1)) > that.toPeriodMonths(that.findPeriodWithId(periodId2))) ? 1 : -1
|
||||
}).forEach(function (v) {
|
||||
if (!found && v.networkId == that.selectedNetworkId && v.protectionBandwidth == that.selectedProtectionBandwidth && v.serverBandwidth == serverBandwidth) {
|
||||
that.selectPeriod(v.periodId)
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.selectPeriod = function (periodId) {
|
||||
this.selectedPeriodId = periodId
|
||||
|
||||
let price = null
|
||||
let found = false
|
||||
let that = this
|
||||
this.prices.forEach(function (v) {
|
||||
if (!found && v.networkId == that.selectedNetworkId
|
||||
&& v.protectionBandwidth == that.selectedProtectionBandwidth
|
||||
&& v.serverBandwidth == that.selectedServerBandwidth
|
||||
&& v.periodId == that.selectedPeriodId) {
|
||||
price = v
|
||||
found = true
|
||||
}
|
||||
})
|
||||
if (price == null) {
|
||||
teaweb.warn("数据错误,请刷新页面后重试,如果仍然没有恢复,请联系管理员。")
|
||||
return
|
||||
}
|
||||
|
||||
this.selectedPrice = price
|
||||
|
||||
this.selectedPackageId = price.packageId
|
||||
this.count = 1
|
||||
this.max = price.maxInstances
|
||||
this.changeCount(this.count)
|
||||
}
|
||||
|
||||
this.hasProtectionBandwidth = function (protectionBandwidth) {
|
||||
if (this.prices == null) {
|
||||
return
|
||||
}
|
||||
|
||||
let found = false
|
||||
let that = this
|
||||
this.prices.forEach(function (v) {
|
||||
if (v.networkId == that.selectedNetworkId && v.protectionBandwidth == protectionBandwidth) {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
this.hasServerBandwidth = function (serverBandwidth) {
|
||||
if (this.prices == null) {
|
||||
return
|
||||
}
|
||||
|
||||
let found = false
|
||||
let that = this
|
||||
this.prices.forEach(function (v) {
|
||||
if (v.networkId == that.selectedNetworkId
|
||||
&& v.protectionBandwidth == that.selectedProtectionBandwidth
|
||||
&& v.serverBandwidth == serverBandwidth) {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
this.hasPeriod = function (periodId) {
|
||||
if (this.prices == null) {
|
||||
return
|
||||
}
|
||||
|
||||
let found = false
|
||||
let that = this
|
||||
this.prices.forEach(function (v) {
|
||||
if (v.networkId == that.selectedNetworkId
|
||||
&& v.protectionBandwidth == that.selectedProtectionBandwidth
|
||||
&& v.serverBandwidth == that.selectedServerBandwidth
|
||||
&& v.periodId == periodId) {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
this.changeCount = function () {
|
||||
if (this.selectedPrice == null) {
|
||||
this.amount = -1
|
||||
return
|
||||
}
|
||||
this.amount = this.selectedPrice.price * this.count
|
||||
|
||||
// 从服务器获取最新价格
|
||||
this.$post(".price")
|
||||
.params({
|
||||
packageId: this.selectedPackageId,
|
||||
periodId: this.selectedPeriodId,
|
||||
count: this.count
|
||||
})
|
||||
.success(function (resp) {
|
||||
this.amount = resp.data.amount
|
||||
})
|
||||
}
|
||||
|
||||
this.toBits = function (b) {
|
||||
let m = b.match(/^(\d+)(\w+)$/)
|
||||
let n = parseInt(m[1])
|
||||
switch (m[2]) {
|
||||
case "bps":
|
||||
return n
|
||||
case "Kbps":
|
||||
return n * 1024
|
||||
case "Mbps":
|
||||
return n * Math.pow(1024, 2)
|
||||
case "Gbps":
|
||||
return n * Math.pow(1024, 3)
|
||||
case "Tbps":
|
||||
return n * Math.pow(1024, 4)
|
||||
case "Pbps":
|
||||
return n * Math.pow(1024, 5)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
this.compareBits = function (b1, b2) {
|
||||
return (this.toBits(b1) > this.toBits(b2)) ? 1 : -1
|
||||
}
|
||||
|
||||
this.findPeriodWithId = function (periodId) {
|
||||
return this.allPeriods.$find(function (k, v) {
|
||||
return v.id == periodId
|
||||
})
|
||||
}
|
||||
|
||||
this.toPeriodMonths = function (period) {
|
||||
switch (period.unit) {
|
||||
case "year":
|
||||
return period.count * 12
|
||||
default:
|
||||
return period.count
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
{$layout}
|
||||
{$template "../menu"}
|
||||
|
||||
<second-menu>
|
||||
<menu-item @click.prevent="createUserInstance">[添加用户实例]</menu-item>
|
||||
</second-menu>
|
||||
|
||||
<p class="comment" v-if="userInstances.length == 0">暂时还没有用户实例。</p>
|
||||
|
||||
<div v-if="userInstances.length > 0">
|
||||
<table class="ui table selectable celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>用户</th>
|
||||
<th>高防产品</th>
|
||||
<th>高防IP</th>
|
||||
<th style="width: 7em">有效期</th>
|
||||
<th style="width: 7em">开始日期</th>
|
||||
<th style="width: 7em">结束日期</th>
|
||||
<th class="width6">防护对象</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="userInstance in userInstances">
|
||||
<td><user-link :v-user="userInstance.user"></user-link></td>
|
||||
<td>
|
||||
<span v-if="userInstance.package.id > 0">{{userInstance.package.summary}}</span>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="userInstance.ipAddresses != null && userInstance.ipAddresses.length > 0">
|
||||
<div v-for="ip in userInstance.ipAddresses">
|
||||
{{ip}}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="red">无有效IP</span>
|
||||
</td>
|
||||
<td>{{userInstance.periodCount}}{{userInstance.periodUnitName}}</td>
|
||||
<td>{{userInstance.dayFrom}}</td>
|
||||
<td>{{userInstance.dayTo}}
|
||||
<div v-if="userInstance.isExpired">
|
||||
<span v-if="userInstance.isExpired" class="small red">已过期</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<!-- 防护对象 -->
|
||||
<a href="" v-if="userInstance.isAvailable" @click.prevent="updateObjectsPopup(userInstance.id)">
|
||||
<span v-if="userInstance.countObjects > 0">{{userInstance.countObjects}}个对象</span>
|
||||
<span v-else>设置</span>
|
||||
</a>
|
||||
<span v-else class="disabled">{{userInstance.countObjects}}</span>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<!-- 续费 -->
|
||||
<span v-if="userInstance.instance.userInstanceId == userInstance.id"><a href="" @click.prevent="renewUserInstance(userInstance.id)">续期</a></span>
|
||||
<span v-else class="disabled">续期</span>
|
||||
|
||||
<!-- 删除 -->
|
||||
<a href="" v-if="userInstance.canDelete" @click.prevent="deleteUserInstance(userInstance.id)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<page-box></page-box>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
Tea.context(function () {
|
||||
this.createUserInstance = function () {
|
||||
teaweb.popup(".createPopup", {
|
||||
height: "32em",
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.renewUserInstance = function (userInstanceId) {
|
||||
teaweb.popup(".renewPopup?userInstanceId=" + userInstanceId, {
|
||||
height: "26em",
|
||||
callback: function () {
|
||||
teaweb.successRefresh("续期成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.updateObjectsPopup = function (userInstanceId) {
|
||||
teaweb.popup(".updateObjectsPopup?userInstanceId=" + userInstanceId, {
|
||||
width: "60em",
|
||||
height: "40em",
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.deleteUserInstance = function (userInstanceId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除此用户实例吗?", function () {
|
||||
that.$post(".delete")
|
||||
.params({
|
||||
userInstanceId: userInstanceId
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.successRefresh("删除成功")
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,60 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>续期实例</h3>
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="userInstanceId" :value="userInstance.id"/>
|
||||
<input type="hidden" name="periodId" :value="selectedPeriodId"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">用户</td>
|
||||
<td>
|
||||
<div v-if="userInstance.user.id > 0"><user-link :v-user="userInstance.user"></user-link></div>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>高防产品</td>
|
||||
<td>
|
||||
<div v-if="userInstance.package.id > 0">{{userInstance.package.summary}}</div>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>高防IP</td>
|
||||
<td>
|
||||
<div v-if="userInstance.instance.id > 0">
|
||||
<div v-for="ip in userInstance.instance.ipAddresses">{{ip}}</div>
|
||||
</div>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>当前有效期</td>
|
||||
<td>{{userInstance.dayTo}}
|
||||
<p class="comment" v-if="userInstance.isExpired">
|
||||
<span class="red">已过期,续期后新的有效期将会从今天({{userInstance.today}})开始。</span>
|
||||
</p>
|
||||
<p class="comment" v-else>续期后,将会在此基础上增加新的有效期。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>新续有效期 *</td>
|
||||
<td>
|
||||
<div v-if="allPeriods.length > 0">
|
||||
<a v-for="period in allPeriods" class="ui label basic" :class="{blue: period.id == selectedPeriodId}" @click.prevent="selectPeriod(period.id)">{{period.count}}{{period.unitName}}</a>
|
||||
</div>
|
||||
<span v-else class="red">没有可用的有效期</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>价格</td>
|
||||
<td>
|
||||
<span v-if="amount == 0" class="disabled">没有找到对应价格</span>
|
||||
<span v-if="amount > 0">{{amount}}元</span>
|
||||
<p class="comment">管理员操作时,此价格仅供展示,并不会从用户账户中扣款。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn v-if="amount > 0">续期</submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,23 @@
|
||||
Tea.context(function () {
|
||||
this.selectedPeriodId = 0
|
||||
this.amount = -1
|
||||
|
||||
this.$delay(function () {
|
||||
if (this.userInstance.periodId > 0) {
|
||||
let that = this
|
||||
if (this.allPeriods.$find(function (k, v) {
|
||||
return that.userInstance.periodId == v.id
|
||||
}) != null) {
|
||||
this.selectPeriod(this.userInstance.periodId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.selectPeriod = function (periodId) {
|
||||
this.selectedPeriodId = periodId
|
||||
|
||||
this.amount = this.prices.$find(function (k, v) {
|
||||
return v.periodId == periodId
|
||||
}).price
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>设置防护对象</h3>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="userInstanceId" :value="userInstance.id"/>
|
||||
|
||||
<table class="ui table selectable definition">
|
||||
<tr>
|
||||
<td class="title">用户</td>
|
||||
<td>
|
||||
<div v-if="userInstance.user.id > 0"><user-link :v-user="userInstance.user"></user-link></div>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>高防产品</td>
|
||||
<td>
|
||||
<div v-if="userInstance.package.id > 0">{{userInstance.package.summary}}</div>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>高防IP</td>
|
||||
<td>
|
||||
<div v-if="userInstance.instance.id > 0">
|
||||
<div v-for="ip in userInstance.instance.ipAddresses">{{ip}}</div>
|
||||
</div>
|
||||
<span v-else class="red">已删除</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>防护对象</td>
|
||||
<td>
|
||||
<div v-if="userInstance.isAvailable && userInstance.user.id > 0">
|
||||
<ad-instance-objects-box :v-objects="userInstance.objects" :v-user-id="userInstance.user.id"></ad-instance-objects-box>
|
||||
</div>
|
||||
<span v-else class="red">实例已经失效,无法设置防护对象</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<submit-btn v-show="userInstance.isAvailable && userInstance.user.id > 0"></submit-btn>
|
||||
</form>
|
||||
10
EdgeAdmin/web/views/@default/clusters/cluster/@menu.html
Normal file
10
EdgeAdmin/web/views/@default/clusters/cluster/@menu.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<second-menu>
|
||||
<menu-item :href="'/clusters/cluster?clusterId=' + clusterId">{{currentClusterName}}</menu-item>
|
||||
<span class="disabled item" style="padding: 0">»</span>
|
||||
<menu-item :href="'/clusters/cluster/nodes?clusterId=' + clusterId" code="index">节点列表</menu-item>
|
||||
<span class="disabled item">|</span>
|
||||
<menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">[创建节点]</menu-item>
|
||||
<span class="disabled item">|</span>
|
||||
<menu-item :href="'/clusters/cluster/installManual?clusterId=' + clusterId" code="install">安装升级</menu-item>
|
||||
<menu-item :href="'/clusters/cluster/groups?clusterId=' + clusterId" code="group">节点分组</menu-item>
|
||||
</second-menu>
|
||||
@@ -0,0 +1,11 @@
|
||||
.message.loading {
|
||||
padding: 1.5em;
|
||||
}
|
||||
.chart-box {
|
||||
height: 14em;
|
||||
}
|
||||
h4 span {
|
||||
font-size: 0.8em;
|
||||
color: grey;
|
||||
}
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,QAAQ;EACP,cAAA;;AAGD;EACC,YAAA;;AAGD,EACC;EACC,gBAAA;EACA,WAAA","file":"index.css"}
|
||||
111
EdgeAdmin/web/views/@default/clusters/cluster/boards/index.html
Normal file
111
EdgeAdmin/web/views/@default/clusters/cluster/boards/index.html
Normal file
@@ -0,0 +1,111 @@
|
||||
{$layout}
|
||||
{$template "/echarts"}
|
||||
|
||||
<second-menu>
|
||||
<menu-item :href="'/clusters/cluster?clusterId=' + clusterId">{{currentClusterName}}</menu-item>
|
||||
<span class="disabled item" style="padding: 0">»</span>
|
||||
<span class="item active">看板</span>
|
||||
</second-menu>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<div style="margin-top: 0.8em">
|
||||
<div class="ui message loading" v-if="isLoading">
|
||||
<div class="ui active inline loader small"></div> 数据加载中...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<columns-grid v-show="!isLoading">
|
||||
<div class="ui column">
|
||||
<h4>在线节点<link-icon :href="'/clusters/cluster/nodes?clusterId=' + clusterId"></link-icon></h4>
|
||||
<div class="value"><span class="green">{{board.countActiveNodes}}</span>个</div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>离线节点<link-icon :href="'/clusters/cluster/nodes?clusterId=' + clusterId"></link-icon></h4>
|
||||
<div class="value"><span :class="{red: board.countInactiveNodes > 0}">{{board.countInactiveNodes}}</span>个</div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>网站</h4>
|
||||
<div class="value"><span>{{board.countServers}}</span>个</div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>用户</h4>
|
||||
<div class="value"><span>{{board.countUsers}}</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">
|
||||
<!-- 节点排行 -->
|
||||
<h4 v-show="!isLoading">节点访问排行 <span>(24小时)</span></h4>
|
||||
<div class="ui divider"></div>
|
||||
<div class="chart-box" id="top-nodes-chart"></div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<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>
|
||||
|
||||
<!-- 指标 -->
|
||||
<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>
|
||||
478
EdgeAdmin/web/views/@default/clusters/cluster/boards/index.js
Normal file
478
EdgeAdmin/web/views/@default/clusters/cluster/boards/index.js
Normal file
@@ -0,0 +1,478 @@
|
||||
Tea.context(function () {
|
||||
this.isLoading = true
|
||||
this.board = {}
|
||||
this.metricCharts = []
|
||||
|
||||
this.monthlyTrafficFormat = ["0", "B"]
|
||||
this.todayTrafficFormat = ["0", "B"]
|
||||
this.yesterdayTrafficFormat = ["0", "B"]
|
||||
|
||||
/**
|
||||
* 流量统计
|
||||
*/
|
||||
this.trafficTab = "hourly"
|
||||
|
||||
this.$delay(function () {
|
||||
this.$post("$")
|
||||
.params({
|
||||
clusterId: this.clusterId
|
||||
})
|
||||
.timeout(30)
|
||||
.success(function (resp) {
|
||||
// 当月、今日及昨日流量
|
||||
if (resp.data.monthlyTraffic.length > 0) {
|
||||
this.monthlyTrafficFormat = teaweb.splitFormat(resp.data.monthlyTraffic)
|
||||
}
|
||||
if (resp.data.todayTraffic.length > 0) {
|
||||
this.todayTrafficFormat = teaweb.splitFormat(resp.data.todayTraffic)
|
||||
}
|
||||
if (resp.data.yesterdayTraffic.length > 0) {
|
||||
this.yesterdayTrafficFormat = teaweb.splitFormat(resp.data.yesterdayTraffic)
|
||||
}
|
||||
|
||||
for (let k in resp.data) {
|
||||
this[k] = resp.data[k]
|
||||
}
|
||||
|
||||
this.reloadHourlyTrafficChart()
|
||||
this.reloadHourlyRequestsChart()
|
||||
this.reloadTopNodesChart()
|
||||
this.reloadCPUChart()
|
||||
|
||||
this.isLoading = false
|
||||
|
||||
// 域名统计
|
||||
this.$post(".domainStats")
|
||||
.params({
|
||||
clusterId: this.clusterId
|
||||
})
|
||||
.success(function (resp) {
|
||||
for (let k in resp.data) {
|
||||
this[k] = resp.data[k]
|
||||
}
|
||||
this.reloadTopDomainsChart()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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.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 = "/clusters/cluster/node?nodeId=" + stats[args.dataIndex].nodeId + "&clusterId=" + that.clusterId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 域名排行
|
||||
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.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
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
.message.loading {
|
||||
padding: 1.5em;
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
height: 14em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
span {
|
||||
font-size: 0.8em;
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
/*# sourceMappingURL=createBatch.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["createBatch.less"],"names":[],"mappings":"AAAA;EACC,SAAA;;AAGD;EACC,SAAA","file":"createBatch.css"}
|
||||
@@ -0,0 +1,37 @@
|
||||
{$layout}
|
||||
{$template "/clusters/cluster/menu"}
|
||||
{$template "/left_menu"}
|
||||
|
||||
<div class="right-box">
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
||||
|
||||
<div v-if="quota.maxNodes > 0 && quota.leftNodes >= 0"><span style="color: #959da6">当前授权最多支持节点数:{{quota.maxNodes}}个,剩余节点数:{{quota.leftNodes}}个。</span></div>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">节点IP列表</td>
|
||||
<td>
|
||||
<textarea name="ipList" rows="20" placeholder="IP列表,每行一个IP" ref="ipList"></textarea>
|
||||
<p class="comment">每行一个节点IP。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>所属分组</td>
|
||||
<td>
|
||||
<node-group-selector :v-cluster-id="clusterId"></node-group-selector>
|
||||
<p class="comment">用来筛选节点<span v-if="teaIsPlus">,同时可以在分组中设置二级缓存节点</span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>所属区域</td>
|
||||
<td>
|
||||
<node-region-selector></node-region-selector>
|
||||
<p class="comment">设置区域后可以根据区域进行流量统计和计费。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifySuccess("保存成功", "/clusters/cluster/nodes?clusterId=" + this.clusterId)
|
||||
|
||||
this.$delay(function () {
|
||||
this.$refs.ipList.focus()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
13
EdgeAdmin/web/views/@default/clusters/cluster/createNode.css
Normal file
13
EdgeAdmin/web/views/@default/clusters/cluster/createNode.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
.row {
|
||||
line-height: 2;
|
||||
}
|
||||
.step.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
/*# sourceMappingURL=createNode.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["createNode.less"],"names":[],"mappings":"AAAA;EACC,SAAA;;AAGD;EACC,SAAA;;AAGD;EACC,cAAA;;AAGD,KAAK;EACJ,iBAAA","file":"createNode.css"}
|
||||
198
EdgeAdmin/web/views/@default/clusters/cluster/createNode.html
Normal file
198
EdgeAdmin/web/views/@default/clusters/cluster/createNode.html
Normal file
@@ -0,0 +1,198 @@
|
||||
{$layout}
|
||||
{$template "/clusters/cluster/menu"}
|
||||
{$template "/left_menu"}
|
||||
{$template "/code_editor"}
|
||||
|
||||
<div class="right-box">
|
||||
<div class="ui steps small fluid">
|
||||
<div class="ui step" :class="{active: step == 'info'}">
|
||||
<div class="content">1. 填写节点信息</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == 'install'}">
|
||||
<div class="content">2. 安装节点</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == 'finish'}">
|
||||
<div class="content">3. 完成</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 填写信息 -->
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success" v-show="step == 'info'">
|
||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
||||
|
||||
<div v-if="quota.maxNodes > 0 && quota.leftNodes >= 0"><span style="color: #959da6">当前授权最多节点数:{{quota.maxNodes}}个,剩余节点数:{{quota.leftNodes}}个。</span></div>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">节点名称 *</td>
|
||||
<td>
|
||||
<input type="text" name="name" maxlength="50" ref="focus" v-model="name" @input="changeName"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP地址 *</td>
|
||||
<td>
|
||||
<node-ip-addresses-box></node-ip-addresses-box>
|
||||
<p class="comment">用于访问节点和域名解析等<span v-if="defaultIP.length > 0"><strong>,如果没有填写默认为{{defaultIP}}</strong></span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="dnsRoutes.length > 0">
|
||||
<td>DNS线路</td>
|
||||
<td>
|
||||
<input type="hidden" name="dnsDomainId" :value="dnsDomainId"/>
|
||||
<dns-route-selector :v-all-routes="dnsRoutes"></dns-route-selector>
|
||||
<p class="comment">当前节点对应的DNS线路,可用线路是根据集群设置的域名获取的,注意DNS服务商可能对这些线路有其他限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>所属分组</td>
|
||||
<td>
|
||||
<node-group-selector :v-cluster-id="clusterId"></node-group-selector>
|
||||
<p class="comment">用来筛选节点<span v-if="teaIsPlus">,同时可以在分组中设置二级缓存节点</span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>所属区域</td>
|
||||
<td>
|
||||
<node-region-selector></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>
|
||||
<td>SSH主机地址</td>
|
||||
<td>
|
||||
<input type="text" name="sshHost" maxlength="64"/>
|
||||
<p class="comment">比如192.168.1.100,用于远程安装节点应用程序。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SSH主机端口</td>
|
||||
<td>
|
||||
<input type="text" name="sshPort" maxlength="5"/>
|
||||
<p class="comment">常见的比如22。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SSH登录认证</td>
|
||||
<td>
|
||||
<grant-selector :v-node-cluster-id="clusterId"></grant-selector>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<submit-btn>下一步</submit-btn>
|
||||
</form>
|
||||
|
||||
<!-- 安装节点 -->
|
||||
<div v-if="step == 'install'">
|
||||
<div class="ui tabular menu">
|
||||
<a href="" class="item" :class="{active: installMethod == 'remote'}" @click.prevent="switchInstallMethod('remote')">远程安装</a>
|
||||
<a href="" class="item" :class="{active: installMethod == 'manual'}" @click.prevent="switchInstallMethod('manual')">手动安装</a>
|
||||
</div>
|
||||
|
||||
<!-- 远程安装 -->
|
||||
<div v-if="installMethod == 'remote'">
|
||||
<form class="ui form">
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">SSH主机地址 *</td>
|
||||
<td>
|
||||
<input type="text" name="sshHost2" maxlength="64" v-model="sshHost" ref="installSSHHostRef" style="width: 16em" @change="changeSSHHost"/>
|
||||
<div v-if="node.addresses != null && node.addresses.length > 1" style="margin-top: 1em">
|
||||
<a href="" class="ui label small basic" v-for="addr in node.addresses" title="点击使用" @click.prevent="selectSSHHost(addr)">{{addr}}</a>
|
||||
</div>
|
||||
<p class="comment">比如192.168.1.100,用于远程安装节点应用程序。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SSH主机端口 *</td>
|
||||
<td>
|
||||
<input type="text" name="sshPort2" maxlength="5" v-model="sshPort" style="width:5em"/>
|
||||
<p class="comment"><node-login-suggest-ports ref="nodeLoginSuggestPortsRef" @select="selectLoginPort" @auto-select="autoSelectLoginPort"></node-login-suggest-ports></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SSH登录认证 *</td>
|
||||
<td>
|
||||
<grant-selector :v-grant="node.grant" :v-node-cluster-id="clusterId" @change="changeGrant"></grant-selector>
|
||||
</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>
|
||||
|
||||
<button class="ui primary button" type="button" @click.prevent="install" v-if="!isInstalling">远程安装</button>
|
||||
<button class="ui button disabled" v-if="isInstalling">正在安装</button>
|
||||
<a href="" @click.prevent="finish" v-if="!isInstalling" style="margin-left: 1em; float: right">跳过安装</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 手动安装 -->
|
||||
<div v-if="installMethod == 'manual'">
|
||||
<div class="row">上传边缘节点程序到服务器并解压,然后在边缘节点安装目录下,复制<code-label>configs/api_node.template.yaml</code-label>为<code-label>configs/api_node.yaml</code-label>,修改文件里面的内容为以下内容:</div>
|
||||
<div class="margin"></div>
|
||||
<source-code-box id="rpc-code" type="text/yaml">rpc.endpoints: [ {{apiEndpoints}} ]
|
||||
nodeId: "{{node.uniqueId}}"
|
||||
secret: "{{node.secret}}"</source-code-box>
|
||||
<div class="margin"></div>
|
||||
<div class="row">然后再使用<code-label>bin/edge-node start</code-label>命令启动节点。</div>
|
||||
<div class="margin"></div>
|
||||
|
||||
<div v-if="installerFiles != null && installerFiles.length > 0">
|
||||
<h4>边缘节点安装文件下载</h4>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="ui divider"></div>
|
||||
<a href="" @click.prevent="finish">安装完成</a>
|
||||
<a href="" @click.prevent="finish" style="float: right">跳过安装</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 完成 -->
|
||||
<div v-show="step == 'finish'">
|
||||
<div>
|
||||
<div style="text-align: center; font-size: 1.4em; margin-top: 2.4em" v-if="isInstalled"><span class="green">"{{node.name}}"节点已被创建并安装成功。</span></div>
|
||||
<div style="text-align: center; font-size: 1.4em; margin-top: 2.4em" v-if="!isInstalled"><span class="green">"{{node.name}}"节点已创建成功。</span></div>
|
||||
<div style="text-align: center; margin-top: 3em">
|
||||
<a :href="'/clusters/cluster/node?nodeId=' + nodeId + '&clusterId=' + clusterId" class="ui button primary" type="button">现在进入节点详情<i class="ui icon long arrow alternate right"></i></a>
|
||||
</div>
|
||||
<div style="text-align: center; margin-top: 1em">
|
||||
<a href="" @click.prevent="createNext">继续创建下一个节点</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
205
EdgeAdmin/web/views/@default/clusters/cluster/createNode.js
Normal file
205
EdgeAdmin/web/views/@default/clusters/cluster/createNode.js
Normal file
@@ -0,0 +1,205 @@
|
||||
Tea.context(function () {
|
||||
this.nodeId = 0
|
||||
this.node = {}
|
||||
this.sshHost = ""
|
||||
this.sshPort = ""
|
||||
this.grantId = 0
|
||||
this.step = "info"
|
||||
this.name = ""
|
||||
|
||||
this.success = function (resp) {
|
||||
this.node = resp.data.node
|
||||
this.nodeId = this.node.id
|
||||
this.sshHost = this.node.login.params.host
|
||||
if (this.node.login.params.port > 0) {
|
||||
this.sshPort = this.node.login.params.port
|
||||
}
|
||||
if (this.node.addresses.length > 0) {
|
||||
this.sshHost = this.node.addresses[0]
|
||||
}
|
||||
this.step = "install"
|
||||
this.$delay(function () {
|
||||
this.$refs.installSSHHostRef.focus()
|
||||
this.$refs.nodeLoginSuggestPortsRef.reload(this.sshHost)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装
|
||||
*/
|
||||
this.isInstalled = false
|
||||
this.installMethod = "remote" // remote | manual
|
||||
this.isInstalling = false
|
||||
|
||||
this.switchInstallMethod = function (method) {
|
||||
this.installMethod = method
|
||||
}
|
||||
|
||||
this.selectSSHHost = function (host) {
|
||||
this.sshHost = host
|
||||
this.changeSSHHost()
|
||||
}
|
||||
|
||||
this.changeSSHHost = function () {
|
||||
if (this.$refs.nodeLoginSuggestPortsRef != null) {
|
||||
this.$refs.nodeLoginSuggestPortsRef.reload(this.sshHost)
|
||||
}
|
||||
}
|
||||
|
||||
this.selectLoginPort = function (port) {
|
||||
this.sshPort = port
|
||||
}
|
||||
|
||||
this.autoSelectLoginPort = function (port) {
|
||||
if (this.sshPort == null || this.sshPort <= 0) {
|
||||
this.sshPort = port
|
||||
}
|
||||
}
|
||||
|
||||
this.install = function () {
|
||||
if (this.node.grant != null) {
|
||||
this.grantId = this.node.grant.id
|
||||
}
|
||||
|
||||
this.isInstalling = true
|
||||
this.$post(".createNodeInstall")
|
||||
.params({
|
||||
nodeId: this.node.id,
|
||||
sshHost: this.sshHost,
|
||||
sshPort: this.sshPort,
|
||||
grantId: this.grantId
|
||||
})
|
||||
.timeout(30)
|
||||
.success(function () {
|
||||
this.$delay(function () {
|
||||
this.isInstalling = true
|
||||
this.reloadStatus(this.node.id)
|
||||
})
|
||||
})
|
||||
.done(function () {
|
||||
this.isInstalling = false
|
||||
})
|
||||
}
|
||||
|
||||
this.changeGrant = function (grant) {
|
||||
if (grant != null) {
|
||||
this.grantId = grant.id
|
||||
} else {
|
||||
this.grantId = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新状态
|
||||
this.installStatus = null
|
||||
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 (this.node.isInstalled) {
|
||||
this.isInstalling = false
|
||||
this.isInstalled = true
|
||||
this.finish()
|
||||
}
|
||||
|
||||
if (!this.isInstalling) {
|
||||
return
|
||||
}
|
||||
|
||||
let nodeId = this.node.id
|
||||
let errMsg = this.installStatus.error
|
||||
|
||||
if (this.installStatus.errorCode.length > 0 || errMsg.length > 0) {
|
||||
this.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登录失败,请检查设置")
|
||||
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.finish = function () {
|
||||
this.step = "finish"
|
||||
}
|
||||
|
||||
this.createNext = function () {
|
||||
teaweb.reload()
|
||||
}
|
||||
|
||||
this.defaultIP = ""
|
||||
this.changeName = function () {
|
||||
let matchIP = this.name.match(/(\d{1,3}\.){3}\d{1,3}/)
|
||||
if (matchIP != null) {
|
||||
if (this.validateIP(matchIP[0])) {
|
||||
this.defaultIP = matchIP[0]
|
||||
} else {
|
||||
this.defaultIP = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.validateIP = function (v) {
|
||||
// 目前只支持ipv4
|
||||
let pieces = v.split(".")
|
||||
if (pieces.length != 4) {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < pieces.length; i++) {
|
||||
if (!/^\d{1,3}$/.test(pieces[i])) {
|
||||
return false
|
||||
}
|
||||
let p = parseInt(pieces[i], 10)
|
||||
if (p > 255) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
|
||||
.row {
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.step.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
8
EdgeAdmin/web/views/@default/clusters/cluster/delete.css
Normal file
8
EdgeAdmin/web/views/@default/clusters/cluster/delete.css
Normal file
@@ -0,0 +1,8 @@
|
||||
.buttons-box {
|
||||
text-align: center;
|
||||
margin-top: 2em;
|
||||
}
|
||||
.buttons-box button {
|
||||
width: 20em;
|
||||
}
|
||||
/*# sourceMappingURL=delete.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["delete.less"],"names":[],"mappings":"AAAA;EACC,kBAAA;EACA,eAAA;;AAFD,YAIC;EACC,WAAA","file":"delete.css"}
|
||||
11
EdgeAdmin/web/views/@default/clusters/cluster/delete.html
Normal file
11
EdgeAdmin/web/views/@default/clusters/cluster/delete.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{$layout}
|
||||
|
||||
<second-menu>
|
||||
<menu-item :href="'/clusters/cluster?clusterId=' + clusterId">{{currentClusterName}}</menu-item>
|
||||
<span class="disabled item" style="padding: 0">»</span>
|
||||
<span class="item active">删除</span>
|
||||
</second-menu>
|
||||
|
||||
<div class="buttons-box">
|
||||
<button class="ui button red" type="button" @click.prevent="deleteCluster(clusterId)">删除当前集群</button>
|
||||
</div>
|
||||
16
EdgeAdmin/web/views/@default/clusters/cluster/delete.js
Normal file
16
EdgeAdmin/web/views/@default/clusters/cluster/delete.js
Normal file
@@ -0,0 +1,16 @@
|
||||
Tea.context(function () {
|
||||
this.deleteCluster = function (clusterId) {
|
||||
let that = this
|
||||
teaweb.confirm("html:确定要删除此集群吗?<br/>删除后不可恢复!", function () {
|
||||
that.$post("/clusters/cluster/delete")
|
||||
.params({
|
||||
clusterId: clusterId
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.success("删除成功", function () {
|
||||
window.location = "/clusters"
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
.buttons-box {
|
||||
text-align: center;
|
||||
margin-top: 2em;
|
||||
|
||||
button {
|
||||
width: 20em;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>创建分组</h3>
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">分组名称 *</td>
|
||||
<td>
|
||||
<input type="text" name="name" maxlength="50" ref="focus"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,36 @@
|
||||
{$layout}
|
||||
{$template "../menu"}
|
||||
|
||||
<first-menu style="margin-top:-1em">
|
||||
<a href="" class="item" @click.prevent="createGroup()">[创建分组]</a>
|
||||
</first-menu>
|
||||
|
||||
<p class="comment" v-if="groups.length == 0">暂时还没有分组。</p>
|
||||
<div v-show="groups.length > 0">
|
||||
<div class="margin"></div>
|
||||
<table class="ui table selectable celled" id="sortable-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:3em"></th>
|
||||
<th>分组名称</th>
|
||||
<th class="center">节点数</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-for="group in groups" :data-group-id="group.id">
|
||||
<tr>
|
||||
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
|
||||
<td><a href="" @click.prevent="updateGroup(group.id)">{{group.name}} <i class="icon expand small"></i></a></td>
|
||||
<td class="center">
|
||||
<a :href="'/clusters/cluster/nodes?clusterId=' + clusterId + '&groupId=' + group.id" v-if="group.countNodes > 0">{{group.countNodes}}</a>
|
||||
<span v-else class="disabled">0</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="" @click.prevent="updateGroup(group.id)">修改</a> <a href="" @click.prevent="deleteGroup(group.id)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p v-if="groups.length > 0" class="comment">可以拖动左侧的<i class="icon bars"></i>排序。</p>
|
||||
@@ -0,0 +1,53 @@
|
||||
Tea.context(function () {
|
||||
this.$delay(function () {
|
||||
let that = this
|
||||
sortTable(function () {
|
||||
let groupIds = []
|
||||
document.querySelectorAll("*[data-group-id]").forEach(function (element) {
|
||||
groupIds.push(element.getAttribute("data-group-id"))
|
||||
})
|
||||
that.$post("/clusters/cluster/groups/sort")
|
||||
.params({
|
||||
groupIds: groupIds
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.successToast("保存成功")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.createGroup = function () {
|
||||
teaweb.popup("/clusters/cluster/groups/createPopup?clusterId=" + this.clusterId, {
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.updateGroup = function (groupId) {
|
||||
teaweb.popup("/clusters/cluster/groups/updatePopup?groupId=" + groupId, {
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.deleteGroup = function (groupId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除这个分组吗?", function () {
|
||||
that.$post("/clusters/cluster/groups/delete")
|
||||
.params({
|
||||
groupId: groupId
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.success("删除成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>选择所属分组</h3>
|
||||
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<input type="hidden" name="groupId" :value="groupId"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">选择分组</td>
|
||||
<td>
|
||||
<div v-if="groups.length > 0">
|
||||
<a href="" class="ui label small basic" v-for="group in groups" :class="{blue:group.id == groupId}" style="margin-bottom:0.5em" @click.prevent="selectGroup(group)">{{group.name}}</a>
|
||||
<p class="comment">点击可已选中要使用的分组。</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="comment">当前集群下暂时还没有可以使用的分组。</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn>确定</submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,8 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifyPopup
|
||||
this.groupId = 0
|
||||
|
||||
this.selectGroup = function (group) {
|
||||
this.groupId = group.id
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>修改分组</h3>
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<input type="hidden" name="groupId" :value="group.id"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">分组名称 *</td>
|
||||
<td>
|
||||
<input type="text" name="name" maxlength="50" ref="focus" v-model="group.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -0,0 +1,7 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
/*# sourceMappingURL=installManual.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["installManual.less"],"names":[],"mappings":"AAAA;EACC,SAAA;;AAGD;EACC,SAAA","file":"installManual.css"}
|
||||
@@ -0,0 +1,42 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
{$template "/left_menu"}
|
||||
|
||||
<div class="right-box">
|
||||
<p class="comment" v-if="nodes.length == 0">暂时没有需要远程安装的节点。</p>
|
||||
|
||||
<div v-if="nodes.length > 0">
|
||||
<h3>所有未安装节点</h3>
|
||||
<table class="ui table selectable celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>节点名</th>
|
||||
<th>访问IP</th>
|
||||
<th>SSH地址</th>
|
||||
<th class="four wide">节点状态</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="node in nodes">
|
||||
<td>
|
||||
<a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}</a>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="addr in node.addresses" v-if="addr.canAccess" class="ui label tiny">{{addr.ip}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="node.login != null && node.login.type == 'ssh' && node.loginParams != null && node.loginParams.host != null && node.loginParams.host.length > 0">
|
||||
{{node.loginParams.host}}:{{node.loginParams.port}}
|
||||
</span>
|
||||
<span v-else class="disabled">没有设置</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="disabled">等待安装</span>
|
||||
</td>
|
||||
<td>
|
||||
<a :href="'/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id">手动安装</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
/*# sourceMappingURL=installNodes.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["installNodes.less"],"names":[],"mappings":"AAAA;EACC,SAAA;;AAGD;EACC,SAAA","file":"installNodes.css"}
|
||||
@@ -0,0 +1,24 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
{$template "/left_menu"}
|
||||
{$template "/code_editor"}
|
||||
|
||||
<div class="right-box">
|
||||
<p>在官网下载节点安装包,然后通过修改节点安装包中的<code-label>configs/api_cluster.yaml</code-label>(如果此配置文件尚未创建,你需要先创建)为以下内容,启动后会自动注册节点。</p>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">配置文件</td>
|
||||
<td>
|
||||
api_cluster.yaml <download-link :v-element="'cluster-config-box'" :v-file="'api_cluster.yaml'">[下载]</download-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">配置内容</td>
|
||||
<td>
|
||||
<source-code-box id="cluster-config-box" type="text/yaml">rpc.endpoints: [ {{cluster.endpoints}} ]
|
||||
clusterId: "{{cluster.uniqueId}}"
|
||||
secret: "{{cluster.secret}}"</source-code-box>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
h3 {
|
||||
position: relative;
|
||||
}
|
||||
h3 button {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: -0.2em;
|
||||
}
|
||||
/*# sourceMappingURL=installRemote.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["installRemote.less"],"names":[],"mappings":"AAAA;EACC,SAAA;;AAGD;EACC,SAAA;;AAGD;EACC,kBAAA;;AAGD,EAAG;EACF,kBAAA;EACA,UAAA;EACA,WAAA","file":"installRemote.css"}
|
||||
@@ -0,0 +1,59 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
{$template "/left_menu"}
|
||||
|
||||
<div class="right-box">
|
||||
<p class="comment" v-if="nodes.length == 0">暂时没有需要远程安装的节点。</p>
|
||||
|
||||
<div v-if="nodes.length > 0">
|
||||
<h3>所有未安装节点
|
||||
<button class="ui button primary tiny" v-if="countCheckedNodes() > 0" @click.prevent="installBatch()">批量安装({{countCheckedNodes()}})</button>
|
||||
</h3>
|
||||
<table class="ui table selectable celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:3em">
|
||||
<checkbox @input="checkNodes"></checkbox>
|
||||
</th>
|
||||
<th>节点名</th>
|
||||
<th>访问IP</th>
|
||||
<th>SSH地址</th>
|
||||
<th class="four wide">节点状态</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="node in nodes">
|
||||
<td>
|
||||
<checkbox v-model="node.isChecked" v-if="node.installStatus == null || !node.installStatus.isOk"></checkbox>
|
||||
</td>
|
||||
<td>
|
||||
<link-icon :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}</link-icon>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="addr in node.addresses" v-if="addr.canAccess" class="ui label tiny basic">{{addr.ip}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="node.login != null && node.login.type == 'ssh' && node.loginParams != null && node.loginParams.host != null && node.loginParams.host.length > 0">
|
||||
{{node.loginParams.host}}:{{node.loginParams.port}}
|
||||
</span>
|
||||
<span v-else class="disabled">没有设置</span>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="node.installStatus != null && (node.installStatus.isRunning || node.installStatus.isFinished)">
|
||||
<div v-if="node.installStatus.isRunning" class="blue">安装中...</div>
|
||||
<div v-if="node.installStatus.isFinished">
|
||||
<span v-if="node.installStatus.isOk" class="green">已安装成功</span>
|
||||
<span v-if="!node.installStatus.isOk" class="red">安装过程中发生错误:{{node.installStatus.error}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="disabled">等待安装</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="" @click.prevent="installNode(node)" v-if="!isInstalling">安装</a>
|
||||
<span v-if="isInstalling && node.isInstalling">安装中...</span>
|
||||
<span v-if="isInstalling && !node.isInstalling" class="disabled">安装</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
162
EdgeAdmin/web/views/@default/clusters/cluster/installRemote.js
Normal file
162
EdgeAdmin/web/views/@default/clusters/cluster/installRemote.js
Normal file
@@ -0,0 +1,162 @@
|
||||
Tea.context(function () {
|
||||
this.isInstalling = false
|
||||
this.isBatch = false
|
||||
let installingNode = null
|
||||
|
||||
this.nodes.forEach(function (v) {
|
||||
v.isChecked = false
|
||||
})
|
||||
|
||||
this.$delay(function () {
|
||||
this.reload()
|
||||
})
|
||||
|
||||
let that = this
|
||||
|
||||
this.checkNodes = function (isChecked) {
|
||||
this.nodes.forEach(function (v) {
|
||||
v.isChecked = isChecked
|
||||
})
|
||||
}
|
||||
|
||||
this.countCheckedNodes = function () {
|
||||
return that.nodes.$count(function (k, v) {
|
||||
return v.isChecked
|
||||
})
|
||||
}
|
||||
|
||||
this.installNode = function (node) {
|
||||
let that = this
|
||||
if (this.isBatch) {
|
||||
installingNode = node
|
||||
that.isInstalling = true
|
||||
node.isInstalling = true
|
||||
|
||||
that.$post("$")
|
||||
.params({
|
||||
nodeId: node.id
|
||||
})
|
||||
} else {
|
||||
teaweb.confirm("确定要开始安装此节点吗?", function () {
|
||||
installingNode = node
|
||||
that.isInstalling = true
|
||||
node.isInstalling = true
|
||||
|
||||
that.$post("$")
|
||||
.params({
|
||||
nodeId: node.id
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.installBatch = function () {
|
||||
let that = this
|
||||
this.isBatch = true
|
||||
teaweb.confirm("确定要批量安装选中的节点吗?", function () {
|
||||
that.installNext()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装下一个
|
||||
*/
|
||||
this.installNext = function () {
|
||||
let nextNode = this.nodes.$find(function (k, v) {
|
||||
return v.isChecked
|
||||
})
|
||||
|
||||
if (nextNode == null) {
|
||||
teaweb.success("全部安装成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
} else {
|
||||
this.installNode(nextNode)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新加载状态
|
||||
*/
|
||||
this.reload = function () {
|
||||
let that = this
|
||||
if (installingNode != null) {
|
||||
this.$post("/clusters/cluster/installStatus")
|
||||
.params({
|
||||
nodeId: installingNode.id
|
||||
})
|
||||
.success(function (resp) {
|
||||
if (resp.data.status != null) {
|
||||
installingNode.installStatus = resp.data.status
|
||||
if (installingNode.installStatus.isFinished) {
|
||||
if (installingNode.installStatus.isOk) {
|
||||
installingNode.isChecked = false // 取消选中
|
||||
installingNode = null
|
||||
if (that.isBatch) {
|
||||
that.installNext()
|
||||
} else {
|
||||
teaweb.success("安装成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let nodeId = installingNode.id
|
||||
let errMsg = installingNode.installStatus.error
|
||||
that.isInstalling = false
|
||||
installingNode.isInstalling = false
|
||||
installingNode = null
|
||||
|
||||
switch (resp.data.status.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 () {
|
||||
teaweb.reload()
|
||||
}
|
||||
})
|
||||
})
|
||||
return
|
||||
case "SSH登录失败,请检查设置":
|
||||
teaweb.warn("需要填写SSH登录信息", function () {
|
||||
teaweb.popup("/clusters/cluster/updateNodeSSH?nodeId=" + nodeId, {
|
||||
height: "30em",
|
||||
callback: function () {
|
||||
teaweb.reload()
|
||||
}
|
||||
})
|
||||
})
|
||||
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:
|
||||
teaweb.warn("安装失败:" + errMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.done(function () {
|
||||
setTimeout(this.reload, 3000)
|
||||
})
|
||||
} else {
|
||||
setTimeout(this.reload, 3000)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
.left-box {
|
||||
top: 10em;
|
||||
}
|
||||
|
||||
.right-box {
|
||||
top: 10em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
h3 button {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: -0.2em;
|
||||
}
|
||||
@@ -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"> <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>
|
||||
@@ -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 */
|
||||
@@ -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"}
|
||||
@@ -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> 数据加载中...
|
||||
</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">> {{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>
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
a.underline {
|
||||
border-bottom: 1px #db2828 dashed;
|
||||
}
|
||||
/*# sourceMappingURL=detail.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["detail.less"],"names":[],"mappings":"AAAA,CAAC;EACA,iCAAA","file":"detail.css"}
|
||||
332
EdgeAdmin/web/views/@default/clusters/cluster/node/detail.html
Normal file
332
EdgeAdmin/web/views/@default/clusters/cluster/node/detail.html
Normal 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>
|
||||
<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)}} <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">
|
||||
<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>
|
||||
<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">> {{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>
|
||||
<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>
|
||||
<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>去安装></span></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tbody v-show="node.status.isActive">
|
||||
<tr>
|
||||
<td>CPU用量</td>
|
||||
<td>{{node.status.cpuUsageText}} <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}} {{node.status.load5m}} {{node.status.load15m}} <tip-icon content="三个数字分别代表1分钟、5分钟、15分钟平均负载"></tip-icon></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>缓存用量</td>
|
||||
<td>
|
||||
硬盘:{{node.status.cacheTotalDiskSize}} 内存:{{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}}
|
||||
<a :href="'/clusters/cluster/upgradeRemote?clusterId=' + clusterId" v-if="shouldUpgrade"><span class="red">发现新版本v{{newVersion}} »</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">已安装 <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>
|
||||
59
EdgeAdmin/web/views/@default/clusters/cluster/node/detail.js
Normal file
59
EdgeAdmin/web/views/@default/clusters/cluster/node/detail.js
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
a.underline {
|
||||
border-bottom: 1px #db2828 dashed;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.installing-box {
|
||||
line-height: 1.8;
|
||||
}
|
||||
.installing-box .blue {
|
||||
color: #2185d0;
|
||||
}
|
||||
/*# sourceMappingURL=install.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["install.less"],"names":[],"mappings":"AAAA;EACC,gBAAA;;AADD,eAGC;EACC,cAAA","file":"install.css"}
|
||||
132
EdgeAdmin/web/views/@default/clusters/cluster/node/install.html
Normal file
132
EdgeAdmin/web/views/@default/clusters/cluster/node/install.html
Normal 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}} <a href="" @click.prevent="showSSHPopup(nodeId)">[修改]</a></span>
|
||||
<span v-else><span class="red">尚未设置</span> <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
|
||||
<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 <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>
|
||||
117
EdgeAdmin/web/views/@default/clusters/cluster/node/install.js
Normal file
117
EdgeAdmin/web/views/@default/clusters/cluster/node/install.js
Normal 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()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
.installing-box {
|
||||
line-height: 1.8;
|
||||
|
||||
.blue {
|
||||
color: #2185d0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pre.log-box {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
/*# sourceMappingURL=logs.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["logs.less"],"names":[],"mappings":"AAAA,GAAG;EACF,SAAA;EACA,UAAA","file":"logs.css"}
|
||||
57
EdgeAdmin/web/views/@default/clusters/cluster/node/logs.html
Normal file
57
EdgeAdmin/web/views/@default/clusters/cluster/node/logs.html
Normal 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>
|
||||
@@ -0,0 +1,6 @@
|
||||
Tea.context(function () {
|
||||
this.$delay(function () {
|
||||
teaweb.datepicker("day-from-picker")
|
||||
teaweb.datepicker("day-to-picker")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
pre.log-box {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
41
EdgeAdmin/web/views/@default/clusters/cluster/node/settings/cache/index.html
vendored
Normal file
41
EdgeAdmin/web/views/@default/clusters/cluster/node/settings/cache/index.html
vendored
Normal 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>
|
||||
3
EdgeAdmin/web/views/@default/clusters/cluster/node/settings/cache/index.js
vendored
Normal file
3
EdgeAdmin/web/views/@default/clusters/cluster/node/settings/cache/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功")
|
||||
})
|
||||
@@ -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>
|
||||
@@ -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]
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功")
|
||||
})
|
||||
@@ -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>
|
||||
@@ -0,0 +1,9 @@
|
||||
Tea.context(function () {
|
||||
this.param = null
|
||||
|
||||
this.changeParam = function (param) {
|
||||
if (param != null) {
|
||||
this.param = param
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user