mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-07 23:30:26 +08:00
实现迁移辅助功能(系统设置 -- 高级设置 -- 迁移)
This commit is contained in:
@@ -40,6 +40,7 @@ func (this *AdvancedHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNex
|
||||
if teaconst.IsPlus {
|
||||
tabbar.Add("监控节点", "", "/settings/monitorNodes", "", this.tab == "monitorNodes")
|
||||
}
|
||||
tabbar.Add("迁移", "", "/settings/transfer", "", this.tab == "transfer")
|
||||
if teaconst.BuildPlus {
|
||||
tabbar.Add("商业版认证", "", "/settings/authority", "", this.tab == "authority")
|
||||
}
|
||||
|
||||
17
internal/web/actions/default/settings/transfer/index.go
Normal file
17
internal/web/actions/default/settings/transfer/index.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package transfer
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "transfer", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.Show()
|
||||
}
|
||||
23
internal/web/actions/default/settings/transfer/init.go
Normal file
23
internal/web/actions/default/settings/transfer/init.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
|
||||
Helper(settingutils.NewAdvancedHelper("transfer")).
|
||||
Prefix("/settings/transfer").
|
||||
Get("", new(IndexAction)).
|
||||
Post("/validateAPI", new(ValidateAPIAction)).
|
||||
Post("/updateHosts", new(UpdateHostsAction)).
|
||||
Post("/upgradeNodes", new(UpgradeNodesAction)).
|
||||
Post("/statNodes", new(StatNodesAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
23
internal/web/actions/default/settings/transfer/statNodes.go
Normal file
23
internal/web/actions/default/settings/transfer/statNodes.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type StatNodesAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *StatNodesAction) RunPost(params struct{}) {
|
||||
countNodesResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{ActiveState: 1})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["countNodes"] = countNodesResp.Count
|
||||
|
||||
this.Success()
|
||||
}
|
||||
158
internal/web/actions/default/settings/transfer/updateHosts.go
Normal file
158
internal/web/actions/default/settings/transfer/updateHosts.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
)
|
||||
|
||||
type UpdateHostsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdateHostsAction) RunPost(params struct {
|
||||
Protocol string
|
||||
Host string
|
||||
Port string
|
||||
|
||||
OldHosts []string
|
||||
NewHosts []string
|
||||
}) {
|
||||
if len(params.OldHosts) != len(params.NewHosts) {
|
||||
this.Fail("参数配置错误,请刷新页面后重试")
|
||||
}
|
||||
|
||||
// 检查端口
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
this.Fail("加载当前平台的API配置失败:" + err.Error())
|
||||
}
|
||||
var apiURL = params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port
|
||||
config.RPC.Endpoints = []string{apiURL}
|
||||
client, err := rpc.NewRPCClient(config, false)
|
||||
if err != nil {
|
||||
this.Fail("检查API节点地址出错:" + err.Error())
|
||||
}
|
||||
defer func() {
|
||||
_ = client.Close()
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
this.FailField("host", "测试API节点时出错,请检查配置,错误信息:"+err.Error())
|
||||
}
|
||||
_, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.APIContext(0), &pb.FindCurrentAPINodeVersionRequest{})
|
||||
if err != nil {
|
||||
this.FailField("host", "无法连接此API节点,错误信息:"+err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = client.Close()
|
||||
}()
|
||||
|
||||
// API节点列表
|
||||
nodesResp, err := client.APINodeRPC().FindAllEnabledAPINodes(client.Context(0), &pb.FindAllEnabledAPINodesRequest{})
|
||||
if err != nil {
|
||||
this.Fail("获取API节点列表失败,错误信息:" + err.Error())
|
||||
}
|
||||
var endpoints = []string{}
|
||||
for _, node := range nodesResp.ApiNodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
|
||||
// http
|
||||
if len(node.HttpJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.HttpJSON = bytes.ReplaceAll(node.HttpJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
}
|
||||
|
||||
// https
|
||||
if len(node.HttpsJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.HttpsJSON = bytes.ReplaceAll(node.HttpsJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
}
|
||||
|
||||
// restHTTP
|
||||
if len(node.RestHTTPJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.RestHTTPJSON = bytes.ReplaceAll(node.RestHTTPJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
}
|
||||
|
||||
// restHTTPS
|
||||
if len(node.RestHTTPSJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.RestHTTPSJSON = bytes.ReplaceAll(node.RestHTTPSJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
}
|
||||
|
||||
// access addrs
|
||||
if len(node.AccessAddrsJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.AccessAddrsJSON = bytes.ReplaceAll(node.AccessAddrsJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
|
||||
var addrs []*serverconfigs.NetworkAddressConfig
|
||||
err = json.Unmarshal(node.AccessAddrsJSON, &addrs)
|
||||
if err != nil {
|
||||
this.Fail("读取节点访问地址失败:" + err.Error())
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
err = addr.Init()
|
||||
if err != nil {
|
||||
// 暂时不提示错误
|
||||
continue
|
||||
}
|
||||
for _, a := range addr.FullAddresses() {
|
||||
if !lists.ContainsString(endpoints, a) {
|
||||
endpoints = append(endpoints, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存
|
||||
_, err = client.APINodeRPC().UpdateAPINode(client.Context(0), &pb.UpdateAPINodeRequest{
|
||||
ApiNodeId: node.Id,
|
||||
Name: node.Name,
|
||||
Description: node.Description,
|
||||
HttpJSON: node.HttpJSON,
|
||||
HttpsJSON: node.HttpsJSON,
|
||||
AccessAddrsJSON: node.AccessAddrsJSON,
|
||||
IsOn: node.IsOn,
|
||||
RestIsOn: node.RestIsOn,
|
||||
RestHTTPJSON: node.RestHTTPJSON,
|
||||
RestHTTPSJSON: node.RestHTTPSJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.Fail("保存API节点信息失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/messageconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type UpgradeNodesAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpgradeNodesAction) RunPost(params struct {
|
||||
ApiNodeProtocol string
|
||||
ApiNodeHost string
|
||||
ApiNodePort int
|
||||
}) {
|
||||
nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), &pb.ListEnabledNodesMatchRequest{
|
||||
ActiveState: 1,
|
||||
Size: 100,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var nodes = nodesResp.Nodes
|
||||
this.Data["hasNext"] = len(nodes) > 0
|
||||
this.Data["count"] = len(nodes)
|
||||
|
||||
if len(nodes) > 0 {
|
||||
var message = &messageconfigs.ChangeAPINodeMessage{
|
||||
Addr: params.ApiNodeProtocol + "://" + configutils.QuoteIP(params.ApiNodeHost) + ":" + types.String(params.ApiNodePort),
|
||||
}
|
||||
messageJSON, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, node := range nodesResp.Nodes {
|
||||
resp, err := this.RPC().NodeRPC().SendCommandToNode(this.AdminContext(), &pb.NodeStreamMessage{
|
||||
NodeId: node.Id,
|
||||
TimeoutSeconds: 3,
|
||||
Code: messageconfigs.MessageCodeChangeAPINode,
|
||||
DataJSON: messageJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if !resp.IsOk {
|
||||
this.Fail(resp.Message)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
152
internal/web/actions/default/settings/transfer/validateAPI.go
Normal file
152
internal/web/actions/default/settings/transfer/validateAPI.go
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type ValidateAPIAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ValidateAPIAction) RunPost(params struct {
|
||||
Host string
|
||||
Port string
|
||||
Protocol string
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Must.
|
||||
Field("newAPINodeHost", params.Host).
|
||||
Require("请输入新的API节点IP或域名").
|
||||
Field("newAPINodePort", params.Port).
|
||||
Require("请输入新的API节点端口")
|
||||
|
||||
if !regexp.MustCompile(`^\d{1,5}$`).MatchString(params.Port) {
|
||||
this.FailField("newAPINodePort", "请输入正确的端口")
|
||||
}
|
||||
|
||||
// 检查端口
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
this.Fail("加载当前平台的API配置失败:" + err.Error())
|
||||
}
|
||||
config.RPC.Endpoints = []string{params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port}
|
||||
client, err := rpc.NewRPCClient(config, false)
|
||||
if err != nil {
|
||||
this.Fail("检查API节点地址出错:" + err.Error())
|
||||
}
|
||||
defer func() {
|
||||
_ = client.Close()
|
||||
}()
|
||||
|
||||
_, err = client.AdminRPC().FindAdminFullname(this.AdminContext(), &pb.FindAdminFullnameRequest{AdminId: this.AdminId()})
|
||||
if err != nil {
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
if statusErr.Code() == codes.Unavailable {
|
||||
this.Fail("测试新API节点失败:无法连接新的API节点:请检查:1、API节点地址和端口是否正确;2、防火墙或安全策略是否已正确设置。详细原因:" + err.Error())
|
||||
}
|
||||
}
|
||||
this.Fail("测试新API节点失败:" + err.Error())
|
||||
}
|
||||
|
||||
// 所有API节点
|
||||
apiNodesResp, err := client.APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var apiNodes = apiNodesResp.ApiNodes
|
||||
var hosts = []string{}
|
||||
for _, node := range apiNodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
|
||||
// http
|
||||
if len(node.HttpJSON) > 0 {
|
||||
var config = &serverconfigs.HTTPProtocolConfig{}
|
||||
err = json.Unmarshal(node.HttpJSON, config)
|
||||
if err != nil {
|
||||
this.Fail("读取节点HTTP信息失败:" + err.Error())
|
||||
}
|
||||
for _, listen := range config.Listen {
|
||||
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
|
||||
hosts = append(hosts, listen.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https
|
||||
if len(node.HttpsJSON) > 0 {
|
||||
var config = &serverconfigs.HTTPSProtocolConfig{}
|
||||
err = json.Unmarshal(node.HttpsJSON, config)
|
||||
if err != nil {
|
||||
this.Fail("读取节点HTTPS信息失败:" + err.Error())
|
||||
}
|
||||
for _, listen := range config.Listen {
|
||||
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
|
||||
hosts = append(hosts, listen.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// restHTTP
|
||||
if len(node.RestHTTPJSON) > 0 {
|
||||
var config = &serverconfigs.HTTPProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPJSON, config)
|
||||
if err != nil {
|
||||
this.Fail("读取节点REST HTTP信息失败:" + err.Error())
|
||||
}
|
||||
for _, listen := range config.Listen {
|
||||
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
|
||||
hosts = append(hosts, listen.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// restHTTPS
|
||||
if len(node.RestHTTPSJSON) > 0 {
|
||||
var config = &serverconfigs.HTTPSProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPSJSON, config)
|
||||
if err != nil {
|
||||
this.Fail("读取节点REST HTTPS信息失败:" + err.Error())
|
||||
}
|
||||
for _, listen := range config.Listen {
|
||||
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
|
||||
hosts = append(hosts, listen.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// access addrs
|
||||
if len(node.AccessAddrsJSON) > 0 {
|
||||
var addrs []*serverconfigs.NetworkAddressConfig
|
||||
err = json.Unmarshal(node.AccessAddrsJSON, &addrs)
|
||||
if err != nil {
|
||||
this.Fail("读取节点访问地址失败:" + err.Error())
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if len(addr.Host) > 0 && !lists.ContainsString(hosts, addr.Host) {
|
||||
hosts = append(hosts, addr.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Data["hosts"] = hosts
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -117,6 +117,7 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/profile"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/security"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/transfer"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ui"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/upgrade"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-ui"
|
||||
|
||||
@@ -2306,7 +2306,7 @@ window.Tea.Action = function (action, params) {
|
||||
console.log(error);
|
||||
|
||||
if (typeof (_errorFn) === "function") {
|
||||
_errorFn.call(Tea.Vue, {});
|
||||
_errorFn.call(Tea.Vue, { message: error.message });
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
|
||||
13
web/views/@default/settings/transfer/index.css
Normal file
13
web/views/@default/settings/transfer/index.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.steps .step.active .content {
|
||||
font-weight: bold;
|
||||
}
|
||||
.step-box .button-group {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.step-box .button-group .next {
|
||||
float: right;
|
||||
}
|
||||
.step-box .content {
|
||||
min-height: 6em;
|
||||
}
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
1
web/views/@default/settings/transfer/index.css.map
Normal file
1
web/views/@default/settings/transfer/index.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,MACC,MAAK,OACJ;EACC,iBAAA;;AAKH,SACC;EACC,eAAA;;AAFF,SACC,cAGC;EACC,YAAA;;AALH,SASC;EACC,eAAA","file":"index.css"}
|
||||
304
web/views/@default/settings/transfer/index.html
Normal file
304
web/views/@default/settings/transfer/index.html
Normal file
@@ -0,0 +1,304 @@
|
||||
{$layout}
|
||||
|
||||
<div class="margin"></div>
|
||||
|
||||
<div class="ui steps fluid small">
|
||||
<div class="ui step" :class="{active: step == STEP_PREPARE}">
|
||||
<div class="content">开始</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == STEP_DATABASE}">
|
||||
<div class="content">迁移数据库</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == STEP_API}">
|
||||
<div class="content">迁移API节点</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == STEP_ADDRESS}">
|
||||
<div class="content">变更地址</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == STEP_ADMIN}">
|
||||
<div class="content">迁移管理平台</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == STEP_UPGRADE}">
|
||||
<div class="content">升级节点配置</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == STEP_FINISH}">
|
||||
<div class="content">完成</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 准备工作 -->
|
||||
<div class="step-box" v-if="step == STEP_PREPARE">
|
||||
<div class="content">
|
||||
<p>通过此引导程序可以帮助你将当前平台配置迁移到新的平台中。</p>
|
||||
<p>每个步骤请小心操作,一旦配置错误可能需要手工还原。</p>
|
||||
</div>
|
||||
|
||||
<div class="ui button-group">
|
||||
<button class="ui button primary next" @click.prevent="doPrepare()">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据库 -->
|
||||
<div class="step-box" v-if="step == STEP_DATABASE">
|
||||
<form class="ui form">
|
||||
<table class="ui table selectable definition">
|
||||
<tr>
|
||||
<td class="title">数据库地址是否改变?</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="0" v-model="databaseChanged" id="database-changed-0" class="hidden"/>
|
||||
<label for="database-changed-0">没有变</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="1" v-model="databaseChanged" id="database-changed-1" class="hidden"/>
|
||||
<label for="database-changed-1">变了</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="databaseChanged == 1">
|
||||
<td>数据是否已经导入到新的数据库?</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="0" v-model="databaseTransferred" id="database-transferred-0" class="hidden"/>
|
||||
<label for="database-transferred-0">没有导入</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="1" v-model="databaseTransferred" id="database-transferred-1" class="hidden"/>
|
||||
<label for="database-transferred-1">已经导入</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment" v-if="databaseTransferred == 0"><span class="red">请自行将老的数据库中数据导入到新的数据库中。</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<div class="ui button-group">
|
||||
<button class="ui button prev" @click.prevent="doBack(STEP_PREPARE)">上一步</button>
|
||||
<button class="ui button primary next" @click.prevent="doDatabase()">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 检查API节点 -->
|
||||
<div class="step-box" v-if="step == STEP_API">
|
||||
<form class="ui form">
|
||||
<table class="ui table selectable definition">
|
||||
<tr>
|
||||
<td class="title">API节点地址是否变更?</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="1" v-model="apiNodeChanged" id="api-node-changed-1" class="hidden"/>
|
||||
<label for="api-node-changed-1">已经变更</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="0" v-model="apiNodeChanged" id="api-node-changed-0" class="hidden"/>
|
||||
<label for="api-node-changed-0">没有变更</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody v-show="apiNodeChanged == 1">
|
||||
<tr>
|
||||
<td>是否已安装新的API节点</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="1" v-model="apiNodeInstalled" id="api-node-installed-1" class="hidden"/>
|
||||
<label for="api-node-installed-1">已经安装</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="0" v-model="apiNodeInstalled" id="api-node-installed-0" class="hidden"/>
|
||||
<label for="api-node-installed-0">没有安装</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment" v-if="apiNodeInstalled == 0"><span class="red">请先安装新的API节点,请拷贝当前的API节点配置<code-label>configs/api.yaml</code-label>和<code-label>configs/db.yaml</code-label>到新API节点对应的位置,并记得修改其中的<code-label>configs/db.yaml</code-label>中的数据库地址、用户名、密码。</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>新的API节点IP或域名 *</td>
|
||||
<td>
|
||||
<input type="text" name="newAPINodeHost" placeholder="IP或域名" v-model="apiNodeHost" style="width: 14em" maxlength="100"/>
|
||||
<p class="comment">如果有多个IP或者域名,填写任意其中一个即可。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>新的API节点端口 *</td>
|
||||
<td>
|
||||
<input type="text" name="newAPINodePort" v-model="apiNodePort" style="width: 5em" maxlength="5" placeholder="端口"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>新的API节点协议</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="apiNodeProtocol">
|
||||
<option value="http">HTTP</option>
|
||||
<option value="https">HTTPS</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="ui button prev" @click.prevent="doBack(STEP_DATABASE)">上一步</button>
|
||||
<button class="ui button primary next" @click.prevent="doAPI()">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 地址 -->
|
||||
<div class="step-box" v-show="step == STEP_ADDRESS">
|
||||
<form method="post" class="ui form" data-tea-action=".updateHosts" data-tea-success="doAddress">
|
||||
<input type="hidden" name="host" :value="apiNodeHost"/>
|
||||
<input type="hidden" name="port" :value="apiNodePort"/>
|
||||
<input type="hidden" name="protocol" :value="apiNodeProtocol"/>
|
||||
<table class="ui table selectable celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50%">API节点原地址</th>
|
||||
<th>API节点新地址(留空表示不修改)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="host in apiAddressHosts">
|
||||
<td>{{host}}</td>
|
||||
<td>
|
||||
<input type="text" maxlength="100" name="newHosts"/>
|
||||
<input type="hidden" name="oldHosts" :value="host"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<div class="button-group">
|
||||
<button class="ui button prev" @click.prevent="doBack(STEP_API)">上一步</button>
|
||||
<button type="submit" class="ui button primary next">下一步</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- admin -->
|
||||
<div class="step-box" v-if="step == STEP_ADMIN">
|
||||
<form class="ui form">
|
||||
<table class="ui table selectable definition">
|
||||
<tr>
|
||||
<td class="title">管理平台地址是否变更?</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="1" v-model="adminNodeChanged" id="admin-node-changed-1" class="hidden"/>
|
||||
<label for="admin-node-changed-1">已经变更</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="0" v-model="adminNodeChanged" id="admin-node-changed-0" class="hidden"/>
|
||||
<label for="admin-node-changed-0">没有变更</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody v-show="adminNodeChanged == 1">
|
||||
<tr>
|
||||
<td>是否已安装新的管理平台</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="1" v-model="adminNodeInstalled" id="admin-node-installed-1" class="hidden"/>
|
||||
<label for="admin-node-installed-1">已经安装</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="0" v-model="adminNodeInstalled" id="admin-node-installed-0" class="hidden"/>
|
||||
<label for="admin-node-installed-0">没有安装</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment" v-if="adminNodeInstalled == 0"><span class="red">请先安装新的管理平台,请拷贝当前的管理平台下的<code-label>configs/</code-label>目录下的配置到新管理平台对应的位置。</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<div class="ui button-group">
|
||||
<button class="ui button prev" @click.prevent="doBack(STEP_API)" v-if="apiNodeChanged == 0">上一步</button>
|
||||
<button class="ui button prev" @click.prevent="doBack(STEP_ADDRESS)" v-if="apiNodeChanged == 1">上一步</button>
|
||||
<button class="ui button primary next" @click.prevent="doAdmin()">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 升级节点配置 -->
|
||||
<div class="step-box" v-if="step == STEP_UPGRADE">
|
||||
<form class="ui form">
|
||||
<table class="ui table selectable definition">
|
||||
<tr>
|
||||
<td class="title">升级节点API地址配置</td>
|
||||
<td>
|
||||
<span>{{apiNodeProtocol}}://<span v-if="apiNodeHost.indexOf(':')>0">[{{apiNodeHost}}]</span><span v-else>{{apiNodeHost}}</span>:{{apiNodePort}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>当前平台节点数</td>
|
||||
<td>
|
||||
<span v-if="isUpgrading">{{countNodes}}</span>
|
||||
<span v-else>-</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>新平台节点数</td>
|
||||
<td>
|
||||
<span v-if="isUpgrading">{{countFinishedNodes}}</span>
|
||||
<span v-else>-</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>完成比例</td>
|
||||
<td>
|
||||
<span v-if="isUpgrading">{{percentNodes}}%</span>
|
||||
<span v-else>-</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<div class="ui button-group">
|
||||
<button class="ui button prev" @click.prevent="doBack(STEP_ADMIN)">上一步</button>
|
||||
<button class="ui button primary next" @click.prevent="doStartUpgrade()" v-if="!isUpgrading">开始升级</button>
|
||||
<button class="ui button next" :class="{disabled: percentNodes<100}" v-if="isUpgrading && percentNodes < 100">等待完成</button>
|
||||
<button class="ui button primary next" @click.prevent="doUpgrade()" v-if="isUpgrading && percentNodes == 100">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 完成 -->
|
||||
<div class="step-box" v-if="step == STEP_FINISH">
|
||||
<div class="content">
|
||||
<p v-if="adminNodeChanged == 1">所有迁移任务已完成,请登录新的管理平台进行管理。</p>
|
||||
<p v-if="adminNodeChanged == 0">所有迁移任务已完成。</p>
|
||||
</div>
|
||||
<div class="ui button-group">
|
||||
<button class="ui button prev" @click.prevent="doBack(STEP_UPGRADE)">上一步</button>
|
||||
<button class="ui button primary next" @click.prevent="doFinish()">完成</button>
|
||||
</div>
|
||||
</div>
|
||||
191
web/views/@default/settings/transfer/index.js
Normal file
191
web/views/@default/settings/transfer/index.js
Normal file
@@ -0,0 +1,191 @@
|
||||
Tea.context(function () {
|
||||
this.STEP_PREPARE = "prepare"
|
||||
this.STEP_DATABASE = "database"
|
||||
this.STEP_ADMIN = "admin"
|
||||
this.STEP_API = "api"
|
||||
this.STEP_ADDRESS = "address"
|
||||
this.STEP_UPGRADE = "upgrade"
|
||||
this.STEP_FINISH = "finish"
|
||||
|
||||
this.step = this.STEP_PREPARE
|
||||
|
||||
this.doBack = function (step) {
|
||||
this.step = step
|
||||
|
||||
switch (step) {
|
||||
case this.STEP_UPGRADE:
|
||||
if (this.apiNodeChanged == 0) {
|
||||
this.doBack(this.STEP_ADMIN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备工作
|
||||
*/
|
||||
this.doPrepare = function () {
|
||||
this.step = this.STEP_DATABASE
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库
|
||||
*/
|
||||
this.databaseChanged = 1
|
||||
this.databaseTransferred = 0
|
||||
|
||||
this.doDatabase = function () {
|
||||
if (this.databaseChanged == 1 && this.databaseTransferred == 0) {
|
||||
teaweb.warn("请先将老的数据导入到新的数据库中。")
|
||||
return
|
||||
}
|
||||
this.step = this.STEP_API
|
||||
}
|
||||
|
||||
/**
|
||||
* API
|
||||
*/
|
||||
this.apiNodeChanged = 1
|
||||
this.apiNodeHost = ""
|
||||
this.apiNodePort = ""
|
||||
this.apiNodeProtocol = "http"
|
||||
this.apiNodeInstalled = 1
|
||||
|
||||
this.doAPI = function () {
|
||||
if (this.apiNodeChanged == 0) {
|
||||
this.step = this.STEP_ADMIN
|
||||
return
|
||||
}
|
||||
if (this.apiNodeInstalled == 0) {
|
||||
teaweb.warn("请先安装新的API节点")
|
||||
return
|
||||
}
|
||||
|
||||
this.$post(".validateAPI")
|
||||
.params({
|
||||
host: this.apiNodeHost,
|
||||
port: this.apiNodePort,
|
||||
protocol: this.apiNodeProtocol
|
||||
})
|
||||
.timeout(30)
|
||||
.success(function (resp) {
|
||||
if (this.apiNodeChanged == 1) {
|
||||
this.step = this.STEP_ADDRESS
|
||||
this.apiAddressHosts = resp.data.hosts
|
||||
} else {
|
||||
this.step = this.STEP_ADMIN
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改地址
|
||||
*/
|
||||
this.apiAddresses = []
|
||||
this.apiAddressHosts = []
|
||||
|
||||
this.doAddress = function () {
|
||||
this.step = this.STEP_ADMIN
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理平台
|
||||
*/
|
||||
this.adminNodeChanged = 1
|
||||
this.adminNodeInstalled = 1
|
||||
|
||||
this.doAdmin = function () {
|
||||
if (this.adminNodeChanged == 1 && this.adminNodeInstalled == 0) {
|
||||
teaweb.warn("请先安装新的管理平台")
|
||||
return
|
||||
}
|
||||
|
||||
if (this.apiNodeChanged == 0) {
|
||||
this.step = this.STEP_FINISH
|
||||
} else {
|
||||
this.step = this.STEP_UPGRADE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 边缘节点
|
||||
*/
|
||||
this.isUpgrading = false
|
||||
this.percentNodes = 0
|
||||
this.countNodes = 0
|
||||
this.countFinishedNodes = 0
|
||||
|
||||
this.doStartUpgrade = function () {
|
||||
this.percentNodes = 0
|
||||
this.countNodes = 0
|
||||
this.countFinishedNodes = 0
|
||||
|
||||
this.$post(".statNodes")
|
||||
.success(function (resp) {
|
||||
this.countNodes = resp.data.countNodes
|
||||
if (this.countNodes == 0) {
|
||||
this.isUpgrading = true
|
||||
this.percentNodes = 100
|
||||
return
|
||||
}
|
||||
|
||||
this.isUpgrading = true
|
||||
this.upgradeNodeTimer()
|
||||
})
|
||||
.fail(function () {
|
||||
|
||||
})
|
||||
.error(function () {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
this.upgradeNodeTimer = function () {
|
||||
if (!this.isUpgrading) {
|
||||
return
|
||||
}
|
||||
if (this.percentNodes == 100) {
|
||||
return
|
||||
}
|
||||
this.$post(".upgradeNodes")
|
||||
.params({
|
||||
apiNodeProtocol: this.apiNodeProtocol,
|
||||
apiNodeHost: this.apiNodeHost,
|
||||
apiNodePort: this.apiNodePort
|
||||
})
|
||||
.success(function (resp) {
|
||||
this.countFinishedNodes += resp.data.count
|
||||
if (this.countNodes > 0) {
|
||||
this.percentNodes = this.countFinishedNodes * 100 / this.countNodes
|
||||
if (this.percentNodes > 100) {
|
||||
this.percentNodes = 100
|
||||
}
|
||||
}
|
||||
|
||||
if (resp.data.hasNext) {
|
||||
this.$delay(function () {
|
||||
this.upgradeNodeTimer()
|
||||
}, 5000)
|
||||
}
|
||||
})
|
||||
.fail(function (resp) {
|
||||
this.isUpgrading = false
|
||||
|
||||
teaweb.warn(resp.message)
|
||||
})
|
||||
.error(function (err) {
|
||||
teaweb.warn("请求错误:" + err.message)
|
||||
this.isUpgrading = false
|
||||
})
|
||||
}
|
||||
|
||||
this.doUpgrade = function () {
|
||||
this.step = this.STEP_FINISH
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成
|
||||
*/
|
||||
this.doFinish = function () {
|
||||
window.location = "/"
|
||||
}
|
||||
})
|
||||
21
web/views/@default/settings/transfer/index.less
Normal file
21
web/views/@default/settings/transfer/index.less
Normal file
@@ -0,0 +1,21 @@
|
||||
.steps {
|
||||
.step.active {
|
||||
.content {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step-box {
|
||||
.button-group {
|
||||
margin-top: 1em;
|
||||
|
||||
.next {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 6em;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user