mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-23 01:40:29 +08:00
在节点手动安装页显示节点安装文件下载链接
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
@@ -27,7 +28,7 @@ func (this *CreateNodeAction) Init() {
|
|||||||
func (this *CreateNodeAction) RunGet(params struct {
|
func (this *CreateNodeAction) RunGet(params struct {
|
||||||
ClusterId int64
|
ClusterId int64
|
||||||
}) {
|
}) {
|
||||||
leftMenuItems := []maps.Map{
|
var leftMenuItems = []maps.Map{
|
||||||
{
|
{
|
||||||
"name": "单个创建",
|
"name": "单个创建",
|
||||||
"url": "/clusters/cluster/createNode?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
|
"url": "/clusters/cluster/createNode?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
|
||||||
@@ -47,7 +48,7 @@ func (this *CreateNodeAction) RunGet(params struct {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dnsRouteMaps := []maps.Map{}
|
var dnsRouteMaps = []maps.Map{}
|
||||||
this.Data["dnsDomainId"] = 0
|
this.Data["dnsDomainId"] = 0
|
||||||
if clusterDNSResp.Domain != nil {
|
if clusterDNSResp.Domain != nil {
|
||||||
domainId := clusterDNSResp.Domain.Id
|
domainId := clusterDNSResp.Domain.Id
|
||||||
@@ -76,8 +77,8 @@ func (this *CreateNodeAction) RunGet(params struct {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
apiNodes := apiNodesResp.ApiNodes
|
var apiNodes = apiNodesResp.ApiNodes
|
||||||
apiEndpoints := []string{}
|
var apiEndpoints = []string{}
|
||||||
for _, apiNode := range apiNodes {
|
for _, apiNode := range apiNodes {
|
||||||
if !apiNode.IsOn {
|
if !apiNode.IsOn {
|
||||||
continue
|
continue
|
||||||
@@ -86,6 +87,9 @@ func (this *CreateNodeAction) RunGet(params struct {
|
|||||||
}
|
}
|
||||||
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
|
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
|
||||||
|
|
||||||
|
// 安装文件下载
|
||||||
|
this.Data["installerFiles"] = clusterutils.ListInstallerFiles()
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DownloadInstallerAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *DownloadInstallerAction) Init() {
|
||||||
|
this.Nav("", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *DownloadInstallerAction) RunGet(params struct {
|
||||||
|
Name string
|
||||||
|
}) {
|
||||||
|
if len(params.Name) == 0 {
|
||||||
|
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||||
|
this.WriteString("file not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件名
|
||||||
|
// 以防止路径穿越等风险
|
||||||
|
if !regexp.MustCompile(`^[a-zA-Z0-9.-]+$`).MatchString(params.Name) {
|
||||||
|
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||||
|
this.WriteString("file not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var zipFile = Tea.Root + "/edge-api/deploy/" + params.Name
|
||||||
|
fp, err := os.OpenFile(zipFile, os.O_RDWR, 0444)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||||
|
this.WriteString("file not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||||
|
this.WriteString("file can not be opened")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = fp.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
stat, err := fp.Stat()
|
||||||
|
if err != nil {
|
||||||
|
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||||
|
this.WriteString("file can not be opened")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.AddHeader("Content-Disposition", "attachment; filename=\""+params.Name+"\";")
|
||||||
|
this.AddHeader("Content-Type", "application/zip")
|
||||||
|
this.AddHeader("Content-Length", types.String(stat.Size()))
|
||||||
|
_, _ = io.Copy(this.ResponseWriter, fp)
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ func init() {
|
|||||||
GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)).
|
GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)).
|
||||||
GetPost("/installManual", new(InstallManualAction)).
|
GetPost("/installManual", new(InstallManualAction)).
|
||||||
Post("/suggestLoginPorts", new(SuggestLoginPortsAction)).
|
Post("/suggestLoginPorts", new(SuggestLoginPortsAction)).
|
||||||
|
Get("/downloadInstaller", new(DownloadInstallerAction)).
|
||||||
|
|
||||||
// 节点相关
|
// 节点相关
|
||||||
Prefix("/clusters/cluster/node").
|
Prefix("/clusters/cluster/node").
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,6 +36,20 @@ func (this *InstallAction) RunGet(params struct {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 最近运行目录
|
||||||
|
var exeRoot = ""
|
||||||
|
if len(node.StatusJSON) > 0 {
|
||||||
|
var nodeStatus = &nodeconfigs.NodeStatus{}
|
||||||
|
err = json.Unmarshal(node.StatusJSON, nodeStatus)
|
||||||
|
if err == nil {
|
||||||
|
var exePath = nodeStatus.ExePath
|
||||||
|
if len(exePath) > 0 {
|
||||||
|
exeRoot = filepath.Dir(filepath.Dir(exePath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Data["exeRoot"] = exeRoot
|
||||||
|
|
||||||
// 安装信息
|
// 安装信息
|
||||||
if node.InstallStatus != nil {
|
if node.InstallStatus != nil {
|
||||||
this.Data["installStatus"] = maps.Map{
|
this.Data["installStatus"] = maps.Map{
|
||||||
@@ -70,7 +88,7 @@ func (this *InstallAction) RunGet(params struct {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
apiNodes := apiNodesResp.ApiNodes
|
var apiNodes = apiNodesResp.ApiNodes
|
||||||
apiEndpoints := []string{}
|
apiEndpoints := []string{}
|
||||||
for _, apiNode := range apiNodes {
|
for _, apiNode := range apiNodes {
|
||||||
if !apiNode.IsOn {
|
if !apiNode.IsOn {
|
||||||
@@ -87,6 +105,10 @@ func (this *InstallAction) RunGet(params struct {
|
|||||||
nodeMap["secret"] = node.Secret
|
nodeMap["secret"] = node.Secret
|
||||||
nodeMap["cluster"] = clusterMap
|
nodeMap["cluster"] = clusterMap
|
||||||
|
|
||||||
|
// 安装文件
|
||||||
|
var installerFiles = clusterutils.ListInstallerFiles()
|
||||||
|
this.Data["installerFiles"] = installerFiles
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package clusterutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type installerFile struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
OS string `json:"os"`
|
||||||
|
Arch string `json:"arch"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListInstallerFiles() []*installerFile {
|
||||||
|
var dir = Tea.Root + "/edge-api/deploy"
|
||||||
|
matches, err := filepath.Glob(dir + "/edge-node-*.zip")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = []*installerFile{}
|
||||||
|
var reg = regexp.MustCompile(`^edge-node-(\w+)-(\w+)-v([\w.]+)\.zip$`)
|
||||||
|
for _, match := range matches {
|
||||||
|
var baseName = filepath.Base(match)
|
||||||
|
var subMatches = reg.FindStringSubmatch(baseName)
|
||||||
|
if len(subMatches) >= 4 {
|
||||||
|
var osName = subMatches[1]
|
||||||
|
if len(osName) > 0 {
|
||||||
|
osName = strings.ToUpper(osName[:1]) + osName[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
var arch = subMatches[2]
|
||||||
|
if arch == "amd64" {
|
||||||
|
arch = "x86_64"
|
||||||
|
}
|
||||||
|
|
||||||
|
var version = subMatches[3]
|
||||||
|
if version != teaconst.Version { // 只能下载当前版本
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, &installerFile{
|
||||||
|
Name: subMatches[0],
|
||||||
|
OS: osName,
|
||||||
|
Arch: arch,
|
||||||
|
Version: version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序,将x86_64排在最上面
|
||||||
|
if len(result) > 0 {
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
return result[i].Arch == "x86_64"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
|
|
||||||
<!-- 手动安装 -->
|
<!-- 手动安装 -->
|
||||||
<div v-if="installMethod == 'manual'">
|
<div v-if="installMethod == 'manual'">
|
||||||
<div class="row">上传边缘节点程序到服务器并解压,然后在边缘节点安装目录下,复制<code-label>configs/api.template.yaml</code-label>为<code-label>configs/api.yaml</code-label>,然后修改文件里面的内容为以下内容:</div>
|
<div class="row">上传边缘节点程序到服务器并解压,然后在边缘节点安装目录下,复制<code-label>configs/api.template.yaml</code-label>为<code-label>configs/api.yaml</code-label>,修改文件里面的内容为以下内容:</div>
|
||||||
<div class="margin"></div>
|
<div class="margin"></div>
|
||||||
<source-code-box id="rpc-code" type="text/yaml">rpc:
|
<source-code-box id="rpc-code" type="text/yaml">rpc:
|
||||||
endpoints: [ {{apiEndpoints}} ]
|
endpoints: [ {{apiEndpoints}} ]
|
||||||
@@ -148,6 +148,30 @@ nodeId: "{{node.uniqueId}}"
|
|||||||
secret: "{{node.secret}}"</source-code-box>
|
secret: "{{node.secret}}"</source-code-box>
|
||||||
<div class="margin"></div>
|
<div class="margin"></div>
|
||||||
<div class="row">然后再使用<code-label>bin/edge-node start</code-label>命令启动节点。</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>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<a href="" @click.prevent="finish">安装完成</a>
|
<a href="" @click.prevent="finish">安装完成</a>
|
||||||
|
|||||||
@@ -23,6 +23,29 @@
|
|||||||
|
|
||||||
<h4>方法2:手动安装</h4>
|
<h4>方法2:手动安装</h4>
|
||||||
<table class="ui table definition selectable">
|
<table class="ui table definition selectable">
|
||||||
|
<tr v-if="installerFiles != null && installerFiles.length > 0">
|
||||||
|
<td>安装文件</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>
|
<tr>
|
||||||
<td>配置文件<em>(configs/api.yaml)</em><br/>
|
<td>配置文件<em>(configs/api.yaml)</em><br/>
|
||||||
<em><download-link :v-element="'rpc-code'" :v-file="'api.yaml'">[下载]</download-link ></em></td>
|
<em><download-link :v-element="'rpc-code'" :v-file="'api.yaml'">[下载]</download-link ></em></td>
|
||||||
@@ -37,8 +60,9 @@ secret: "{{node.secret}}"</source-code-box>
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="title">安装目录</td>
|
<td class="title">安装目录</td>
|
||||||
<td>
|
<td>
|
||||||
<div v-if="node.installDir.length == 0">使用集群设置<span
|
<div v-if="node.installDir.length == 0">
|
||||||
v-if="node.cluster != null && node.cluster.installDir.length > 0">({{node.cluster.installDir}})</span>
|
<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>
|
</div>
|
||||||
<span v-else>{{node.installDir}}</span>
|
<span v-else>{{node.installDir}}</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -71,6 +95,10 @@ secret: "{{node.secret}}"</source-code-box>
|
|||||||
<span v-else>{{node.installDir}}</span>
|
<span v-else>{{node.installDir}}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr v-if="exeRoot.length > 0">
|
||||||
|
<td>最近运行目录</td>
|
||||||
|
<td>{{exeRoot}}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user