mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-12-02 01:40:25 +08:00
增加本地API节点需要升级提示
This commit is contained in:
169
internal/web/actions/default/dashboard/dashboardutils/utils.go
Normal file
169
internal/web/actions/default/dashboard/dashboardutils/utils.go
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package dashboardutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/lists"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckDiskPartitions 检查服务器磁盘空间
|
||||||
|
func CheckDiskPartitions(thresholdPercent float64) (path string, usage uint64, usagePercent float64, shouldWarning bool) {
|
||||||
|
partitions, err := disk.Partitions(false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootFS = ""
|
||||||
|
|
||||||
|
for _, p := range partitions {
|
||||||
|
if p.Mountpoint == "/" {
|
||||||
|
rootFS = p.Fstype
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range partitions {
|
||||||
|
if p.Mountpoint == "/boot" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p.Fstype != rootFS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stat, _ := disk.Usage(p.Mountpoint)
|
||||||
|
if stat != nil {
|
||||||
|
if stat.Used < 2*uint64(sizes.G) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stat.UsedPercent > thresholdPercent {
|
||||||
|
path = stat.Path
|
||||||
|
usage = stat.Used
|
||||||
|
usagePercent = stat.UsedPercent
|
||||||
|
shouldWarning = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckLocalAPINode 检查本地的API节点
|
||||||
|
func CheckLocalAPINode(rpcClient *rpc.RPCClient, ctx context.Context) (exePath string, runtimeVersion string, fileVersion string, ok bool) {
|
||||||
|
resp, err := rpcClient.APINodeRPC().FindCurrentAPINode(ctx, &pb.FindCurrentAPINodeRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp.ApiNode == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var instanceCode = resp.ApiNode.InstanceCode
|
||||||
|
if len(instanceCode) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var statusJSON = resp.ApiNode.StatusJSON
|
||||||
|
if len(statusJSON) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var status = &nodeconfigs.NodeStatus{}
|
||||||
|
err = json.Unmarshal(statusJSON, status)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runtimeVersion = status.BuildVersion
|
||||||
|
|
||||||
|
if len(runtimeVersion) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if stringutil.VersionCompare(runtimeVersion, teaconst.APINodeVersion) >= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exePath = status.ExePath
|
||||||
|
if len(exePath) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := os.Stat(exePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if stat.IsDir() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例信息
|
||||||
|
{
|
||||||
|
var outputBuffer = &bytes.Buffer{}
|
||||||
|
var cmd = exec.Command(exePath, "instance")
|
||||||
|
cmd.Stdout = outputBuffer
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputBytes = outputBuffer.Bytes()
|
||||||
|
if len(outputBytes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var instanceMap = maps.Map{}
|
||||||
|
err = json.Unmarshal(bytes.TrimSpace(outputBytes), &instanceMap)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if instanceMap.GetString("code") != instanceCode {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件版本
|
||||||
|
{
|
||||||
|
var outputBuffer = &bytes.Buffer{}
|
||||||
|
var cmd = exec.Command(exePath, "-v")
|
||||||
|
cmd.Stdout = outputBuffer
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputString = outputBuffer.String()
|
||||||
|
if len(outputString) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var subMatch = regexp.MustCompile(`\s+v([\d.]+)\s+`).FindStringSubmatch(outputString)
|
||||||
|
if len(subMatch) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileVersion = subMatch[1]
|
||||||
|
|
||||||
|
// 文件版本是否为最新
|
||||||
|
if fileVersion != teaconst.APINodeVersion {
|
||||||
|
fileVersion = runtimeVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -7,16 +7,13 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/dashboardutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/iwind/TeaGo/lists"
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"github.com/shirou/gopsutil/v3/disk"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexAction struct {
|
type IndexAction struct {
|
||||||
@@ -66,7 +63,7 @@ func (this *IndexAction) RunPost(params struct{}) {
|
|||||||
|
|
||||||
// 检查当前服务器空间
|
// 检查当前服务器空间
|
||||||
var diskUsageWarning = ""
|
var diskUsageWarning = ""
|
||||||
diskPath, diskUsage, diskUsagePercent, shouldWarning := this.checkDiskPartitions(90)
|
diskPath, diskUsage, diskUsagePercent, shouldWarning := dashboardutils.CheckDiskPartitions(90)
|
||||||
if shouldWarning {
|
if shouldWarning {
|
||||||
diskUsageWarning = "当前服务器磁盘空间不足,请立即扩充容量,文件路径:" + diskPath + ",已使用:" + types.String(diskUsage/1024/1024/1024) + "G,已使用比例:" + fmt.Sprintf("%.2f%%", diskUsagePercent) + ",仅剩余空间:" + fmt.Sprintf("%.2f%%", 100-diskUsagePercent) + "。"
|
diskUsageWarning = "当前服务器磁盘空间不足,请立即扩充容量,文件路径:" + diskPath + ",已使用:" + types.String(diskUsage/1024/1024/1024) + "G,已使用比例:" + fmt.Sprintf("%.2f%%", diskUsagePercent) + ",仅剩余空间:" + fmt.Sprintf("%.2f%%", 100-diskUsagePercent) + "。"
|
||||||
}
|
}
|
||||||
@@ -263,49 +260,18 @@ func (this *IndexAction) RunPost(params struct{}) {
|
|||||||
this.Data["metricCharts"] = chartMaps
|
this.Data["metricCharts"] = chartMaps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当前API节点版本
|
||||||
|
{
|
||||||
|
exePath, runtimeVersion, fileVersion, ok := dashboardutils.CheckLocalAPINode(this.RPC(), this.AdminContext())
|
||||||
|
if ok {
|
||||||
|
this.Data["localLowerVersionAPINode"] = maps.Map{
|
||||||
|
"exePath": exePath,
|
||||||
|
"runtimeVersion": runtimeVersion,
|
||||||
|
"fileVersion": fileVersion,
|
||||||
|
"isRestarting": false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查服务器磁盘空间
|
|
||||||
func (this *IndexAction) checkDiskPartitions(thresholdPercent float64) (path string, usage uint64, usagePercent float64, shouldWarning bool) {
|
|
||||||
partitions, err := disk.Partitions(false)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootFS = ""
|
|
||||||
|
|
||||||
for _, p := range partitions {
|
|
||||||
if p.Mountpoint == "/" {
|
|
||||||
rootFS = p.Fstype
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range partitions {
|
|
||||||
if p.Mountpoint == "/boot" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if p.Fstype != rootFS {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
stat, _ := disk.Usage(p.Mountpoint)
|
|
||||||
if stat != nil {
|
|
||||||
if stat.Used < 2*uint64(sizes.G) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if stat.UsedPercent > thresholdPercent {
|
|
||||||
path = stat.Path
|
|
||||||
usage = stat.Used
|
|
||||||
usagePercent = stat.UsedPercent
|
|
||||||
shouldWarning = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ func init() {
|
|||||||
Data("teaMenu", "dashboard").
|
Data("teaMenu", "dashboard").
|
||||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
|
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
|
||||||
GetPost("", new(IndexAction)).
|
GetPost("", new(IndexAction)).
|
||||||
|
Post("/restartLocalAPINode", new(RestartLocalAPINodeAction)).
|
||||||
EndAll()
|
EndAll()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package dashboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RestartLocalAPINodeAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RestartLocalAPINodeAction) RunPost(params struct {
|
||||||
|
ExePath string
|
||||||
|
}) {
|
||||||
|
// 检查当前用户是超级用户
|
||||||
|
adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: this.AdminId()})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if adminResp.Admin == nil || !adminResp.Admin.IsSuper {
|
||||||
|
this.Fail("请切换到超级用户进行此操作")
|
||||||
|
}
|
||||||
|
|
||||||
|
var exePath = params.ExePath
|
||||||
|
if len(exePath) == 0 {
|
||||||
|
this.Fail("找不到要重启的API节点文件")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var stdoutBuffer = &bytes.Buffer{}
|
||||||
|
var cmd = exec.Command(exePath, "restart")
|
||||||
|
cmd.Stdout = stdoutBuffer
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
this.Fail("运行失败:输出:" + stdoutBuffer.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已启动
|
||||||
|
var countTries = 120
|
||||||
|
for {
|
||||||
|
countTries--
|
||||||
|
if countTries < 0 {
|
||||||
|
this.Fail("启动超时,请尝试手动启动")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdoutBuffer = &bytes.Buffer{}
|
||||||
|
var cmd = exec.Command(exePath, "status")
|
||||||
|
cmd.Stdout = stdoutBuffer
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if regexp.MustCompile(`pid:\s*\d+`).
|
||||||
|
MatchString(stdoutBuffer.String()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
@@ -23,16 +23,26 @@
|
|||||||
<a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
<a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 升级提醒 -->
|
<!-- 边缘节点升级提醒 -->
|
||||||
<div class="ui icon message error" v-if="!isLoading && nodeUpgradeInfo.count > 0">
|
<div class="ui icon message error" v-if="!isLoading && nodeUpgradeInfo.count > 0">
|
||||||
<i class="icon warning circle"></i>
|
<i class="icon warning circle"></i>
|
||||||
<a href="/clusters">升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,系统正在尝试自动升级...</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
<a href="/clusters">升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,系统正在尝试自动升级...</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- API节点升级提醒 -->
|
||||||
<div class="ui icon message error" v-if="!isLoading && apiNodeUpgradeInfo.count > 0">
|
<div class="ui icon message error" v-if="!isLoading && apiNodeUpgradeInfo.count > 0">
|
||||||
<i class="icon warning circle"></i>
|
<i class="icon warning circle"></i>
|
||||||
<a href="/api">升级提醒:有 {{apiNodeUpgradeInfo.count}} 个API节点需要升级到 v{{apiNodeUpgradeInfo.version}} 版本;如果已经升级,请尝试重启API节点进程。</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
<a href="/api">升级提醒:有 {{apiNodeUpgradeInfo.count}} 个API节点需要升级到 v{{apiNodeUpgradeInfo.version}} 版本;如果已经升级,请尝试重启API节点进程。</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 本地API节点 -->
|
||||||
|
<div class="ui icon message error" v-if="!isLoading && localLowerVersionAPINode != null">
|
||||||
|
<i class="icon warning circle"></i>
|
||||||
|
<span v-if="localLowerVersionAPINode.runtimeVersion == localLowerVersionAPINode.fileVersion">升级提醒:发现一个正在使用的本地API节点版本需要升级,文件位置:{{localLowerVersionAPINode.exePath}},当前版本:v{{localLowerVersionAPINode.runtimeVersion}}。</span>
|
||||||
|
<span v-if="localLowerVersionAPINode.runtimeVersion != localLowerVersionAPINode.fileVersion">升级提醒:发现一个正在使用的本地API节点版本文件已经更新,但需要重启后生效,文件位置:{{localLowerVersionAPINode.exePath}}。 <a href="" @click.prevent="restartAPINode" v-if="!isRestartingLocalAPINode">[帮我重启]</a><span v-if="isRestartingLocalAPINode">尝试重启中...</span></span>
|
||||||
|
<a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 没有磁盘空间提醒 -->
|
<!-- 没有磁盘空间提醒 -->
|
||||||
<div class="ui icon message error" v-if="!isLoading && dashboard.diskUsageWarning != null && dashboard.diskUsageWarning.length > 0">
|
<div class="ui icon message error" v-if="!isLoading && dashboard.diskUsageWarning != null && dashboard.diskUsageWarning.length > 0">
|
||||||
<i class="icon warning circle"></i>
|
<i class="icon warning circle"></i>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Tea.context(function () {
|
|||||||
this.trafficTab = "hourly"
|
this.trafficTab = "hourly"
|
||||||
this.metricCharts = []
|
this.metricCharts = []
|
||||||
this.dashboard = {}
|
this.dashboard = {}
|
||||||
|
this.localLowerVersionAPINode = null
|
||||||
|
|
||||||
this.$delay(function () {
|
this.$delay(function () {
|
||||||
this.$post("$")
|
this.$post("$")
|
||||||
@@ -193,4 +194,29 @@ Tea.context(function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重启本地API节点
|
||||||
|
this.isRestartingLocalAPINode = false
|
||||||
|
this.restartAPINode = function () {
|
||||||
|
if (this.isRestartingLocalAPINode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.localLowerVersionAPINode == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.isRestartingLocalAPINode = true
|
||||||
|
this.localLowerVersionAPINode.isRestarting = true
|
||||||
|
this.$post("/dashboard/restartLocalAPINode")
|
||||||
|
.params({
|
||||||
|
"exePath": this.localLowerVersionAPINode.exePath
|
||||||
|
})
|
||||||
|
.timeout(300)
|
||||||
|
.success(function () {
|
||||||
|
teaweb.reload()
|
||||||
|
})
|
||||||
|
.done(function () {
|
||||||
|
this.isRestartingLocalAPINode = false
|
||||||
|
this.localLowerVersionAPINode.isRestarting = false
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user