mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 05:00:25 +08:00 
			
		
		
		
	在节点手动安装页显示节点安装文件下载链接
This commit is contained in:
		@@ -5,6 +5,7 @@ import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/utils"
 | 
			
		||||
	"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/EdgeCommon/pkg/nodeconfigs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
@@ -27,7 +28,7 @@ func (this *CreateNodeAction) Init() {
 | 
			
		||||
func (this *CreateNodeAction) RunGet(params struct {
 | 
			
		||||
	ClusterId int64
 | 
			
		||||
}) {
 | 
			
		||||
	leftMenuItems := []maps.Map{
 | 
			
		||||
	var leftMenuItems = []maps.Map{
 | 
			
		||||
		{
 | 
			
		||||
			"name":     "单个创建",
 | 
			
		||||
			"url":      "/clusters/cluster/createNode?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
 | 
			
		||||
@@ -47,7 +48,7 @@ func (this *CreateNodeAction) RunGet(params struct {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	dnsRouteMaps := []maps.Map{}
 | 
			
		||||
	var dnsRouteMaps = []maps.Map{}
 | 
			
		||||
	this.Data["dnsDomainId"] = 0
 | 
			
		||||
	if clusterDNSResp.Domain != nil {
 | 
			
		||||
		domainId := clusterDNSResp.Domain.Id
 | 
			
		||||
@@ -76,8 +77,8 @@ func (this *CreateNodeAction) RunGet(params struct {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	apiNodes := apiNodesResp.ApiNodes
 | 
			
		||||
	apiEndpoints := []string{}
 | 
			
		||||
	var apiNodes = apiNodesResp.ApiNodes
 | 
			
		||||
	var apiEndpoints = []string{}
 | 
			
		||||
	for _, apiNode := range apiNodes {
 | 
			
		||||
		if !apiNode.IsOn {
 | 
			
		||||
			continue
 | 
			
		||||
@@ -86,6 +87,9 @@ func (this *CreateNodeAction) RunGet(params struct {
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
 | 
			
		||||
 | 
			
		||||
	// 安装文件下载
 | 
			
		||||
	this.Data["installerFiles"] = clusterutils.ListInstallerFiles()
 | 
			
		||||
 | 
			
		||||
	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("/installManual", new(InstallManualAction)).
 | 
			
		||||
			Post("/suggestLoginPorts", new(SuggestLoginPortsAction)).
 | 
			
		||||
			Get("/downloadInstaller", new(DownloadInstallerAction)).
 | 
			
		||||
 | 
			
		||||
			// 节点相关
 | 
			
		||||
			Prefix("/clusters/cluster/node").
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,16 @@
 | 
			
		||||
package node
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
 | 
			
		||||
	"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/clusterutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/iwind/TeaGo/actions"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -32,6 +36,20 @@ func (this *InstallAction) RunGet(params struct {
 | 
			
		||||
		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 {
 | 
			
		||||
		this.Data["installStatus"] = maps.Map{
 | 
			
		||||
@@ -70,7 +88,7 @@ func (this *InstallAction) RunGet(params struct {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	apiNodes := apiNodesResp.ApiNodes
 | 
			
		||||
	var apiNodes = apiNodesResp.ApiNodes
 | 
			
		||||
	apiEndpoints := []string{}
 | 
			
		||||
	for _, apiNode := range apiNodes {
 | 
			
		||||
		if !apiNode.IsOn {
 | 
			
		||||
@@ -87,6 +105,10 @@ func (this *InstallAction) RunGet(params struct {
 | 
			
		||||
	nodeMap["secret"] = node.Secret
 | 
			
		||||
	nodeMap["cluster"] = clusterMap
 | 
			
		||||
 | 
			
		||||
	// 安装文件
 | 
			
		||||
	var installerFiles = clusterutils.ListInstallerFiles()
 | 
			
		||||
	this.Data["installerFiles"] = installerFiles
 | 
			
		||||
 | 
			
		||||
	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 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>
 | 
			
		||||
            <source-code-box id="rpc-code" type="text/yaml">rpc:
 | 
			
		||||
    endpoints: [ {{apiEndpoints}} ]
 | 
			
		||||
@@ -148,6 +148,30 @@ 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>
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,29 @@
 | 
			
		||||
 | 
			
		||||
    <h4>方法2:手动安装</h4>
 | 
			
		||||
    <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>
 | 
			
		||||
            <td>配置文件<em>(configs/api.yaml)</em><br/>
 | 
			
		||||
            <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>
 | 
			
		||||
            <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 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>
 | 
			
		||||
@@ -71,6 +95,10 @@ secret: "{{node.secret}}"</source-code-box>
 | 
			
		||||
                <span v-else>{{node.installDir}}</span>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr v-if="exeRoot.length > 0">
 | 
			
		||||
            <td>最近运行目录</td>
 | 
			
		||||
            <td>{{exeRoot}}</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
    <div>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user