feat: sync httpdns sdk/platform updates without large binaries
This commit is contained in:
@@ -1,3 +1,139 @@
|
||||
{$layout}
|
||||
|
||||
<p class="comment">此功能暂未开放,敬请期待。</p>
|
||||
<div class="margin"></div>
|
||||
|
||||
<!-- 自动升级设置 -->
|
||||
<div class="ui segment">
|
||||
<h3>自动升级</h3>
|
||||
<table class="ui table definition">
|
||||
<tr>
|
||||
<td class="title">开启自动升级</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="autoUpgrade" v-model="config.autoUpgrade" @change="updateAutoUpgrade">
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">开启后,边缘节点、DNS节点、HTTPDNS节点每分钟检查新版本并自动下载升级。关闭后节点不会自动升级,但管理员仍可在下方手动升级。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="margin"></div>
|
||||
|
||||
<!-- 手动升级 -->
|
||||
<div class="ui segment">
|
||||
<h3 style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>手动升级</span>
|
||||
<button class="ui button primary tiny" v-if="totalUpgradeCount > 0"
|
||||
@click.prevent="upgradeAll()">全部升级({{totalUpgradeCount}})</button>
|
||||
</h3>
|
||||
|
||||
<div v-if="modules.length == 0">
|
||||
<p class="comment">暂无需要升级的节点。</p>
|
||||
</div>
|
||||
|
||||
<div v-for="mod in modules" class="ui segment" style="margin-bottom: 1em; padding: 0;">
|
||||
<h4 style="cursor: pointer; display: flex; justify-content: space-between; align-items: center; padding: 0.8em 1em; margin: 0; background: #f9fafb; border-bottom: 1px solid rgba(34,36,38,.15);"
|
||||
@click.prevent="mod.expanded = !mod.expanded">
|
||||
<div>
|
||||
<i class="icon angle down" v-if="mod.expanded"></i>
|
||||
<i class="icon angle right" v-else></i>
|
||||
{{mod.name}}
|
||||
<span class="ui label tiny basic" v-if="mod.count > 0"
|
||||
style="margin-left: 0.5em;">{{mod.count}}个待升级</span>
|
||||
</div>
|
||||
<button class="ui button primary tiny" v-if="mod.count > 0"
|
||||
@click.stop.prevent="upgradeModule(mod.code)">升级所有{{mod.name}}</button>
|
||||
</h4>
|
||||
|
||||
<div v-show="mod.expanded" style="padding: 1em;">
|
||||
<div v-for="cluster in mod.clusters" style="margin-bottom: 1em;">
|
||||
<h5 style="cursor: pointer; display: flex; justify-content: space-between; align-items: center; padding: 0.6em; background: #f3f4f5; border-radius: 4px; margin: 0;"
|
||||
@click.prevent="cluster.expanded = !cluster.expanded">
|
||||
<div>
|
||||
<i class="icon angle down" v-if="cluster.expanded"></i>
|
||||
<i class="icon angle right" v-else></i>
|
||||
{{cluster.name}}
|
||||
<span class="ui label tiny basic" style="margin-left: 0.5em;">{{cluster.count}}个待升级</span>
|
||||
</div>
|
||||
<div>
|
||||
<button class="ui button tiny primary" v-if="countCheckedNodesInCluster(cluster) > 0"
|
||||
@click.stop.prevent="upgradeBatchInCluster(mod.code, cluster)">批量升级({{countCheckedNodesInCluster(cluster)}})</button>
|
||||
<button class="ui button tiny"
|
||||
@click.stop.prevent="upgradeCluster(mod.code, cluster.id)">升级集群内所有节点</button>
|
||||
</div>
|
||||
</h5>
|
||||
|
||||
<div v-show="cluster.expanded" style="margin-top: 0.5em;">
|
||||
<table class="ui table selectable celled small" style="margin: 0;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:3em">
|
||||
<div class="ui checkbox" @click.prevent="toggleCheckAll(cluster)">
|
||||
<input type="checkbox" :checked="isAllChecked(cluster)">
|
||||
<label></label>
|
||||
</div>
|
||||
</th>
|
||||
<th>节点名</th>
|
||||
<th>访问IP</th>
|
||||
<th>SSH地址</th>
|
||||
<th>版本变化</th>
|
||||
<th class="four wide">节点状态</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="node in cluster.nodes">
|
||||
<td>
|
||||
<div class="ui checkbox" v-if="!isNodeUpgradeFinished(node)">
|
||||
<input type="checkbox" v-model="node.isChecked">
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{{node.name}}
|
||||
<a :href="nodeDetailURL(mod.code, cluster.id, node.id)" title="节点详情" style="margin-left: 0.4em"><i class="icon external alternate small"></i></a>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="node.accessIP && node.accessIP.length > 0" class="ui label tiny basic">{{node.accessIP}}</span>
|
||||
<span v-else class="disabled">-</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 v-if="node.oldVersion">v{{node.oldVersion}}</span>
|
||||
<span v-else class="ui label tiny">未知</span>
|
||||
-> <strong>v{{node.newVersion}}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="node.installStatus != null && (node.installStatus.isRunning || node.installStatus.isFinished)">
|
||||
<div v-if="node.installStatus.isRunning && !node.installStatus.isFinished"
|
||||
class="blue">
|
||||
<i class="notched circle loading icon"></i> 升级中...
|
||||
</div>
|
||||
<div v-if="node.installStatus.isFinished">
|
||||
<span v-if="node.installStatus.isOk" class="green"><i
|
||||
class="icon check circle"></i>
|
||||
已升级成功</span>
|
||||
<span v-if="!node.installStatus.isOk" class="red"><i
|
||||
class="icon warning circle"></i>
|
||||
升级过程中发生错误:{{node.installStatus.error}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="disabled">等待升级</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="" @click.prevent="upgradeNode(mod.code, node)" v-if="!node.isUpgrading">升级</a>
|
||||
<span v-if="node.isUpgrading" class="blue">升级中...</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
457
EdgeAdmin/web/views/@default/settings/upgrade/index.js
Normal file
457
EdgeAdmin/web/views/@default/settings/upgrade/index.js
Normal file
@@ -0,0 +1,457 @@
|
||||
Tea.context(function () {
|
||||
let that = this
|
||||
|
||||
// 计算总待升级数
|
||||
this.totalUpgradeCount = 0
|
||||
this.modules.forEach(function (mod) {
|
||||
mod.expanded = true
|
||||
that.totalUpgradeCount += mod.count
|
||||
if (mod.clusters != null) {
|
||||
mod.clusters.forEach(function (cluster) {
|
||||
cluster.expanded = true
|
||||
if (cluster.nodes != null) {
|
||||
cluster.nodes.forEach(function (node) {
|
||||
node.isUpgrading = false
|
||||
node.isChecked = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 正在升级的节点ID集合(按模块)
|
||||
this.upgradingNodeIds = {
|
||||
"node": [],
|
||||
"dns": [],
|
||||
"httpdns": []
|
||||
}
|
||||
|
||||
// 启动状态轮询
|
||||
this.$delay(function () {
|
||||
this.pollStatus()
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取节点详情页URL
|
||||
*/
|
||||
this.nodeDetailURL = function (moduleCode, clusterId, nodeId) {
|
||||
switch (moduleCode) {
|
||||
case "node":
|
||||
return "/clusters/cluster/node?clusterId=" + clusterId + "&nodeId=" + nodeId
|
||||
case "dns":
|
||||
return "/ns/clusters/cluster/node?clusterId=" + clusterId + "&nodeId=" + nodeId
|
||||
case "httpdns":
|
||||
return "/httpdns/clusters/cluster/node?clusterId=" + clusterId + "&nodeId=" + nodeId
|
||||
default:
|
||||
return "#"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断节点升级是否已完成(成功)
|
||||
*/
|
||||
this.isNodeUpgradeFinished = function (node) {
|
||||
return node.installStatus != null && node.installStatus.isFinished && node.installStatus.isOk
|
||||
}
|
||||
|
||||
/**
|
||||
* 全选/取消全选
|
||||
*/
|
||||
this.toggleCheckAll = function (cluster) {
|
||||
if (cluster.nodes == null) return
|
||||
let allChecked = that.isAllChecked(cluster)
|
||||
cluster.nodes.forEach(function (node) {
|
||||
if (!that.isNodeUpgradeFinished(node)) {
|
||||
node.isChecked = !allChecked
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否全部选中
|
||||
*/
|
||||
this.isAllChecked = function (cluster) {
|
||||
if (cluster.nodes == null || cluster.nodes.length == 0) return false
|
||||
let checkableNodes = cluster.nodes.filter(function (node) {
|
||||
return !that.isNodeUpgradeFinished(node)
|
||||
})
|
||||
if (checkableNodes.length == 0) return false
|
||||
return checkableNodes.every(function (node) { return node.isChecked })
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算集群内选中的节点数
|
||||
*/
|
||||
this.countCheckedNodesInCluster = function (cluster) {
|
||||
if (cluster.nodes == null) return 0
|
||||
return cluster.nodes.filter(function (node) { return node.isChecked }).length
|
||||
}
|
||||
|
||||
/**
|
||||
* 全部升级
|
||||
*/
|
||||
this.upgradeAll = function () {
|
||||
var vue = this
|
||||
teaweb.confirm("确定要升级所有模块的所有待升级节点吗?", function () {
|
||||
vue.$post("/settings/upgrade/upgradeNode")
|
||||
.params({
|
||||
module: "",
|
||||
scope: "all",
|
||||
clusterId: 0,
|
||||
nodeId: 0
|
||||
})
|
||||
.success(function () {
|
||||
// 标记所有节点为升级中
|
||||
that.modules.forEach(function (mod) {
|
||||
if (mod.clusters != null) {
|
||||
mod.clusters.forEach(function (cluster) {
|
||||
if (cluster.nodes != null) {
|
||||
cluster.nodes.forEach(function (node) {
|
||||
node.installStatus = {
|
||||
isRunning: true,
|
||||
isFinished: false,
|
||||
isOk: false,
|
||||
error: "",
|
||||
errorCode: ""
|
||||
}
|
||||
node.isUpgrading = true
|
||||
that.trackNode(mod.code, node.id)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.fail(function (resp) {
|
||||
teaweb.warn("升级请求失败:" + resp.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模块升级
|
||||
*/
|
||||
this.upgradeModule = function (moduleCode) {
|
||||
var vue = this
|
||||
teaweb.confirm("确定要升级该模块的所有待升级节点吗?", function () {
|
||||
vue.$post("/settings/upgrade/upgradeNode")
|
||||
.params({
|
||||
module: moduleCode,
|
||||
scope: "module",
|
||||
clusterId: 0,
|
||||
nodeId: 0
|
||||
})
|
||||
.success(function () {
|
||||
that.markModuleUpgrading(moduleCode)
|
||||
})
|
||||
.fail(function (resp) {
|
||||
teaweb.warn("升级请求失败:" + resp.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 按集群升级
|
||||
*/
|
||||
this.upgradeCluster = function (moduleCode, clusterId) {
|
||||
var vue = this
|
||||
teaweb.confirm("确定要升级该集群的所有待升级节点吗?", function () {
|
||||
vue.$post("/settings/upgrade/upgradeNode")
|
||||
.params({
|
||||
module: moduleCode,
|
||||
scope: "cluster",
|
||||
clusterId: clusterId,
|
||||
nodeId: 0
|
||||
})
|
||||
.success(function () {
|
||||
that.markClusterUpgrading(moduleCode, clusterId)
|
||||
})
|
||||
.fail(function (resp) {
|
||||
teaweb.warn("升级请求失败:" + resp.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量升级集群内选中的节点
|
||||
*/
|
||||
this.upgradeBatchInCluster = function (moduleCode, cluster) {
|
||||
if (cluster.nodes == null) return
|
||||
var checkedNodes = cluster.nodes.filter(function (node) { return node.isChecked })
|
||||
if (checkedNodes.length == 0) return
|
||||
|
||||
var vue = this
|
||||
teaweb.confirm("确定要批量升级选中的 " + checkedNodes.length + " 个节点吗?", function () {
|
||||
checkedNodes.forEach(function (node) {
|
||||
node.installStatus = {
|
||||
isRunning: true,
|
||||
isFinished: false,
|
||||
isOk: false,
|
||||
error: "",
|
||||
errorCode: ""
|
||||
}
|
||||
node.isUpgrading = true
|
||||
node.isChecked = false
|
||||
that.trackNode(moduleCode, node.id)
|
||||
|
||||
vue.$post("/settings/upgrade/upgradeNode")
|
||||
.params({
|
||||
module: moduleCode,
|
||||
scope: "node",
|
||||
clusterId: 0,
|
||||
nodeId: node.id
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 升级单个节点
|
||||
*/
|
||||
this.upgradeNode = function (moduleCode, node) {
|
||||
var vue = this
|
||||
teaweb.confirm("确定要升级节点 \"" + node.name + "\" 吗?", function () {
|
||||
node.installStatus = {
|
||||
isRunning: true,
|
||||
isFinished: false,
|
||||
isOk: false,
|
||||
error: "",
|
||||
errorCode: ""
|
||||
}
|
||||
node.isUpgrading = true
|
||||
that.trackNode(moduleCode, node.id)
|
||||
|
||||
vue.$post("/settings/upgrade/upgradeNode")
|
||||
.params({
|
||||
module: moduleCode,
|
||||
scope: "node",
|
||||
clusterId: 0,
|
||||
nodeId: node.id
|
||||
})
|
||||
.success(function () { })
|
||||
.fail(function (resp) {
|
||||
node.isUpgrading = false
|
||||
teaweb.warn("升级请求失败:" + resp.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记模块下所有节点为升级中
|
||||
*/
|
||||
this.markModuleUpgrading = function (moduleCode) {
|
||||
that.modules.forEach(function (mod) {
|
||||
if (mod.code == moduleCode && mod.clusters != null) {
|
||||
mod.clusters.forEach(function (cluster) {
|
||||
if (cluster.nodes != null) {
|
||||
cluster.nodes.forEach(function (node) {
|
||||
node.installStatus = {
|
||||
isRunning: true,
|
||||
isFinished: false,
|
||||
isOk: false,
|
||||
error: "",
|
||||
errorCode: ""
|
||||
}
|
||||
node.isUpgrading = true
|
||||
that.trackNode(moduleCode, node.id)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记集群下所有节点为升级中
|
||||
*/
|
||||
this.markClusterUpgrading = function (moduleCode, clusterId) {
|
||||
that.modules.forEach(function (mod) {
|
||||
if (mod.code == moduleCode && mod.clusters != null) {
|
||||
mod.clusters.forEach(function (cluster) {
|
||||
if (cluster.id == clusterId && cluster.nodes != null) {
|
||||
cluster.nodes.forEach(function (node) {
|
||||
node.installStatus = {
|
||||
isRunning: true,
|
||||
isFinished: false,
|
||||
isOk: false,
|
||||
error: "",
|
||||
errorCode: ""
|
||||
}
|
||||
node.isUpgrading = true
|
||||
that.trackNode(moduleCode, node.id)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 追踪节点升级状态
|
||||
*/
|
||||
this.trackNode = function (moduleCode, nodeId) {
|
||||
if (that.upgradingNodeIds[moduleCode] == null) {
|
||||
that.upgradingNodeIds[moduleCode] = []
|
||||
}
|
||||
if (that.upgradingNodeIds[moduleCode].indexOf(nodeId) < 0) {
|
||||
that.upgradingNodeIds[moduleCode].push(nodeId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态轮询
|
||||
*/
|
||||
this.pollStatus = function () {
|
||||
var vue = this
|
||||
|
||||
// 检查是否有正在升级的节点
|
||||
let hasUpgrading = false
|
||||
for (let key in that.upgradingNodeIds) {
|
||||
if (that.upgradingNodeIds[key].length > 0) {
|
||||
hasUpgrading = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasUpgrading) {
|
||||
setTimeout(function () { that.pollStatus() }, 5000)
|
||||
return
|
||||
}
|
||||
|
||||
vue.$post("/settings/upgrade/status")
|
||||
.params({
|
||||
nodeIdsJSON: JSON.stringify(that.upgradingNodeIds)
|
||||
})
|
||||
.success(function (resp) {
|
||||
let statuses = resp.data.statuses
|
||||
if (statuses == null) {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新各模块节点状态
|
||||
for (let moduleCode in statuses) {
|
||||
let nodeStatuses = statuses[moduleCode]
|
||||
if (nodeStatuses == null) {
|
||||
continue
|
||||
}
|
||||
nodeStatuses.forEach(function (ns) {
|
||||
that.updateNodeStatus(moduleCode, ns.id, ns.installStatus)
|
||||
})
|
||||
}
|
||||
})
|
||||
.done(function () {
|
||||
setTimeout(function () { that.pollStatus() }, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新节点安装状态
|
||||
*/
|
||||
this.updateNodeStatus = function (moduleCode, nodeId, installStatus) {
|
||||
that.modules.forEach(function (mod) {
|
||||
if (mod.code == moduleCode && mod.clusters != null) {
|
||||
mod.clusters.forEach(function (cluster) {
|
||||
if (cluster.nodes != null) {
|
||||
cluster.nodes.forEach(function (node) {
|
||||
if (node.id == nodeId && installStatus != null) {
|
||||
node.installStatus = installStatus
|
||||
// 升级完成后移除跟踪
|
||||
if (installStatus.isFinished) {
|
||||
node.isUpgrading = false
|
||||
let idx = that.upgradingNodeIds[moduleCode].indexOf(nodeId)
|
||||
if (idx >= 0) {
|
||||
that.upgradingNodeIds[moduleCode].splice(idx, 1)
|
||||
}
|
||||
|
||||
if (installStatus.isOk) {
|
||||
// 升级成功,延迟后从列表中移除
|
||||
setTimeout(function () {
|
||||
let nIdx = cluster.nodes.indexOf(node)
|
||||
if (nIdx >= 0) {
|
||||
cluster.nodes.splice(nIdx, 1)
|
||||
cluster.count--
|
||||
mod.count--
|
||||
that.totalUpgradeCount--
|
||||
|
||||
if (cluster.count <= 0) {
|
||||
let cIdx = mod.clusters.indexOf(cluster)
|
||||
if (cIdx >= 0) {
|
||||
mod.clusters.splice(cIdx, 1)
|
||||
}
|
||||
}
|
||||
if (mod.count <= 0) {
|
||||
let mIdx = that.modules.indexOf(mod)
|
||||
if (mIdx >= 0) {
|
||||
that.modules.splice(mIdx, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 2000)
|
||||
} else {
|
||||
// 升级失败,根据 errorCode 给出具体提示
|
||||
that.handleUpgradeError(moduleCode, node, installStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理升级错误,根据 errorCode 提供更友好的提示
|
||||
*/
|
||||
this.handleUpgradeError = function (moduleCode, node, installStatus) {
|
||||
let errorCode = installStatus.errorCode || ""
|
||||
let errMsg = installStatus.error || ""
|
||||
|
||||
switch (errorCode) {
|
||||
case "EMPTY_LOGIN":
|
||||
case "EMPTY_SSH_HOST":
|
||||
case "EMPTY_SSH_PORT":
|
||||
case "EMPTY_GRANT":
|
||||
// SSH 信息未配置的错误,不弹窗(页面上已有"没有设置"提示)
|
||||
break
|
||||
case "CREATE_ROOT_DIRECTORY_FAILED":
|
||||
teaweb.warn("节点 \"" + node.name + "\" 创建根目录失败:" + errMsg)
|
||||
break
|
||||
case "INSTALL_HELPER_FAILED":
|
||||
teaweb.warn("节点 \"" + node.name + "\" 安装助手失败:" + errMsg)
|
||||
break
|
||||
case "TEST_FAILED":
|
||||
teaweb.warn("节点 \"" + node.name + "\" 环境测试失败:" + errMsg)
|
||||
break
|
||||
case "RPC_TEST_FAILED":
|
||||
teaweb.warn("节点 \"" + node.name + "\" RPC通讯测试失败:" + errMsg)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新自动升级状态
|
||||
*/
|
||||
this.updateAutoUpgrade = function () {
|
||||
var vue = this
|
||||
|
||||
// @change 已经翻转了值,先记录新值并立即恢复
|
||||
let newValue = that.config.autoUpgrade
|
||||
that.config.autoUpgrade = !newValue
|
||||
|
||||
let msg = newValue ? "确定要开启自动升级功能吗?开启后节点会自动下载安装新版本。" : "确定要关闭自动升级功能吗?关闭后只能通过这里手动执行升级。"
|
||||
teaweb.confirm(msg, function () {
|
||||
vue.$post("/settings/upgrade")
|
||||
.params({
|
||||
autoUpgrade: newValue ? 1 : 0
|
||||
})
|
||||
.success(function () {
|
||||
that.config.autoUpgrade = newValue
|
||||
teaweb.successToast("设置保存成功")
|
||||
})
|
||||
.fail(function (resp) {
|
||||
teaweb.warn("设置保存失败:" + resp.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user