Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
package settings
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type AdvancedAction struct {
actionutils.ParentAction
}
func (this *AdvancedAction) Init() {
this.Nav("", "", "")
}
func (this *AdvancedAction) RunGet(params struct{}) {
// 跳转到高级设置的第一个Tab
this.RedirectURL("/settings/database")
}

View File

@@ -0,0 +1,48 @@
package api
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
NodeId int64
}) {
// 创建日志
defer this.CreateLogInfo(codes.APINode_LogDeleteAPINode, params.NodeId)
// 检查是否是唯一的节点
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var apiNode = nodeResp.ApiNode
if apiNode == nil {
this.Success()
return
}
if apiNode.IsOn {
countResp, err := this.RPC().APINodeRPC().CountAllEnabledAndOnAPINodes(this.AdminContext(), &pb.CountAllEnabledAndOnAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
if countResp.Count == 1 {
this.Fail("无法删除此节点必须保留至少一个可用的API节点")
}
}
_, err = this.RPC().APINodeRPC().DeleteAPINode(this.AdminContext(), &pb.DeleteAPINodeRequest{ApiNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,15 @@
package api
import (
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) {
}

View File

@@ -0,0 +1,130 @@
package api
import (
"context"
"encoding/json"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
stringutil "github.com/iwind/TeaGo/utils/string"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().APINodeRPC().CountAllEnabledAPINodes(this.AdminContext(), &pb.CountAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
var nodeMaps = []maps.Map{}
if count > 0 {
nodesResp, err := this.RPC().APINodeRPC().ListEnabledAPINodes(this.AdminContext(), &pb.ListEnabledAPINodesRequest{
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, node := range nodesResp.ApiNodes {
// 状态
var status = &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
logs.Error(err)
continue
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
}
// Rest地址
var restAccessAddrs = []string{}
if node.RestIsOn {
if len(node.RestHTTPJSON) > 0 {
httpConfig := &serverconfigs.HTTPProtocolConfig{}
err = json.Unmarshal(node.RestHTTPJSON, httpConfig)
if err != nil {
this.ErrorPage(err)
return
}
_ = httpConfig.Init()
if httpConfig.IsOn && len(httpConfig.Listen) > 0 {
for _, listen := range httpConfig.Listen {
restAccessAddrs = append(restAccessAddrs, listen.FullAddresses()...)
}
}
}
if len(node.RestHTTPSJSON) > 0 {
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
err = json.Unmarshal(node.RestHTTPSJSON, httpsConfig)
if err != nil {
this.ErrorPage(err)
return
}
_ = httpsConfig.Init(context.TODO())
if httpsConfig.IsOn && len(httpsConfig.Listen) > 0 {
restAccessAddrs = append(restAccessAddrs, httpsConfig.FullAddresses()...)
}
}
}
var shouldUpgrade = status.IsActive && len(status.BuildVersion) > 0 && stringutil.VersionCompare(teaconst.APINodeVersion, status.BuildVersion) > 0
canUpgrade, _ := apinodeutils.CanUpgrade(status.BuildVersion, status.OS, status.Arch)
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"isOn": node.IsOn,
"name": node.Name,
"accessAddrs": node.AccessAddrs,
"restAccessAddrs": restAccessAddrs,
"isPrimary": node.IsPrimary,
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"buildVersion": status.BuildVersion,
"latestVersion": teaconst.APINodeVersion,
"shouldUpgrade": shouldUpgrade,
"canUpgrade": shouldUpgrade && canUpgrade,
},
})
}
}
this.Data["nodes"] = nodeMaps
// 检查是否有调试数据
countMethodStatsResp, err := this.RPC().APIMethodStatRPC().CountAPIMethodStatsWithDay(this.AdminContext(), &pb.CountAPIMethodStatsWithDayRequest{Day: timeutil.Format("Ymd")})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["hasMethodStats"] = countMethodStatsResp.Count > 0
this.Show()
}

View File

@@ -0,0 +1,24 @@
package api
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/api/node"
"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(NewHelper()).
Helper(settingutils.NewAdvancedHelper("apiNodes")).
Prefix("/settings/api").
Get("", new(IndexAction)).
Get("/methodStats", new(MethodStatsAction)).
GetPost("/node/createPopup", new(node.CreatePopupAction)).
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,78 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package api
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"sort"
"strings"
)
type MethodStatsAction struct {
actionutils.ParentAction
}
func (this *MethodStatsAction) Init() {
this.Nav("", "", "")
}
func (this *MethodStatsAction) RunGet(params struct {
Order string
Method string
Tag string
}) {
this.Data["order"] = params.Order
this.Data["method"] = params.Method
this.Data["tag"] = params.Tag
statsResp, err := this.RPC().APIMethodStatRPC().FindAPIMethodStatsWithDay(this.AdminContext(), &pb.FindAPIMethodStatsWithDayRequest{Day: timeutil.Format("Ymd")})
if err != nil {
this.ErrorPage(err)
return
}
var pbStats = statsResp.ApiMethodStats
switch params.Order {
case "method":
sort.Slice(pbStats, func(i, j int) bool {
return pbStats[i].Method < pbStats[j].Method
})
case "costMs.desc":
sort.Slice(pbStats, func(i, j int) bool {
return pbStats[i].CostMs > pbStats[j].CostMs
})
case "peekMs.desc":
sort.Slice(pbStats, func(i, j int) bool {
return pbStats[i].PeekMs > pbStats[j].PeekMs
})
case "calls.desc":
sort.Slice(pbStats, func(i, j int) bool {
return pbStats[i].CountCalls > pbStats[j].CountCalls
})
}
var statMaps = []maps.Map{}
for _, stat := range pbStats {
if len(params.Method) > 0 && !strings.Contains(strings.ToLower(stat.Method), strings.ToLower(params.Method)) {
continue
}
if len(params.Tag) > 0 && !strings.Contains(strings.ToLower(stat.Tag), strings.ToLower(params.Tag)) {
continue
}
statMaps = append(statMaps, maps.Map{
"id": stat.Id,
"method": stat.Method,
"tag": stat.Tag,
"costMs": stat.CostMs,
"peekMs": stat.PeekMs,
"countCalls": stat.CountCalls,
})
}
this.Data["stats"] = statMaps
this.Show()
}

View File

@@ -0,0 +1,67 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"net"
"net/url"
"regexp"
"strings"
)
// CreateAddrPopupAction 添加地址
type CreateAddrPopupAction struct {
actionutils.ParentAction
}
func (this *CreateAddrPopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreateAddrPopupAction) RunGet(params struct {
}) {
this.Show()
}
func (this *CreateAddrPopupAction) RunPost(params struct {
Protocol string
Addr string
Must *actions.Must
}) {
params.Must.
Field("addr", params.Addr).
Require("请输入访问地址")
// 兼容URL
if regexp.MustCompile(`^(?i)(http|https)://`).MatchString(params.Addr) {
u, err := url.Parse(params.Addr)
if err != nil {
this.FailField("addr", "错误的访问地址不需要添加http://或https://")
}
params.Addr = u.Host
}
// 自动添加端口
if !strings.Contains(params.Addr, ":") {
switch params.Protocol {
case "http":
params.Addr += ":80"
case "https":
params.Addr += ":443"
}
}
host, port, err := net.SplitHostPort(params.Addr)
if err != nil {
this.FailField("addr", "错误的访问地址")
}
var addrConfig = &serverconfigs.NetworkAddressConfig{
Protocol: serverconfigs.Protocol(params.Protocol),
Host: host,
PortRange: port,
}
this.Data["addr"] = addrConfig
this.Success()
}

View File

@@ -0,0 +1,217 @@
package node
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "node", "create")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Description string
ListensJSON []byte
CertIdsJSON []byte
AccessAddrsJSON []byte
RestIsOn bool
RestListensJSON []byte
IsOn bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入API节点名称")
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
// 监听地址
var listens = []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal(params.ListensJSON, &listens)
if err != nil {
this.ErrorPage(err)
return
}
if len(listens) == 0 {
this.Fail("请添加至少一个进程监听地址")
}
for _, addr := range listens {
if addr.Protocol.IsHTTPFamily() {
httpConfig.IsOn = true
httpConfig.Listen = append(httpConfig.Listen, addr)
} else if addr.Protocol.IsHTTPSFamily() {
httpsConfig.IsOn = true
httpsConfig.Listen = append(httpsConfig.Listen, addr)
}
}
// Rest监听地址
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
if params.RestIsOn {
var restListens = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.RestListensJSON, &restListens)
if err != nil {
this.ErrorPage(err)
return
}
if len(restListens) == 0 {
this.Fail("请至少添加一个HTTP API监听端口")
return
}
for _, addr := range restListens {
if addr.Protocol.IsHTTPFamily() {
restHTTPConfig.IsOn = true
restHTTPConfig.Listen = append(restHTTPConfig.Listen, addr)
} else if addr.Protocol.IsHTTPSFamily() {
restHTTPSConfig.IsOn = true
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr)
}
}
// 是否有端口冲突
var rpcAddresses = []string{}
for _, listen := range listens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
rpcAddresses = append(rpcAddresses, listen.Addresses()...)
}
for _, listen := range restListens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
for _, address := range listen.Addresses() {
if lists.ContainsString(rpcAddresses, address) {
this.Fail("HTTP API地址 '" + address + "' 和 GRPC地址冲突请修改后提交")
return
}
}
}
}
// 证书
var certIds = []int64{}
if len(params.CertIdsJSON) > 0 {
err = json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil {
this.ErrorPage(err)
return
}
}
if ((httpsConfig.IsOn && len(httpsConfig.Listen) > 0) || (restHTTPSConfig.IsOn && len(httpsConfig.Listen) > 0)) && len(certIds) == 0 {
this.Fail("请添加至少一个证书")
}
var certRefs = []*sslconfigs.SSLCertRef{}
for _, certId := range certIds {
certRefs = append(certRefs, &sslconfigs.SSLCertRef{
IsOn: true,
CertId: certId,
})
}
certRefsJSON, err := json.Marshal(certRefs)
if err != nil {
this.ErrorPage(err)
return
}
// 创建策略
if len(certIds) > 0 {
sslPolicyCreateResp, err := this.RPC().SSLPolicyRPC().CreateSSLPolicy(this.AdminContext(), &pb.CreateSSLPolicyRequest{
SslCertsJSON: certRefsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
sslPolicyId := sslPolicyCreateResp.SslPolicyId
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyId,
}
restHTTPSConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyId,
}
}
// 访问地址
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.AccessAddrsJSON, &accessAddrs)
if err != nil {
this.ErrorPage(err)
return
}
if len(accessAddrs) == 0 {
this.Fail("请添加至少一个外部访问地址")
}
httpJSON, err := json.Marshal(httpConfig)
if err != nil {
this.ErrorPage(err)
return
}
httpsJSON, err := json.Marshal(httpsConfig)
if err != nil {
this.ErrorPage(err)
return
}
restHTTPJSON, err := json.Marshal(restHTTPConfig)
if err != nil {
this.ErrorPage(err)
return
}
restHTTPSJSON, err := json.Marshal(restHTTPSConfig)
if err != nil {
this.ErrorPage(err)
return
}
createResp, err := this.RPC().APINodeRPC().CreateAPINode(this.AdminContext(), &pb.CreateAPINodeRequest{
Name: params.Name,
Description: params.Description,
HttpJSON: httpJSON,
HttpsJSON: httpsJSON,
RestIsOn: params.RestIsOn,
RestHTTPJSON: restHTTPJSON,
RestHTTPSJSON: restHTTPSJSON,
AccessAddrsJSON: params.AccessAddrsJSON,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.APINode_LogCreateAPINode, createResp.ApiNodeId)
this.Success()
}

View File

@@ -0,0 +1,21 @@
package node
import (
"github.com/iwind/TeaGo/actions"
"net/http"
)
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) (goNext bool) {
if action.Request.Method != http.MethodGet {
return true
}
return true
}

View File

@@ -0,0 +1,186 @@
package node
import (
"encoding/json"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/maps"
stringutil "github.com/iwind/TeaGo/utils/string"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.ApiNode
if node == nil {
this.NotFound("apiNode", params.NodeId)
return
}
// 监听地址
var hasHTTPS = false
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
if len(node.HttpJSON) > 0 {
err = json.Unmarshal(node.HttpJSON, httpConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
if len(node.HttpsJSON) > 0 {
err = json.Unmarshal(node.HttpsJSON, httpsConfig)
if err != nil {
this.ErrorPage(err)
return
}
hasHTTPS = len(httpsConfig.Listen) > 0
}
// 监听地址
var listens = []*serverconfigs.NetworkAddressConfig{}
listens = append(listens, httpConfig.Listen...)
listens = append(listens, httpsConfig.Listen...)
// 证书信息
var certs = []*sslconfigs.SSLCertConfig{}
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId,
IgnoreData: true,
})
if err != nil {
this.ErrorPage(err)
return
}
var sslPolicyConfigJSON = sslPolicyConfigResp.SslPolicyJSON
if len(sslPolicyConfigJSON) > 0 {
var sslPolicy = &sslconfigs.SSLPolicy{}
err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy)
if err != nil {
this.ErrorPage(err)
return
}
certs = sslPolicy.Certs
}
}
// 访问地址
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(node.AccessAddrsJSON) > 0 {
err = json.Unmarshal(node.AccessAddrsJSON, &accessAddrs)
if err != nil {
this.ErrorPage(err)
return
}
}
// Rest地址
var restAccessAddrs = []*serverconfigs.NetworkAddressConfig{}
if node.RestIsOn {
if len(node.RestHTTPJSON) > 0 {
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
err = json.Unmarshal(node.RestHTTPJSON, httpConfig)
if err != nil {
this.ErrorPage(err)
return
}
if httpConfig.IsOn && len(httpConfig.Listen) > 0 {
restAccessAddrs = append(restAccessAddrs, httpConfig.Listen...)
}
}
if len(node.RestHTTPSJSON) > 0 {
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
err = json.Unmarshal(node.RestHTTPSJSON, httpsConfig)
if err != nil {
this.ErrorPage(err)
return
}
if httpsConfig.IsOn && len(httpsConfig.Listen) > 0 {
restAccessAddrs = append(restAccessAddrs, httpsConfig.Listen...)
}
if !hasHTTPS {
hasHTTPS = len(httpsConfig.Listen) > 0
}
}
}
// 状态
var status = &nodeconfigs.NodeStatus{}
var statusIsValid = false
this.Data["newVersion"] = ""
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
this.ErrorPage(err)
return
}
if status.UpdatedAt >= time.Now().Unix()-300 {
statusIsValid = true
// 是否为新版本
if stringutil.VersionCompare(status.BuildVersion, teaconst.APINodeVersion) < 0 {
this.Data["newVersion"] = teaconst.APINodeVersion
}
}
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"description": node.Description,
"isOn": node.IsOn,
"listens": listens,
"accessAddrs": accessAddrs,
"restIsOn": node.RestIsOn,
"restAccessAddrs": restAccessAddrs,
"hasHTTPS": hasHTTPS,
"certs": certs,
"isPrimary": node.IsPrimary,
"statusIsValid": statusIsValid,
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"connectionCount": status.ConnectionCount,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"load1m": numberutils.FormatFloat2(status.Load1m),
"load5m": numberutils.FormatFloat2(status.Load5m),
"load15m": numberutils.FormatFloat2(status.Load15m),
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
"exePath": status.ExePath,
},
}
this.Show()
}

View File

@@ -0,0 +1,33 @@
package node
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("apiNodes")).
Prefix("/settings/api/node").
// 这里不受Helper的约束
GetPost("/createAddrPopup", new(CreateAddrPopupAction)).
GetPost("/updateAddrPopup", new(UpdateAddrPopupAction)).
// 节点相关
Helper(NewHelper()).
Get("", new(IndexAction)).
GetPost("/update", new(UpdateAction)).
Get("/install", new(InstallAction)).
Get("/logs", new(LogsAction)).
GetPost("/upgradePopup", new(UpgradePopupAction)).
Post("/upgradeCheck", new(UpgradeCheckAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,130 @@
package node
import (
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"gopkg.in/yaml.v3"
"os"
"strings"
)
type InstallAction struct {
actionutils.ParentAction
}
func (this *InstallAction) Init() {
this.Nav("", "", "install")
}
func (this *InstallAction) RunGet(params struct {
NodeId int64
}) {
// API节点信息
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.ApiNode
if node == nil {
this.NotFound("apiNode", params.NodeId)
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"uniqueId": node.UniqueId,
"secret": node.Secret,
}
// 数据库配置
var dbConfigMap = maps.Map{
"config": "",
"error": "",
"isNotFound": false,
}
var dbConfigFile = Tea.ConfigFile("api_db.yaml")
data, err := os.ReadFile(dbConfigFile)
dbConfigMap["config"] = string(data)
var config = &configs.SimpleDBConfig{}
err = yaml.Unmarshal(data, config)
if err == nil && len(config.Host) > 0 {
var password = config.Password
if len(password) > 0 {
password = strings.Repeat("*", len(password))
}
config.Password = password
tmpConf, _ := yaml.Marshal(config)
dbConfigMap["config"] = string(tmpConf)
} else {
var config = &dbs.Config{}
err := yaml.Unmarshal(data, config)
if err != nil {
this.Data["error"] = "parse config file failed: api_db.yaml: " + err.Error()
this.Show()
return
}
if err != nil {
this.Data["error"] = "parse config file failed: api_db.yaml: " + err.Error()
this.Show()
return
}
if config.DBs == nil {
this.Data["error"] = "no database configured in config file: api_db.yaml"
this.Show()
return
}
for key, db := range config.DBs {
var dbConfig *dbs.DBConfig
dbConfig = db
if dbConfig == nil {
this.Data["error"] = "no database configured in config file: api_db.yaml"
this.Show()
return
}
var dsn = dbConfig.Dsn
cfg, err := mysql.ParseDSN(dsn)
if err != nil {
this.Data["error"] = "parse dsn error: " + err.Error()
this.Show()
return
}
var password = cfg.Passwd
if len(password) > 0 {
password = strings.Repeat("*", len(password))
}
var host = cfg.Addr
var port = "3306"
var index = strings.LastIndex(host, ":")
if index > 0 {
port = host[index+1:]
host = host[:index]
}
dbConfig.Dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&timeout=30s", cfg.User, password, host, port, cfg.DBName)
config.DBs[key] = dbConfig
//fmt.Println(host, port, cfg.User, password, cfg.DBName)
}
tmpConf, _ := yaml.Marshal(config)
dbConfigMap["config"] = string(tmpConf)
}
if err != nil {
dbConfigMap["error"] = err.Error()
dbConfigMap["isNotFound"] = os.IsNotExist(err)
}
this.Data["dbConfig"] = dbConfigMap
this.Show()
}

View File

@@ -0,0 +1,96 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type LogsAction struct {
actionutils.ParentAction
}
func (this *LogsAction) Init() {
this.Nav("", "node", "log")
this.SecondMenu("nodes")
}
func (this *LogsAction) RunGet(params struct {
NodeId int64
DayFrom string
DayTo string
Keyword string
Level string
}) {
this.Data["nodeId"] = params.NodeId
this.Data["dayFrom"] = params.DayFrom
this.Data["dayTo"] = params.DayTo
this.Data["keyword"] = params.Keyword
this.Data["level"] = params.Level
apiNodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
apiNode := apiNodeResp.ApiNode
if apiNode == nil {
this.NotFound("apiNode", params.NodeId)
return
}
this.Data["node"] = maps.Map{
"id": apiNode.Id,
"name": apiNode.Name,
}
countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
Role: nodeconfigs.NodeRoleAPI,
NodeId: params.NodeId,
DayFrom: params.DayFrom,
DayTo: params.DayTo,
Keyword: params.Keyword,
Level: params.Level,
})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count, 20)
logsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleAPI,
DayFrom: params.DayFrom,
DayTo: params.DayTo,
Keyword: params.Keyword,
Level: params.Level,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var logs = []maps.Map{}
for _, log := range logsResp.NodeLogs {
logs = append(logs, maps.Map{
"tag": log.Tag,
"description": log.Description,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
"level": log.Level,
"isToday": timeutil.FormatTime("Y-m-d", log.CreatedAt) == timeutil.Format("Y-m-d"),
})
}
this.Data["logs"] = logs
this.Data["page"] = page.AsHTML()
this.Show()
}

View File

@@ -0,0 +1,341 @@
package node
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
NodeId int64
}) {
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{
ApiNodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.ApiNode
if node == nil {
this.WriteString("要操作的节点不存在")
return
}
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
if len(node.HttpJSON) > 0 {
err = json.Unmarshal(node.HttpJSON, httpConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
if len(node.HttpsJSON) > 0 {
err = json.Unmarshal(node.HttpsJSON, httpsConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
// 监听地址
var listens = []*serverconfigs.NetworkAddressConfig{}
listens = append(listens, httpConfig.Listen...)
listens = append(listens, httpsConfig.Listen...)
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
if len(node.RestHTTPJSON) > 0 {
err = json.Unmarshal(node.RestHTTPJSON, restHTTPConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
if len(node.RestHTTPSJSON) > 0 {
err = json.Unmarshal(node.RestHTTPSJSON, restHTTPSConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
// 监听地址
var restListens = []*serverconfigs.NetworkAddressConfig{}
restListens = append(restListens, restHTTPConfig.Listen...)
restListens = append(restListens, restHTTPSConfig.Listen...)
// 证书信息
var certs = []*sslconfigs.SSLCertConfig{}
var sslPolicyId = int64(0)
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId,
IgnoreData: true,
})
if err != nil {
this.ErrorPage(err)
return
}
var sslPolicyConfigJSON = sslPolicyConfigResp.SslPolicyJSON
if len(sslPolicyConfigJSON) > 0 {
sslPolicyId = httpsConfig.SSLPolicyRef.SSLPolicyId
sslPolicy := &sslconfigs.SSLPolicy{}
err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy)
if err != nil {
this.ErrorPage(err)
return
}
certs = sslPolicy.Certs
}
}
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(node.AccessAddrsJSON) > 0 {
err = json.Unmarshal(node.AccessAddrsJSON, &accessAddrs)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"description": node.Description,
"isOn": node.IsOn,
"listens": listens,
"restIsOn": node.RestIsOn,
"restListens": restListens,
"certs": certs,
"sslPolicyId": sslPolicyId,
"accessAddrs": accessAddrs,
"isPrimary": node.IsPrimary,
}
this.Show()
}
// RunPost 保存基础设置
func (this *UpdateAction) RunPost(params struct {
NodeId int64
Name string
SslPolicyId int64
ListensJSON []byte
RestIsOn bool
RestListensJSON []byte
CertIdsJSON []byte
AccessAddrsJSON []byte
Description string
IsOn bool
IsPrimary bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入API节点名称")
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
// 监听地址
var listens = []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal(params.ListensJSON, &listens)
if err != nil {
this.ErrorPage(err)
return
}
if len(listens) == 0 {
this.Fail("请添加至少一个进程监听地址")
}
for _, addr := range listens {
if addr.Protocol.IsHTTPFamily() {
httpConfig.IsOn = true
httpConfig.Listen = append(httpConfig.Listen, addr)
} else if addr.Protocol.IsHTTPSFamily() {
httpsConfig.IsOn = true
httpsConfig.Listen = append(httpsConfig.Listen, addr)
}
}
// Rest监听地址
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
if params.RestIsOn {
var restListens = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.RestListensJSON, &restListens)
if err != nil {
this.ErrorPage(err)
return
}
if len(restListens) == 0 {
this.Fail("请至少添加一个HTTP API监听端口")
return
}
for _, addr := range restListens {
if addr.Protocol.IsHTTPFamily() {
restHTTPConfig.IsOn = true
restHTTPConfig.Listen = append(restHTTPConfig.Listen, addr)
} else if addr.Protocol.IsHTTPSFamily() {
restHTTPSConfig.IsOn = true
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr)
}
}
// 是否有端口冲突
var rpcAddresses = []string{}
for _, listen := range listens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
rpcAddresses = append(rpcAddresses, listen.Addresses()...)
}
for _, listen := range restListens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
for _, address := range listen.Addresses() {
if lists.ContainsString(rpcAddresses, address) {
this.Fail("HTTP API地址 '" + address + "' 和 GRPC地址冲突请修改后提交")
return
}
}
}
}
// 证书
var certIds = []int64{}
if len(params.CertIdsJSON) > 0 {
err = json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil {
this.ErrorPage(err)
return
}
}
if ((httpsConfig.IsOn && len(httpsConfig.Listen) > 0) || (restHTTPSConfig.IsOn && len(httpsConfig.Listen) > 0)) && len(certIds) == 0 {
this.Fail("请添加至少一个证书")
}
var certRefs = []*sslconfigs.SSLCertRef{}
for _, certId := range certIds {
certRefs = append(certRefs, &sslconfigs.SSLCertRef{
IsOn: true,
CertId: certId,
})
}
certRefsJSON, err := json.Marshal(certRefs)
if err != nil {
this.ErrorPage(err)
return
}
// 创建策略
var sslPolicyId = params.SslPolicyId
if sslPolicyId == 0 {
if len(certIds) > 0 {
sslPolicyCreateResp, err := this.RPC().SSLPolicyRPC().CreateSSLPolicy(this.AdminContext(), &pb.CreateSSLPolicyRequest{
SslCertsJSON: certRefsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
sslPolicyId = sslPolicyCreateResp.SslPolicyId
}
} else {
_, err = this.RPC().SSLPolicyRPC().UpdateSSLPolicy(this.AdminContext(), &pb.UpdateSSLPolicyRequest{
SslPolicyId: sslPolicyId,
SslCertsJSON: certRefsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
}
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyId,
}
restHTTPSConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyId,
}
// 访问地址
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.AccessAddrsJSON, &accessAddrs)
if err != nil {
this.ErrorPage(err)
return
}
if len(accessAddrs) == 0 {
this.Fail("请添加至少一个外部访问地址")
}
httpJSON, err := json.Marshal(httpConfig)
if err != nil {
this.ErrorPage(err)
return
}
httpsJSON, err := json.Marshal(httpsConfig)
if err != nil {
this.ErrorPage(err)
return
}
restHTTPJSON, err := json.Marshal(restHTTPConfig)
if err != nil {
this.ErrorPage(err)
return
}
restHTTPSJSON, err := json.Marshal(restHTTPSConfig)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().APINodeRPC().UpdateAPINode(this.AdminContext(), &pb.UpdateAPINodeRequest{
ApiNodeId: params.NodeId,
Name: params.Name,
Description: params.Description,
HttpJSON: httpJSON,
HttpsJSON: httpsJSON,
RestIsOn: params.RestIsOn,
RestHTTPJSON: restHTTPJSON,
RestHTTPSJSON: restHTTPSJSON,
AccessAddrsJSON: params.AccessAddrsJSON,
IsOn: params.IsOn,
IsPrimary: params.IsPrimary,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.APINode_LogUpdateAPINode, params.NodeId)
this.Success()
}

View File

@@ -0,0 +1,65 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"net"
"net/url"
"regexp"
"strings"
)
type UpdateAddrPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateAddrPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateAddrPopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *UpdateAddrPopupAction) RunPost(params struct {
Protocol string
Addr string
Must *actions.Must
}) {
params.Must.
Field("addr", params.Addr).
Require("请输入访问地址")
// 兼容URL
if regexp.MustCompile(`^(?i)(http|https)://`).MatchString(params.Addr) {
u, err := url.Parse(params.Addr)
if err != nil {
this.FailField("addr", "错误的访问地址不需要添加http://或https://")
}
params.Addr = u.Host
}
// 自动添加端口
if !strings.Contains(params.Addr, ":") {
switch params.Protocol {
case "http":
params.Addr += ":80"
case "https":
params.Addr += ":443"
}
}
host, port, err := net.SplitHostPort(params.Addr)
if err != nil {
this.FailField("addr", "错误的访问地址")
}
addrConfig := &serverconfigs.NetworkAddressConfig{
Protocol: serverconfigs.Protocol(params.Protocol),
Host: host,
PortRange: port,
}
this.Data["addr"] = addrConfig
this.Success()
}

View File

@@ -0,0 +1,67 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// UpgradeCheckAction 检查升级结果
type UpgradeCheckAction struct {
actionutils.ParentAction
}
func (this *UpgradeCheckAction) Init() {
this.Nav("", "", "")
}
func (this *UpgradeCheckAction) RunPost(params struct {
NodeId int64
}) {
this.Data["isOk"] = false
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
if err != nil {
this.Success()
return
}
var node = nodeResp.ApiNode
if node == nil || len(node.AccessAddrs) == 0 {
this.Success()
return
}
apiConfig, err := configs.LoadAPIConfig()
if err != nil {
this.Success()
return
}
var newAPIConfig = apiConfig.Clone()
newAPIConfig.RPCEndpoints = node.AccessAddrs
rpcClient, err := rpc.NewRPCClient(newAPIConfig, false)
if err != nil {
this.Success()
return
}
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(rpcClient.Context(0), &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
this.Success()
return
}
if versionResp.Version != teaconst.Version {
this.Success()
return
}
this.Data["isOk"] = true
this.Success()
}

View File

@@ -0,0 +1,124 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package node
import (
"encoding/json"
"errors"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"strings"
)
type UpgradePopupAction struct {
actionutils.ParentAction
}
func (this *UpgradePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpgradePopupAction) RunGet(params struct {
NodeId int64
}) {
this.Data["nodeId"] = params.NodeId
this.Data["nodeName"] = ""
this.Data["currentVersion"] = ""
this.Data["latestVersion"] = ""
this.Data["result"] = ""
this.Data["resultIsOk"] = true
this.Data["canUpgrade"] = false
this.Data["isUpgrading"] = false
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.ApiNode
if node == nil {
this.Data["result"] = "要升级的节点不存在"
this.Data["resultIsOk"] = false
this.Show()
return
}
this.Data["nodeName"] = node.Name + " / [" + strings.Join(node.AccessAddrs, ", ") + "]"
// 节点状态
var status = &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
this.ErrorPage(errors.New("decode status failed: " + err.Error()))
return
}
this.Data["currentVersion"] = status.BuildVersion
} else {
this.Data["result"] = "无法检测到节点当前版本"
this.Data["resultIsOk"] = false
this.Show()
return
}
this.Data["latestVersion"] = teaconst.APINodeVersion
if status.IsActive && len(status.BuildVersion) > 0 {
canUpgrade, reason := apinodeutils.CanUpgrade(status.BuildVersion, status.OS, status.Arch)
if !canUpgrade {
this.Data["result"] = reason
this.Data["resultIsOk"] = false
this.Show()
return
}
this.Data["canUpgrade"] = true
this.Data["result"] = "等待升级"
this.Data["resultIsOk"] = true
} else {
this.Data["result"] = "当前节点非连接状态无法远程升级"
this.Data["resultIsOk"] = false
this.Show()
return
}
// 是否正在升级
var oldUpgrader = apinodeutils.SharedManager.FindUpgrader(params.NodeId)
if oldUpgrader != nil {
this.Data["result"] = "正在升级中..."
this.Data["resultIsOk"] = false
this.Data["isUpgrading"] = true
}
this.Show()
}
func (this *UpgradePopupAction) RunPost(params struct {
NodeId int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var manager = apinodeutils.SharedManager
var oldUpgrader = manager.FindUpgrader(params.NodeId)
if oldUpgrader != nil {
this.Fail("正在升级中,无需重复提交 ...")
return
}
var upgrader = apinodeutils.NewUpgrader(params.NodeId)
manager.AddUpgrader(upgrader)
defer func() {
manager.RemoveUpgrader(upgrader)
}()
err := upgrader.Upgrade()
if err != nil {
this.Fail("升级失败:" + err.Error())
return
}
this.Success()
}

View File

@@ -0,0 +1,95 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package authority
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
plusutils "github.com/TeaOSLab/EdgePlus/pkg/utils"
"github.com/iwind/TeaGo/actions"
)
type ActivateAction struct {
actionutils.ParentAction
}
func (this *ActivateAction) Init() {
this.Nav("", "", "activate")
}
func (this *ActivateAction) RunGet(params struct{}) {
// 选中左侧菜单
this.Data["teaSubMenu"] = "authority"
this.Show()
}
func (this *ActivateAction) RunPost(params struct {
Key string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
requestCode, err := plusutils.GenerateRequestCode()
if err != nil {
this.ErrorPage(err)
return
}
if len(params.Key) == 0 {
this.FailField("key", "请输入注册码")
}
// 本地检查
key, err := plusutils.DecodeKey([]byte(params.Key))
if err != nil {
this.Fail("请输入正确的注册码")
return
}
if key.Method != plusutils.MethodRemote {
if len(key.RequestCode) > 0 { // 使用注册码激活
ok, errorCode := plusutils.ValidateRequestCode(key.RequestCode)
if !ok {
this.Fail("无法在当前服务器上使用此注册码,请联系软件开发者,错误代号:" + errorCode)
return
}
} else if len(key.MacAddresses) > 0 { // 使用MAC地址激活
for _, address := range key.MacAddresses {
if !plus.ValidateMac(address) {
this.Fail("无法在当前服务器上使用此注册码,请联系软件开发者")
return
}
}
}
}
resp, err := this.RPC().AuthorityKeyRPC().ValidateAuthorityKey(this.AdminContext(), &pb.ValidateAuthorityKeyRequest{
Key: params.Key,
RequestCode: requestCode,
})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IsOk {
_, err := this.RPC().AuthorityKeyRPC().UpdateAuthorityKey(this.AdminContext(), &pb.UpdateAuthorityKeyRequest{
Value: params.Key,
RequestCode: requestCode,
})
if err != nil {
this.ErrorPage(err)
return
}
// 显示菜单
_ = configloaders.ReloadAdminUIConfig()
this.Success()
} else {
this.FailField("key", "无法激活:"+resp.Error)
}
}

View File

@@ -0,0 +1,31 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package authority
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
plusutils "github.com/TeaOSLab/EdgePlus/pkg/utils"
)
type ApplyAction struct {
actionutils.ParentAction
}
func (this *ApplyAction) Init() {
this.Nav("", "", "apply")
}
func (this *ApplyAction) RunGet(params struct{}) {
// 选中左侧菜单
this.Data["teaSubMenu"] = "authority"
requestCode, err := plusutils.GenerateRequestCode()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["requestCode"] = requestCode
this.Show()
}

View File

@@ -0,0 +1,118 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package authority
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
plusutils "github.com/TeaOSLab/EdgePlus/pkg/utils"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("authority", "", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
// 选中左侧菜单
this.Data["teaSubMenu"] = "authority"
this.Data["key"] = nil
this.Data["plusErr"] = ""
keyResp, err := this.RPC().AuthorityKeyRPC().ReadAuthorityKey(this.AdminContext(), &pb.ReadAuthorityKeyRequest{})
if err != nil {
this.Data["plusErr"] = err.Error()
this.Show()
return
}
var keyMap maps.Map = nil
teaconst.IsPlus = false
plus.UpdateComponents("", []string{})
plus.ErrString = ""
var key = keyResp.AuthorityKey
if key != nil {
if len(key.MacAddresses) == 0 {
key.MacAddresses = []string{}
}
if len(key.Components) == 0 {
key.Components = []string{}
}
var isActive = len(key.DayTo) > 0 && key.DayTo >= timeutil.Format("Y-m-d")
var method = ""
if isActive {
nativeKey, err := plusutils.DecodeKey([]byte(key.Value))
var isOk = true
if err != nil {
teaconst.IsPlus = false
} else if len(nativeKey.RequestCode) > 0 { // 使用申请码
method = nativeKey.Method
isOk, _ = plusutils.ValidateRequestCode(nativeKey.RequestCode)
} else if len(nativeKey.MacAddresses) > 0 { // MAC地址
for _, address := range nativeKey.MacAddresses {
if !plus.ValidateMac(address) {
isOk = false
break
}
}
}
if isOk {
teaconst.IsPlus = true
plus.UpdateComponents(key.Edition, key.Components)
} else {
teaconst.IsPlus = false
plus.UpdateComponents("", []string{})
plus.ErrString = "当前管理平台所在服务器环境与商业版认证分发时发生了变化,因此无法使用商业版功能。如果你正在迁移管理平台,请联系开发者重新申请新的注册码。"
}
}
var isExpiring = isActive && key.DayTo < timeutil.Format("Y-m-d", time.Now().AddDate(0, 0, 7))
// 终身授权
var dayTo = key.DayTo
if dayTo >= "2050-01-01" {
dayTo = "终身授权"
}
keyMap = maps.Map{
"dayFrom": key.DayFrom,
"dayTo": dayTo,
"hostname": key.Hostname,
"company": key.Company,
"nodes": key.Nodes,
"components": key.Components,
"editionName": plusutils.EditionName(key.Edition),
"isExpired": !isActive,
"isExpiring": isExpiring,
"updatedTime": timeutil.FormatTime("Y-m-d H:i:s", keyResp.AuthorityKey.UpdatedAt),
"method": method,
}
}
this.Data["key"] = keyMap
this.Data["plusErr"] = plus.ErrString
// 配额
quotaResp, err := this.RPC().AuthorityKeyRPC().FindAuthorityQuota(this.AdminContext(), &pb.FindAuthorityQuotaRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["quota"] = maps.Map{
"maxNodes": quotaResp.MaxNodes,
"countNodes": quotaResp.CountNodes,
}
this.Show()
}

View File

@@ -0,0 +1,23 @@
//go:build plus
package authority
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("authority")).
Prefix("/settings/authority").
Get("", new(IndexAction)).
Get("/apply", new(ApplyAction)).
GetPost("/activate", new(ActivateAction)).
EndAll()
})
}

View File

@@ -0,0 +1,15 @@
package backup
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
this.Show()
}

View File

@@ -0,0 +1,19 @@
package backup
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("backup")).
Prefix("/settings/backup").
Get("", new(IndexAction)).
EndAll()
})
}

View File

@@ -0,0 +1,91 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package browser
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
DataId string
}) {
browserResp, err := this.RPC().FormalClientBrowserRPC().FindFormalClientBrowserWithDataId(this.AdminContext(), &pb.FindFormalClientBrowserWithDataIdRequest{DataId: params.DataId})
if err != nil {
this.ErrorPage(err)
return
}
var browser = browserResp.FormalClientBrowser
if browser == nil {
this.NotFound("formalClientBrowser", 0)
return
}
if browser.Codes == nil {
browser.Codes = []string{}
}
this.Data["browser"] = maps.Map{
"id": browser.Id,
"name": browser.Name,
"codes": browser.Codes,
"dataId": browser.DataId,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
BrowserId int64
Name string
Codes []string
DataId string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ClientBrowser_LogUpdateClientBrowser, params.BrowserId)
params.Must.
Field("name", params.Name).
Require("请输入浏览器名称").
Field("dataId", params.DataId).
Require("请输入数据ID")
if len(params.Codes) == 0 {
params.Codes = []string{params.Name}
}
// 检查dataId
browserResp, err := this.RPC().FormalClientBrowserRPC().FindFormalClientBrowserWithDataId(this.AdminContext(), &pb.FindFormalClientBrowserWithDataIdRequest{DataId: params.DataId})
if err != nil {
this.ErrorPage(err)
return
}
if browserResp.FormalClientBrowser != nil && browserResp.FormalClientBrowser.Id != params.BrowserId {
this.Fail("该数据ID已经被 '" + browserResp.FormalClientBrowser.Name + "'所使用,请换一个")
return
}
_, err = this.RPC().FormalClientBrowserRPC().UpdateFormalClientBrowser(this.AdminContext(), &pb.UpdateFormalClientBrowserRequest{
FormalClientBrowserId: params.BrowserId,
Name: params.Name,
Codes: params.Codes,
DataId: params.DataId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,66 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package clientbrowsers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Codes []string
DataId string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ClientBrowser_LogCreateBrowser, params.Name)
params.Must.
Field("name", params.Name).
Require("请输入浏览器名称").
Field("dataId", params.DataId).
Require("请输入数据ID")
if len(params.Codes) == 0 {
params.Codes = []string{params.Name}
}
// 检查dataId
browserResp, err := this.RPC().FormalClientBrowserRPC().FindFormalClientBrowserWithDataId(this.AdminContext(), &pb.FindFormalClientBrowserWithDataIdRequest{DataId: params.DataId})
if err != nil {
this.ErrorPage(err)
return
}
if browserResp.FormalClientBrowser != nil {
this.Fail("该数据ID已经被 '" + browserResp.FormalClientBrowser.Name + "'所使用,请换一个")
return
}
_, err = this.RPC().FormalClientBrowserRPC().CreateFormalClientBrowser(this.AdminContext(), &pb.CreateFormalClientBrowserRequest{
Name: params.Name,
Codes: params.Codes,
DataId: params.DataId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,59 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package clientbrowsers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
Keyword string
}) {
this.Data["keyword"] = params.Keyword
countResp, err := this.RPC().FormalClientBrowserRPC().CountFormalClientBrowsers(this.AdminContext(), &pb.CountFormalClientBrowsersRequest{
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
browsersResp, err := this.RPC().FormalClientBrowserRPC().ListFormalClientBrowsers(this.AdminContext(), &pb.ListFormalClientBrowsersRequest{
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var browserMaps = []maps.Map{}
for _, browser := range browsersResp.FormalClientBrowsers {
var codes = browser.Codes
if codes == nil {
codes = []string{}
}
browserMaps = append(browserMaps, maps.Map{
"id": browser.Id,
"name": browser.Name,
"codes": codes,
"dataId": browser.DataId,
})
}
this.Data["browsers"] = browserMaps
this.Show()
}

View File

@@ -0,0 +1,33 @@
//go:build plus
package clientbrowsers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/client-browsers/browser"
"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(plus.NewBasicHelper()).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(settingutils.NewAdvancedHelper("clientBrowser")).
//
Prefix("/settings/client-browsers").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
//
Prefix("/settings/client-browsers/browser").
GetPost("/updatePopup", new(browser.UpdatePopupAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,66 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package clientsystems
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Codes []string
DataId string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ClientSystem_LogCreateSystem, params.Name)
params.Must.
Field("name", params.Name).
Require("请输入操作系统名称").
Field("dataId", params.DataId).
Require("请输入数据ID")
if len(params.Codes) == 0 {
params.Codes = []string{params.Name}
}
// 检查dataId
systemResp, err := this.RPC().FormalClientSystemRPC().FindFormalClientSystemWithDataId(this.AdminContext(), &pb.FindFormalClientSystemWithDataIdRequest{DataId: params.DataId})
if err != nil {
this.ErrorPage(err)
return
}
if systemResp.FormalClientSystem != nil {
this.Fail("该数据ID已经被 '" + systemResp.FormalClientSystem.Name + "'所使用,请换一个")
return
}
_, err = this.RPC().FormalClientSystemRPC().CreateFormalClientSystem(this.AdminContext(), &pb.CreateFormalClientSystemRequest{
Name: params.Name,
Codes: params.Codes,
DataId: params.DataId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,59 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package clientsystems
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
Keyword string
}) {
this.Data["keyword"] = params.Keyword
countResp, err := this.RPC().FormalClientSystemRPC().CountFormalClientSystems(this.AdminContext(), &pb.CountFormalClientSystemsRequest{
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
systemsResp, err := this.RPC().FormalClientSystemRPC().ListFormalClientSystems(this.AdminContext(), &pb.ListFormalClientSystemsRequest{
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var systemMaps = []maps.Map{}
for _, system := range systemsResp.FormalClientSystems {
var codes = system.Codes
if codes == nil {
codes = []string{}
}
systemMaps = append(systemMaps, maps.Map{
"id": system.Id,
"name": system.Name,
"codes": codes,
"dataId": system.DataId,
})
}
this.Data["systems"] = systemMaps
this.Show()
}

View File

@@ -0,0 +1,33 @@
//go:build plus
package clientsystems
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/client-systems/system"
"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(plus.NewBasicHelper()).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(settingutils.NewAdvancedHelper("clientSystem")).
//
Prefix("/settings/client-systems").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
//
Prefix("/settings/client-systems/system").
GetPost("/updatePopup", new(system.UpdatePopupAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,91 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package system
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
DataId string
}) {
systemResp, err := this.RPC().FormalClientSystemRPC().FindFormalClientSystemWithDataId(this.AdminContext(), &pb.FindFormalClientSystemWithDataIdRequest{DataId: params.DataId})
if err != nil {
this.ErrorPage(err)
return
}
var system = systemResp.FormalClientSystem
if system == nil {
this.NotFound("formalClientSystem", 0)
return
}
if system.Codes == nil {
system.Codes = []string{}
}
this.Data["system"] = maps.Map{
"id": system.Id,
"name": system.Name,
"codes": system.Codes,
"dataId": system.DataId,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
SystemId int64
Name string
Codes []string
DataId string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ClientSystem_LogUpdateClientSystem, params.SystemId)
params.Must.
Field("name", params.Name).
Require("请输入操作系统名称").
Field("dataId", params.DataId).
Require("请输入数据ID")
if len(params.Codes) == 0 {
params.Codes = []string{params.Name}
}
// 检查dataId
systemResp, err := this.RPC().FormalClientSystemRPC().FindFormalClientSystemWithDataId(this.AdminContext(), &pb.FindFormalClientSystemWithDataIdRequest{DataId: params.DataId})
if err != nil {
this.ErrorPage(err)
return
}
if systemResp.FormalClientSystem != nil && systemResp.FormalClientSystem.Id != params.SystemId {
this.Fail("该数据ID已经被 '" + systemResp.FormalClientSystem.Name + "'所使用,请换一个")
return
}
_, err = this.RPC().FormalClientSystemRPC().UpdateFormalClientSystem(this.AdminContext(), &pb.UpdateFormalClientSystemRequest{
FormalClientSystemId: params.SystemId,
Name: params.Name,
Codes: params.Codes,
DataId: params.DataId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,82 @@
package database
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"sort"
)
type CleanAction struct {
actionutils.ParentAction
}
func (this *CleanAction) Init() {
this.Nav("", "", "clean")
}
func (this *CleanAction) RunGet(params struct {
OrderTable string
OrderSize string
}) {
this.Data["orderTable"] = params.OrderTable
this.Data["orderSize"] = params.OrderSize
this.Show()
}
func (this *CleanAction) RunPost(params struct {
OrderTable string
OrderSize string
Must *actions.Must
}) {
tablesResp, err := this.RPC().DBRPC().FindAllDBTables(this.AdminContext(), &pb.FindAllDBTablesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var tables = tablesResp.DbTables
// 排序
switch params.OrderTable {
case "asc":
sort.Slice(tables, func(i, j int) bool {
return tables[i].Name < tables[j].Name
})
case "desc":
sort.Slice(tables, func(i, j int) bool {
return tables[i].Name > tables[j].Name
})
}
switch params.OrderSize {
case "asc":
sort.Slice(tables, func(i, j int) bool {
return tables[i].DataLength+tables[i].IndexLength < tables[j].DataLength+tables[j].IndexLength
})
case "desc":
sort.Slice(tables, func(i, j int) bool {
return tables[i].DataLength+tables[i].IndexLength > tables[j].DataLength+tables[j].IndexLength
})
}
var tableMaps = []maps.Map{}
for _, table := range tables {
if !table.IsBaseTable || (!table.CanClean && !table.CanDelete) {
continue
}
tableMaps = append(tableMaps, maps.Map{
"name": table.Name,
"rows": table.Rows,
"size": numberutils.FormatBytes(table.DataLength + table.IndexLength),
"canDelete": table.CanDelete,
"canClean": table.CanClean,
"comment": table.Comment,
})
}
this.Data["tables"] = tableMaps
this.Success()
}

View File

@@ -0,0 +1,149 @@
package database
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/actions"
)
type CleanSettingAction struct {
actionutils.ParentAction
}
func (this *CleanSettingAction) Init() {
this.Nav("", "", "cleanSetting")
}
func (this *CleanSettingAction) RunGet(params struct{}) {
// 读取设置
configResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeDatabaseConfigSetting})
if err != nil {
this.ErrorPage(err)
return
}
var config = systemconfigs.NewDatabaseConfig()
if len(configResp.ValueJSON) > 0 {
err = json.Unmarshal(configResp.ValueJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["config"] = config
this.Show()
}
func (this *CleanSettingAction) RunPost(params struct {
ServerAccessLogCleanDays int
ServerBandwidthStatCleanDays int
UserBandwidthStatCleanDays int
UserPlanBandwidthStatCleanDays int
ServerDailyStatCleanDays int
ServerDomainHourlyStatCleanDays int
TrafficDailyStatCleanDays int
TrafficHourlyStatCleanDays int
NodeClusterTrafficDailyStatCleanDays int
NodeTrafficDailyStatCleanDays int
NodeTrafficHourlyStatCleanDays int
HttpCacheTaskCleanDays int
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.Database_LogUpdateCleanDays)
// 读取设置
configResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeDatabaseConfigSetting})
if err != nil {
this.ErrorPage(err)
return
}
var config = systemconfigs.NewDatabaseConfig()
if len(configResp.ValueJSON) > 0 {
err = json.Unmarshal(configResp.ValueJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
}
if params.ServerAccessLogCleanDays < 0 {
params.ServerAccessLogCleanDays = 0
}
config.ServerAccessLog.Clean.Days = params.ServerAccessLogCleanDays
if params.ServerBandwidthStatCleanDays < 0 {
params.ServerBandwidthStatCleanDays = 0
}
config.ServerBandwidthStat.Clean.Days = params.ServerBandwidthStatCleanDays
if params.UserBandwidthStatCleanDays < 0 {
params.UserBandwidthStatCleanDays = 0
}
config.UserBandwidthStat.Clean.Days = params.UserBandwidthStatCleanDays
if params.UserPlanBandwidthStatCleanDays < 0 {
params.UserPlanBandwidthStatCleanDays = 0
}
config.UserPlanBandwidthStat.Clean.Days = params.UserPlanBandwidthStatCleanDays
if params.ServerDailyStatCleanDays < 0 {
params.ServerDailyStatCleanDays = 0
}
config.ServerDailyStat.Clean.Days = params.ServerDailyStatCleanDays
if params.ServerDomainHourlyStatCleanDays < 0 {
params.ServerDomainHourlyStatCleanDays = 0
}
config.ServerDomainHourlyStat.Clean.Days = params.ServerDomainHourlyStatCleanDays
if params.TrafficDailyStatCleanDays < 0 {
params.TrafficDailyStatCleanDays = 0
}
config.TrafficDailyStat.Clean.Days = params.TrafficDailyStatCleanDays
if params.TrafficHourlyStatCleanDays < 0 {
params.TrafficHourlyStatCleanDays = 0
}
config.TrafficHourlyStat.Clean.Days = params.TrafficHourlyStatCleanDays
if params.NodeClusterTrafficDailyStatCleanDays < 0 {
params.NodeClusterTrafficDailyStatCleanDays = 0
}
config.NodeClusterTrafficDailyStat.Clean.Days = params.NodeClusterTrafficDailyStatCleanDays
if params.NodeTrafficDailyStatCleanDays < 0 {
params.NodeTrafficDailyStatCleanDays = 0
}
config.NodeTrafficDailyStat.Clean.Days = params.NodeTrafficDailyStatCleanDays
if params.NodeTrafficHourlyStatCleanDays < 0 {
params.NodeTrafficHourlyStatCleanDays = 0
}
config.NodeTrafficHourlyStat.Clean.Days = params.NodeTrafficHourlyStatCleanDays
if params.HttpCacheTaskCleanDays < 0 {
params.HttpCacheTaskCleanDays = 0
}
config.HTTPCacheTask.Clean.Days = params.HttpCacheTaskCleanDays
configJSON, err := json.Marshal(config)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
Code: systemconfigs.SettingCodeDatabaseConfigSetting,
ValueJSON: configJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,24 @@
package database
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteTableAction struct {
actionutils.ParentAction
}
func (this *DeleteTableAction) RunPost(params struct {
Table string
}) {
defer this.CreateLogInfo(codes.Database_LogDeleteTable, params.Table)
_, err := this.RPC().DBRPC().DeleteDBTable(this.AdminContext(), &pb.DeleteDBTableRequest{DbTable: params.Table})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,119 @@
package database
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"gopkg.in/yaml.v3"
"net"
"os"
"strings"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
this.Data["error"] = ""
var configFile = Tea.ConfigFile("api_db.yaml")
data, err := os.ReadFile(configFile)
if err != nil {
this.Data["error"] = "read config file failed: api_db.yaml: " + err.Error()
this.Show()
return
}
// new config
var config = &configs.SimpleDBConfig{}
err = yaml.Unmarshal(data, config)
if err == nil && len(config.Host) > 0 {
host, port, splitErr := net.SplitHostPort(config.Host)
if splitErr != nil {
port = "3306"
}
var password = config.Password
if len(password) > 0 {
password = strings.Repeat("*", len(password))
}
this.Data["dbConfig"] = maps.Map{
"host": host,
"port": port,
"username": config.User,
"password": password,
"database": config.Database,
}
this.Show()
return
}
this.parseOldConfig(data)
}
func (this *IndexAction) parseOldConfig(data []byte) {
var config = &dbs.Config{}
err := yaml.Unmarshal(data, config)
if err != nil {
this.Data["error"] = "parse config file failed: api_db.yaml: " + err.Error()
this.Show()
return
}
if config.DBs == nil {
this.Data["error"] = "no database configured in config file: api_db.yaml"
this.Show()
return
}
var dbConfig *dbs.DBConfig
for _, db := range config.DBs {
dbConfig = db
break
}
if dbConfig == nil {
this.Data["error"] = "no database configured in config file: api_db.yaml"
this.Show()
return
}
var dsn = dbConfig.Dsn
cfg, err := mysql.ParseDSN(dsn)
if err != nil {
this.Data["error"] = "parse dsn error: " + err.Error()
this.Show()
return
}
var host = cfg.Addr
var port = "3306"
var index = strings.LastIndex(host, ":")
if index > 0 {
port = host[index+1:]
host = host[:index]
}
var password = cfg.Passwd
if len(password) > 0 {
password = strings.Repeat("*", len(password))
}
this.Data["dbConfig"] = maps.Map{
"host": host,
"port": port,
"username": cfg.User,
"password": password,
"database": cfg.DBName,
}
// TODO 测试连接
this.Show()
}

View File

@@ -0,0 +1,24 @@
package database
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("database")).
Prefix("/settings/database").
Get("", new(IndexAction)).
GetPost("/update", new(UpdateAction)).
GetPost("/clean", new(CleanAction)).
GetPost("/cleanSetting", new(CleanSettingAction)).
GetPost("/truncateTable", new(TruncateTableAction)).
GetPost("/deleteTable", new(DeleteTableAction)).
EndAll()
})
}

View File

@@ -0,0 +1,24 @@
package database
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type TruncateTableAction struct {
actionutils.ParentAction
}
func (this *TruncateTableAction) RunPost(params struct {
Table string
}) {
defer this.CreateLogInfo(codes.Database_LogTruncateTable, params.Table)
_, err := this.RPC().DBRPC().TruncateDBTable(this.AdminContext(), &pb.TruncateDBTableRequest{DbTable: params.Table})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,193 @@
package database
import (
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"gopkg.in/yaml.v3"
"net"
"os"
"regexp"
"strings"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct{}) {
this.Data["dbConfig"] = maps.Map{
"host": "",
"port": "",
"username": "",
"password": "",
"database": "",
}
configFile := Tea.ConfigFile("api_db.yaml")
data, err := os.ReadFile(configFile)
if err != nil {
return
}
// new config
var config = &configs.SimpleDBConfig{}
err = yaml.Unmarshal(data, config)
if err == nil && len(config.Host) > 0 {
host, port, splitErr := net.SplitHostPort(config.Host)
if splitErr != nil {
port = "3306"
}
this.Data["dbConfig"] = maps.Map{
"host": host,
"port": port,
"username": config.User,
"password": config.Password,
"database": config.Database,
}
this.Show()
return
}
this.parseOldConfig(data)
}
func (this *UpdateAction) RunPost(params struct {
Host string
Port int32
Database string
Username string
Password string
Must *actions.Must
}) {
defer this.CreateLogInfo(codes.Database_LogUpdateAPINodeDatabaseConfig)
params.Must.
Field("host", params.Host).
Require("请输入主机地址").
Expect(func() (message string, success bool) {
// 是否为IP
if net.ParseIP(params.Host) != nil {
success = true
return
}
if !regexp.MustCompile(`^[\w.-]+$`).MatchString(params.Host) {
message = "主机地址中不能包含特殊字符"
success = false
return
}
success = true
return
}).
Field("port", params.Port).
Gt(0, "端口需要大于0").
Lt(65535, "端口需要小于65535").
Field("database", params.Database).
Require("请输入数据库名称").
Match(`^[\w\.-]+$`, "数据库名称中不能包含特殊字符").
Field("username", params.Username).
Require("请输入连接数据库的用户名").
Match(`^[\w\.-]+$`, "用户名中不能包含特殊字符")
var config = &configs.SimpleDBConfig{
User: params.Username,
Password: params.Password,
Database: params.Database,
Host: configutils.QuoteIP(params.Host) + ":" + fmt.Sprintf("%d", params.Port),
}
configYAML, err := yaml.Marshal(config)
if err != nil {
this.ErrorPage(err)
return
}
// 保存
var configFile = Tea.ConfigFile("api_db.yaml")
err = os.WriteFile(configFile, configYAML, 0666)
if err != nil {
this.Fail("保存配置失败:" + err.Error())
return
}
// TODO 思考是否让本地的API节点生效
this.Success()
}
func (this *UpdateAction) parseOldConfig(data []byte) {
var config = &dbs.Config{}
err := yaml.Unmarshal(data, config)
if err != nil {
this.Show()
return
}
if config.DBs == nil {
this.Show()
return
}
var dbConfig *dbs.DBConfig
for _, db := range config.DBs {
dbConfig = db
break
}
if dbConfig == nil {
this.Data["dbConfig"] = maps.Map{
"host": "",
"port": "",
"username": "",
"password": "",
"database": "",
}
this.Show()
return
}
var dsn = dbConfig.Dsn
cfg, err := mysql.ParseDSN(dsn)
if err != nil {
this.Data["dbConfig"] = maps.Map{
"host": "",
"port": "",
"username": "",
"password": "",
"database": "",
}
this.Show()
return
}
var host = cfg.Addr
var port = "3306"
var index = strings.LastIndex(cfg.Addr, ":")
if index > 0 {
host = cfg.Addr[:index]
port = cfg.Addr[index+1:]
}
this.Data["dbConfig"] = maps.Map{
"host": host,
"port": port,
"username": cfg.User,
"password": cfg.Passwd,
"database": cfg.DBName,
}
this.Show()
}

View File

@@ -0,0 +1,14 @@
package settings
import "github.com/iwind/TeaGo/actions"
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) {
action.Data["teaMenu"] = "settings"
}

View File

@@ -0,0 +1,17 @@
package settings
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
this.RedirectURL("/settings/server")
}

View File

@@ -0,0 +1,19 @@
package settings
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"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(NewHelper()).
Prefix("/settings").
Get("", new(IndexAction)).
Get("/advanced", new(AdvancedAction)).
EndAll()
})
}

View File

@@ -0,0 +1,81 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package cities
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/iplibraryutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "city")
}
func (this *IndexAction) RunGet(params struct {
CountryId int64
ProvinceId int64
}) {
// 检查权限
if !iplibraryutils.CanAccess() {
return
}
this.Data["canAccess"] = true
if params.CountryId <= 0 || params.ProvinceId <= 0 {
params.CountryId = 1 // china
params.ProvinceId = 1 // beijing
}
this.Data["countryId"] = params.CountryId
this.Data["provinceId"] = params.ProvinceId
// 所有国家/地区
countriesResp, err := this.RPC().RegionCountryRPC().FindAllRegionCountries(this.AdminContext(), &pb.FindAllRegionCountriesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var countryMaps = []maps.Map{}
for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{
"id": country.Id,
"name": country.DisplayName,
})
}
this.Data["countries"] = countryMaps
// 城市列表
citiesResp, err := this.RPC().RegionCityRPC().FindAllRegionCitiesWithRegionProvinceId(this.AdminContext(), &pb.FindAllRegionCitiesWithRegionProvinceIdRequest{
RegionProvinceId: params.ProvinceId,
})
if err != nil {
this.ErrorPage(err)
return
}
var cityMaps = []maps.Map{}
for _, city := range citiesResp.RegionCities {
if city.Codes == nil {
city.Codes = []string{}
}
if city.CustomCodes == nil {
city.CustomCodes = []string{}
}
cityMaps = append(cityMaps, maps.Map{
"id": city.Id,
"name": city.Name,
"codes": city.Codes,
"customName": city.CustomName,
"customCodes": city.CustomCodes,
})
}
this.Data["cities"] = cityMaps
this.Show()
}

View File

@@ -0,0 +1,36 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package cities
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type ProvinceOptionsAction struct {
actionutils.ParentAction
}
func (this *ProvinceOptionsAction) RunPost(params struct {
CountryId int64
}) {
provincesResp, err := this.RPC().RegionProvinceRPC().FindAllRegionProvincesWithRegionCountryId(this.AdminContext(), &pb.FindAllRegionProvincesWithRegionCountryIdRequest{
RegionCountryId: params.CountryId,
})
if err != nil {
this.ErrorPage(err)
return
}
var provinceMaps = []maps.Map{}
for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id,
"name": province.DisplayName,
})
}
this.Data["provinces"] = provinceMaps
this.Success()
}

View File

@@ -0,0 +1,72 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package cities
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
CityId int64
}) {
cityResp, err := this.RPC().RegionCityRPC().FindRegionCity(this.AdminContext(), &pb.FindRegionCityRequest{RegionCityId: params.CityId})
if err != nil {
this.ErrorPage(err)
return
}
var city = cityResp.RegionCity
if city == nil {
this.NotFound("regionCity", params.CityId)
return
}
if city.Codes == nil {
city.Codes = []string{}
}
if city.CustomCodes == nil {
city.CustomCodes = []string{}
}
this.Data["city"] = maps.Map{
"id": city.Id,
"name": city.Name,
"codes": city.Codes,
"customName": city.CustomName,
"customCodes": city.CustomCodes,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
CityId int64
CustomName string
CustomCodes []string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.RegionCity_LogUpdateRegionCityCustom, params.CityId)
_, err := this.RPC().RegionCityRPC().UpdateRegionCityCustom(this.AdminContext(), &pb.UpdateRegionCityCustomRequest{
RegionCityId: params.CityId,
CustomName: params.CustomName,
CustomCodes: params.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package countries
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/iplibraryutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "country")
}
func (this *IndexAction) RunGet(params struct{}) {
// 检查权限
if !iplibraryutils.CanAccess() {
return
}
this.Data["canAccess"] = true
countriesResp, err := this.RPC().RegionCountryRPC().FindAllRegionCountries(this.AdminContext(), &pb.FindAllRegionCountriesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var countryMaps = []maps.Map{}
for _, country := range countriesResp.RegionCountries {
if country.Codes == nil {
country.Codes = []string{}
}
if country.CustomCodes == nil {
country.CustomCodes = []string{}
}
countryMaps = append(countryMaps, maps.Map{
"id": country.Id,
"name": country.Name,
"codes": country.Codes,
"customName": country.CustomName,
"customCodes": country.CustomCodes,
})
}
this.Data["countries"] = countryMaps
this.Show()
}

View File

@@ -0,0 +1,74 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package countries
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
CountryId int64
}) {
countryResp, err := this.RPC().RegionCountryRPC().FindRegionCountry(this.AdminContext(), &pb.FindRegionCountryRequest{
RegionCountryId: params.CountryId,
})
if err != nil {
this.ErrorPage(err)
return
}
var country = countryResp.RegionCountry
if country == nil {
this.NotFound("regionCountry", params.CountryId)
return
}
if country.Codes == nil {
country.Codes = []string{}
}
if country.CustomCodes == nil {
country.CustomCodes = []string{}
}
this.Data["country"] = maps.Map{
"id": country.Id,
"name": country.Name,
"codes": country.Codes,
"customName": country.CustomName,
"customCodes": country.CustomCodes,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
CountryId int64
CustomName string
CustomCodes []string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.RegionCountry_LogUpdateRegionCountryCustom, params.CountryId)
_, err := this.RPC().RegionCountryRPC().UpdateRegionCountryCustom(this.AdminContext(), &pb.UpdateRegionCountryCustomRequest{
RegionCountryId: params.CountryId,
CustomName: params.CustomName,
CustomCodes: params.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,48 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
)
type AddCityCustomCodeAction struct {
actionutils.ParentAction
}
func (this *AddCityCustomCodeAction) RunPost(params struct {
CityId int64
Code string
}) {
defer this.CreateLogInfo(codes.RegionCity_LogAddRegionCityCode, params.CityId, params.Code)
cityResp, err := this.RPC().RegionCityRPC().FindRegionCity(this.AdminContext(), &pb.FindRegionCityRequest{RegionCityId: params.CityId})
if err != nil {
this.ErrorPage(err)
return
}
var city = cityResp.RegionCity
if city == nil {
this.NotFound("regionCity", params.CityId)
return
}
if len(params.Code) > 0 && !lists.ContainsString(city.CustomCodes, params.Code) {
city.CustomCodes = append(city.CustomCodes, params.Code)
_, err = this.RPC().RegionCityRPC().UpdateRegionCityCustom(this.AdminContext(), &pb.UpdateRegionCityCustomRequest{
RegionCityId: params.CityId,
CustomName: city.CustomName,
CustomCodes: city.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Success()
}

View File

@@ -0,0 +1,48 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
)
type AddCountryCustomCodeAction struct {
actionutils.ParentAction
}
func (this *AddCountryCustomCodeAction) RunPost(params struct {
CountryId int64
Code string
}) {
defer this.CreateLogInfo(codes.RegionCountry_LogAddRegionCountryCode, params.CountryId, params.Code)
countryResp, err := this.RPC().RegionCountryRPC().FindRegionCountry(this.AdminContext(), &pb.FindRegionCountryRequest{RegionCountryId: params.CountryId})
if err != nil {
this.ErrorPage(err)
return
}
var country = countryResp.RegionCountry
if country == nil {
this.NotFound("regionCountry", params.CountryId)
return
}
if len(params.Code) > 0 && !lists.ContainsString(country.CustomCodes, params.Code) {
country.CustomCodes = append(country.CustomCodes, params.Code)
_, err = this.RPC().RegionCountryRPC().UpdateRegionCountryCustom(this.AdminContext(), &pb.UpdateRegionCountryCustomRequest{
RegionCountryId: params.CountryId,
CustomName: country.CustomName,
CustomCodes: country.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Success()
}

View File

@@ -0,0 +1,48 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
)
type AddProviderCustomCodeAction struct {
actionutils.ParentAction
}
func (this *AddProviderCustomCodeAction) RunPost(params struct {
ProviderId int64
Code string
}) {
defer this.CreateLogInfo(codes.RegionProvider_LogAddRegionProviderCode, params.ProviderId, params.Code)
providerResp, err := this.RPC().RegionProviderRPC().FindRegionProvider(this.AdminContext(), &pb.FindRegionProviderRequest{RegionProviderId: params.ProviderId})
if err != nil {
this.ErrorPage(err)
return
}
var provider = providerResp.RegionProvider
if provider == nil {
this.NotFound("regionProvider", params.ProviderId)
return
}
if len(params.Code) > 0 && !lists.ContainsString(provider.CustomCodes, params.Code) {
provider.CustomCodes = append(provider.CustomCodes, params.Code)
_, err = this.RPC().RegionProviderRPC().UpdateRegionProviderCustom(this.AdminContext(), &pb.UpdateRegionProviderCustomRequest{
RegionProviderId: params.ProviderId,
CustomName: provider.CustomName,
CustomCodes: provider.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Success()
}

View File

@@ -0,0 +1,48 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
)
type AddProvinceCustomCodeAction struct {
actionutils.ParentAction
}
func (this *AddProvinceCustomCodeAction) RunPost(params struct {
ProvinceId int64
Code string
}) {
defer this.CreateLogInfo(codes.RegionProvince_LogAddRegionProvinceCode, params.ProvinceId, params.Code)
provinceResp, err := this.RPC().RegionProvinceRPC().FindRegionProvince(this.AdminContext(), &pb.FindRegionProvinceRequest{RegionProvinceId: params.ProvinceId})
if err != nil {
this.ErrorPage(err)
return
}
var province = provinceResp.RegionProvince
if province == nil {
this.NotFound("regionProvince", params.ProvinceId)
return
}
if len(params.Code) > 0 && !lists.ContainsString(province.CustomCodes, params.Code) {
province.CustomCodes = append(province.CustomCodes, params.Code)
_, err = this.RPC().RegionProvinceRPC().UpdateRegionProvinceCustom(this.AdminContext(), &pb.UpdateRegionProvinceCustomRequest{
RegionProvinceId: params.ProvinceId,
CustomName: province.CustomName,
CustomCodes: province.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Success()
}

View File

@@ -0,0 +1,48 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
)
type AddTownCustomCodeAction struct {
actionutils.ParentAction
}
func (this *AddTownCustomCodeAction) RunPost(params struct {
TownId int64
Code string
}) {
defer this.CreateLogInfo(codes.RegionTown_LogAddRegionTownCode, params.TownId, params.Code)
townResp, err := this.RPC().RegionTownRPC().FindRegionTown(this.AdminContext(), &pb.FindRegionTownRequest{RegionTownId: params.TownId})
if err != nil {
this.ErrorPage(err)
return
}
var town = townResp.RegionTown
if town == nil {
this.NotFound("regionTown", params.TownId)
return
}
if len(params.Code) > 0 && !lists.ContainsString(town.CustomCodes, params.Code) {
town.CustomCodes = append(town.CustomCodes, params.Code)
_, err = this.RPC().RegionTownRPC().UpdateRegionTownCustom(this.AdminContext(), &pb.UpdateRegionTownCustomRequest{
RegionTownId: params.TownId,
CustomName: town.CustomName,
CustomCodes: town.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Success()
}

View File

@@ -0,0 +1,50 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type CitiesAction struct {
actionutils.ParentAction
}
func (this *CitiesAction) RunPost(params struct {
LibraryFileId int64
Size int
}) {
missingCitiesResp, err := this.RPC().IPLibraryFileRPC().CheckCitiesWithIPLibraryFileId(this.AdminContext(), &pb.CheckCitiesWithIPLibraryFileIdRequest{
IpLibraryFileId: params.LibraryFileId,
})
if err != nil {
this.ErrorPage(err)
return
}
var missingCityMaps = []maps.Map{}
for _, missingCity := range missingCitiesResp.MissingCities {
var similarCityMaps = []maps.Map{}
for _, city := range missingCity.SimilarCities {
similarCityMaps = append(similarCityMaps, maps.Map{
"id": city.Id,
"displayName": city.DisplayName,
})
}
missingCityMaps = append(missingCityMaps, maps.Map{
"countryName": missingCity.CountryName,
"provinceName": missingCity.ProvinceName,
"cityName": missingCity.CityName,
"similarCities": similarCityMaps,
})
if params.Size > 0 && len(missingCityMaps) >= params.Size {
break
}
}
this.Data["missingCities"] = missingCityMaps
this.Success()
}

View File

@@ -0,0 +1,43 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type CountriesAction struct {
actionutils.ParentAction
}
func (this *CountriesAction) RunPost(params struct {
LibraryFileId int64
}) {
missingCountriesResp, err := this.RPC().IPLibraryFileRPC().CheckCountriesWithIPLibraryFileId(this.AdminContext(), &pb.CheckCountriesWithIPLibraryFileIdRequest{
IpLibraryFileId: params.LibraryFileId,
})
if err != nil {
this.ErrorPage(err)
return
}
var missingCountryMaps = []maps.Map{}
for _, missingCountry := range missingCountriesResp.MissingCountries {
var similarCountryMaps = []maps.Map{}
for _, country := range missingCountry.SimilarCountries {
similarCountryMaps = append(similarCountryMaps, maps.Map{
"id": country.Id,
"displayName": country.DisplayName,
})
}
missingCountryMaps = append(missingCountryMaps, maps.Map{
"countryName": missingCountry.CountryName,
"similarCountries": similarCountryMaps,
})
}
this.Data["missingCountries"] = missingCountryMaps
this.Success()
}

View File

@@ -0,0 +1,35 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type FinishAction struct {
actionutils.ParentAction
}
func (this *FinishAction) RunPost(params struct {
LibraryFileId int64
}) {
defer this.CreateLogInfo(codes.IPLibrary_LogFinishIPLibrary, params.LibraryFileId)
// set finished
_, err := this.RPC().IPLibraryFileRPC().UpdateIPLibraryFileFinished(this.AdminContext(), &pb.UpdateIPLibraryFileFinishedRequest{IpLibraryFileId: params.LibraryFileId})
if err != nil {
this.ErrorPage(err)
return
}
// generate
_, err = this.RPC().IPLibraryFileRPC().GenerateIPLibraryFile(this.AdminContext(), &pb.GenerateIPLibraryFileRequest{IpLibraryFileId: params.LibraryFileId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type GenerateAction struct {
actionutils.ParentAction
}
func (this *GenerateAction) RunPost(params struct {
LibraryFileId int64
}) {
defer this.CreateLogInfo(codes.IPLibraryFile_LogGenerateIPLibraryFile, params.LibraryFileId)
_, err := this.RPC().IPLibraryFileRPC().GenerateIPLibraryFile(this.AdminContext(), &pb.GenerateIPLibraryFileRequest{IpLibraryFileId: params.LibraryFileId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,43 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type ProvidersAction struct {
actionutils.ParentAction
}
func (this *ProvidersAction) RunPost(params struct {
LibraryFileId int64
}) {
missingProvidersResp, err := this.RPC().IPLibraryFileRPC().CheckProvidersWithIPLibraryFileId(this.AdminContext(), &pb.CheckProvidersWithIPLibraryFileIdRequest{
IpLibraryFileId: params.LibraryFileId,
})
if err != nil {
this.ErrorPage(err)
return
}
var missingProviderMaps = []maps.Map{}
for _, missingProvider := range missingProvidersResp.MissingProviders {
var similarProviderMaps = []maps.Map{}
for _, provider := range missingProvider.SimilarProviders {
similarProviderMaps = append(similarProviderMaps, maps.Map{
"id": provider.Id,
"displayName": provider.DisplayName,
})
}
missingProviderMaps = append(missingProviderMaps, maps.Map{
"providerName": missingProvider.ProviderName,
"similarProviders": similarProviderMaps,
})
}
this.Data["missingProviders"] = missingProviderMaps
this.Success()
}

View File

@@ -0,0 +1,44 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type ProvincesAction struct {
actionutils.ParentAction
}
func (this *ProvincesAction) RunPost(params struct {
LibraryFileId int64
}) {
missingProvincesResp, err := this.RPC().IPLibraryFileRPC().CheckProvincesWithIPLibraryFileId(this.AdminContext(), &pb.CheckProvincesWithIPLibraryFileIdRequest{
IpLibraryFileId: params.LibraryFileId,
})
if err != nil {
this.ErrorPage(err)
return
}
var missingProvinceMaps = []maps.Map{}
for _, missingProvince := range missingProvincesResp.MissingProvinces {
var similarProvinceMaps = []maps.Map{}
for _, province := range missingProvince.SimilarProvinces {
similarProvinceMaps = append(similarProvinceMaps, maps.Map{
"id": province.Id,
"displayName": province.DisplayName,
})
}
missingProvinceMaps = append(missingProvinceMaps, maps.Map{
"countryName": missingProvince.CountryName,
"provinceName": missingProvince.ProvinceName,
"similarProvinces": similarProvinceMaps,
})
}
this.Data["missingProvinces"] = missingProvinceMaps
this.Success()
}

View File

@@ -0,0 +1,36 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
)
type TestFormatAction struct {
actionutils.ParentAction
}
func (this *TestFormatAction) RunPost(params struct {
Template string
Text string
}) {
if len(params.Text) == 0 {
this.Fail("请先输入测试数据")
}
template, err := iplibrary.NewTemplate(params.Template)
if err != nil {
this.Fail("数据格式模板解析错误:" + err.Error())
}
var emptyValues = []string{"0", "无"} // TODO
values, ok := template.Extract(params.Text, emptyValues)
if !ok {
this.Fail("无法分析数据,填写的测试数据和模板不符合")
}
this.Data["values"] = values
this.Success()
}

View File

@@ -0,0 +1,51 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type TownsAction struct {
actionutils.ParentAction
}
func (this *TownsAction) RunPost(params struct {
LibraryFileId int64
Size int
}) {
missingTownsResp, err := this.RPC().IPLibraryFileRPC().CheckTownsWithIPLibraryFileId(this.AdminContext(), &pb.CheckTownsWithIPLibraryFileIdRequest{
IpLibraryFileId: params.LibraryFileId,
})
if err != nil {
this.ErrorPage(err)
return
}
var missingTownMaps = []maps.Map{}
for _, missingTown := range missingTownsResp.MissingTowns {
var similarTownMaps = []maps.Map{}
for _, town := range missingTown.SimilarTowns {
similarTownMaps = append(similarTownMaps, maps.Map{
"id": town.Id,
"displayName": town.DisplayName,
})
}
missingTownMaps = append(missingTownMaps, maps.Map{
"countryName": missingTown.CountryName,
"provinceName": missingTown.ProvinceName,
"cityName": missingTown.CityName,
"townName": missingTown.TownName,
"similarTowns": similarTownMaps,
})
if params.Size > 0 && len(missingTownMaps) >= params.Size {
break
}
}
this.Data["missingTowns"] = missingTownMaps
this.Success()
}

View File

@@ -0,0 +1,271 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package creating
import (
"bytes"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"io"
)
type UploadAction struct {
actionutils.ParentAction
}
func (this *UploadAction) RunPost(params struct {
Name string
Template string
EmptyValues []string
Password string
File *actions.File
}) {
if len(params.Name) == 0 {
this.Fail("请输入IP库名称")
return
}
if len(params.Template) == 0 {
this.Fail("请输入数据格式模板")
return
}
template, err := iplibrary.NewTemplate(params.Template)
if err != nil {
this.Fail("模板格式错误:" + err.Error())
return
}
if params.File == nil {
this.Fail("请上传文件")
}
var emptyValues = []string{"0", "无"}
if len(params.EmptyValues) > 0 {
for _, emptyValue := range params.EmptyValues {
if !lists.ContainsString(emptyValues, emptyValue) {
emptyValues = append(emptyValues, emptyValue)
}
}
}
fileResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.File.Filename,
Size: params.File.Size,
IsPublic: false,
MimeType: params.File.ContentType,
Type: "ipLibraryFile",
})
if err != nil {
this.ErrorPage(err)
return
}
var fileId = fileResp.FileId
fp, err := params.File.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
return
}
defer func() {
_ = fp.Close()
}()
var buf = make([]byte, 128*1024)
var dataBuf = []byte{}
var countries = []string{}
var provinces = [][2]string{}
var cities = [][3]string{}
var towns = [][4]string{}
var providers = []string{}
var countryMap = map[string]bool{} // countryName => bool
var provinceMap = map[string]bool{} // countryName_provinceName => bool
var cityMap = map[string]bool{} // countryName_provinceName_cityName => bool
var townMap = map[string]bool{} // countryName_provinceName_cityName_townName => bool
var providerMap = map[string]bool{} // providerName => bool
for {
n, err := fp.Read(buf)
if n > 0 {
var data = buf[:n]
// upload
_, uploadErr := this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: data,
})
if uploadErr != nil {
this.Fail("文件上传失败:" + uploadErr.Error())
}
// parse
dataBuf = append(dataBuf, data...)
left, valuesList, failLine, parseIsOk, err := this.parse(template, dataBuf, emptyValues)
if err != nil {
this.ErrorPage(err)
return
}
if !parseIsOk {
this.Fail("在 '" + failLine + "' 这行分析失败,请对比数据模板,检查数据格式")
return
}
for _, value := range valuesList {
var countryName = value["country"]
var provinceName = value["province"]
var cityName = value["city"]
var townName = value["town"]
var providerName = value["provider"]
// country
if len(countryName) > 0 {
_, ok := countryMap[countryName]
if !ok {
countries = append(countries, countryName)
countryMap[countryName] = true
}
// province
if len(provinceName) > 0 {
var key = countryName + "_" + provinceName
_, ok = provinceMap[key]
if !ok {
provinceMap[key] = true
provinces = append(provinces, [2]string{countryName, provinceName})
}
// city
if len(cityName) > 0 {
key += "_" + cityName
_, ok = cityMap[key]
if !ok {
cityMap[key] = true
cities = append(cities, [3]string{countryName, provinceName, cityName})
}
// town
if len(townName) > 0 {
key += "_" + townName
_, ok = townMap[key]
if !ok {
townMap[key] = true
towns = append(towns, [4]string{countryName, provinceName, cityName, townName})
}
}
}
}
}
// provider
if len(providerName) > 0 {
_, ok := providerMap[providerName]
if !ok {
providerMap[providerName] = true
providers = append(providers, providerName)
}
}
}
dataBuf = left
}
if err != nil {
if err != io.EOF {
this.ErrorPage(err)
return
}
break
}
}
countriesJSON, err := json.Marshal(countries)
if err != nil {
this.ErrorPage(err)
return
}
provincesJSON, err := json.Marshal(provinces)
if err != nil {
this.ErrorPage(err)
return
}
citiesJSON, err := json.Marshal(cities)
if err != nil {
this.ErrorPage(err)
return
}
townsJSON, err := json.Marshal(towns)
if err != nil {
this.ErrorPage(err)
return
}
providersJSON, err := json.Marshal(providers)
if err != nil {
this.ErrorPage(err)
return
}
createResp, err := this.RPC().IPLibraryFileRPC().CreateIPLibraryFile(this.AdminContext(), &pb.CreateIPLibraryFileRequest{
Name: params.Name,
Template: params.Template,
FileId: fileId,
EmptyValues: params.EmptyValues,
Password: params.Password,
CountriesJSON: countriesJSON,
ProvincesJSON: provincesJSON,
CitiesJSON: citiesJSON,
TownsJSON: townsJSON,
ProvidersJSON: providersJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
var ipLibraryFileId = createResp.IpLibraryFileId
defer this.CreateLogInfo(codes.IPLibraryFile_LogUploadIPLibraryFile, ipLibraryFileId)
this.Data["libraryFileId"] = ipLibraryFileId
this.Success()
}
func (this *UploadAction) parse(template *iplibrary.Template, data []byte, emptyValues []string) (left []byte, valuesList []map[string]string, failLine string, ok bool, err error) {
ok = true
if len(data) == 0 {
return
}
for {
var index = bytes.IndexByte(data, '\n')
if index >= 0 {
var line = data[:index+1]
values, found := template.Extract(string(line), emptyValues)
if found {
valuesList = append(valuesList, values)
} else {
// 防止错误信息太长
if len(line) > 256 {
line = line[:256]
}
failLine = string(line)
ok = false
return
}
data = data[index+1:]
} else {
left = data
break
}
}
return
}

View File

@@ -0,0 +1,38 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibrary
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ArtifactId int64
}) {
defer this.CreateLogInfo(codes.IPLibraryArtifact_LogDeleteIPLibraryArtifact, params.ArtifactId)
// 删除数据库中的记录
_, err := this.RPC().IPLibraryArtifactRPC().DeleteIPLibraryArtifact(this.AdminContext(), &pb.DeleteIPLibraryArtifactRequest{IpLibraryArtifactId: params.ArtifactId})
if err != nil {
this.ErrorPage(err)
return
}
// 删除 EdgeAPI 目录下的 MaxMind 文件(删除所有,因为无法确定具体是哪个文件)
_, err = this.RPC().IPLibraryRPC().DeleteMaxMindFile(this.AdminContext(), &pb.DeleteMaxMindFileRequest{
Filename: "", // 空字符串表示删除所有
})
if err != nil {
// 记录错误但不影响删除操作
this.Fail("删除IP库文件失败" + err.Error())
return
}
this.Success()
}

View File

@@ -0,0 +1,73 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibrary
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
)
type DownloadAction struct {
actionutils.ParentAction
}
func (this *DownloadAction) Init() {
this.Nav("", "", "")
}
func (this *DownloadAction) RunGet(params struct {
ArtifactId int64
}) {
artifactResp, err := this.RPC().IPLibraryArtifactRPC().FindIPLibraryArtifact(this.AdminContext(), &pb.FindIPLibraryArtifactRequest{
IpLibraryArtifactId: params.ArtifactId,
})
if err != nil {
this.ErrorPage(err)
return
}
var artifact = artifactResp.IpLibraryArtifact
if artifact == nil {
this.NotFound("IPLibraryArtifact", params.ArtifactId)
return
}
var fileId = artifact.FileId
if fileId <= 0 {
this.WriteString("ip artifact file not generated")
return
}
fileResp, err := this.RPC().FileRPC().FindEnabledFile(this.AdminContext(), &pb.FindEnabledFileRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
return
}
var file = fileResp.File
if file == nil {
this.WriteString("file not found")
return
}
chunkIdsResp, err := this.RPC().FileChunkRPC().FindAllFileChunkIds(this.AdminContext(), &pb.FindAllFileChunkIdsRequest{FileId: file.Id})
if err != nil {
this.ErrorPage(err)
return
}
this.AddHeader("Content-Disposition", "attachment; filename=\"ip-"+artifact.Code+".db\";")
this.AddHeader("Content-Length", types.String(file.Size))
for _, chunkId := range chunkIdsResp.FileChunkIds {
chunkResp, err := this.RPC().FileChunkRPC().DownloadFileChunk(this.AdminContext(), &pb.DownloadFileChunkRequest{FileChunkId: chunkId})
if err != nil {
this.ErrorPage(err)
return
}
var chunk = chunkResp.FileChunk
if chunk == nil {
break
}
_, _ = this.Write(chunkResp.FileChunk.Data)
}
}

View File

@@ -0,0 +1,22 @@
package iplibrary
import (
"github.com/iwind/TeaGo/actions"
"net/http"
)
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) {
if action.Request.Method != http.MethodGet {
return
}
action.Data["mainTab"] = "component"
action.Data["secondMenuItem"] = "ip-library"
}

View File

@@ -0,0 +1,112 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibrary
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/iplibraryutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "ipLibrary", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
this.Data["canAccess"] = iplibraryutils.CanAccess()
// IP库列表
artifactsResp, err := this.RPC().IPLibraryArtifactRPC().FindAllIPLibraryArtifacts(this.AdminContext(), &pb.FindAllIPLibraryArtifactsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var artifactMaps = []maps.Map{}
// 按创建时间倒序排序,最新的在前
artifacts := artifactsResp.IpLibraryArtifacts
for i := 0; i < len(artifacts); i++ {
for j := i + 1; j < len(artifacts); j++ {
if artifacts[i].CreatedAt < artifacts[j].CreatedAt {
artifacts[i], artifacts[j] = artifacts[j], artifacts[i]
}
}
}
// 确保最后一个(最新的)是使用中的,其他都是未使用的
// 如果列表不为空,将最新的设为使用中,其他设为未使用
if len(artifacts) > 0 {
latestId := artifacts[0].Id
for _, artifact := range artifacts {
var shouldBePublic = (artifact.Id == latestId)
if artifact.IsPublic != shouldBePublic {
// 需要更新状态(这里只标记,不实际更新数据库,因为上传时已经更新了)
// 如果数据库状态不一致,这里会显示不一致,但不会自动修复
}
}
}
for _, artifact := range artifacts {
var meta = &iplibrary.Meta{}
if len(artifact.MetaJSON) > 0 {
err = json.Unmarshal(artifact.MetaJSON, meta)
if err != nil {
this.ErrorPage(err)
return
}
}
// 文件信息
var fileMap = maps.Map{
"size": 0,
}
if artifact.File != nil {
fileMap = maps.Map{
"size": artifact.File.Size,
}
}
// 判断是否使用中:如果是列表中的第一个(最新的),则标记为使用中
isPublic := artifact.Id == artifacts[0].Id
// 根据文件名判断IP库类型
libraryType := "未知"
if artifact.File != nil && len(artifact.File.Filename) > 0 {
filename := strings.ToLower(artifact.File.Filename)
if strings.Contains(filename, "city") {
libraryType = "city库"
} else if strings.Contains(filename, "asn") {
libraryType = "asn库"
}
}
artifactMaps = append(artifactMaps, maps.Map{
"id": artifact.Id,
"name": artifact.Name,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", artifact.CreatedAt),
"isPublic": isPublic,
"fileId": artifact.FileId,
"code": artifact.Code,
"libraryType": libraryType,
"countCountries": len(meta.Countries),
"countProvinces": len(meta.Provinces),
"countCities": len(meta.Cities),
"countTowns": len(meta.Towns),
"countProviders": len(meta.Providers),
"file": fileMap,
})
}
this.Data["artifacts"] = artifactMaps
this.Show()
}

View File

@@ -0,0 +1,97 @@
//go:build plus
package iplibrary
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/cities"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/countries"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/creating"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/libraries"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/library"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/providers"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/provinces"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/towns"
"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(plus.NewBasicHelper()).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(NewHelper()).
Helper(settingutils.NewHelper("ipLibrary")).
// IP库
Prefix("/settings/ip-library").
Get("", new(IndexAction)).
GetPost("/upload", new(UploadAction)).
Get("/download", new(DownloadAction)).
Post("/delete", new(DeleteAction)).
Post("/updatePublic", new(UpdatePublicAction)).
// IP库
Prefix("/settings/ip-library/libraries").
Get("", new(libraries.IndexAction)).
GetPost("/create", new(libraries.CreateAction)).
Post("/delete", new(libraries.DeleteAction)).
// 创建
Prefix("/settings/ip-library/creating").
Post("/testFormat", new(creating.TestFormatAction)).
Post("/upload", new(creating.UploadAction)).
Post("/countries", new(creating.CountriesAction)).
Post("/provinces", new(creating.ProvincesAction)).
Post("/cities", new(creating.CitiesAction)).
Post("/towns", new(creating.TownsAction)).
Post("/providers", new(creating.ProvidersAction)).
Post("/addCountryCustomCode", new(creating.AddCountryCustomCodeAction)).
Post("/addProvinceCustomCode", new(creating.AddProvinceCustomCodeAction)).
Post("/addCityCustomCode", new(creating.AddCityCustomCodeAction)).
Post("/addTownCustomCode", new(creating.AddTownCustomCodeAction)).
Post("/addProviderCustomCode", new(creating.AddProviderCustomCodeAction)).
Post("/finish", new(creating.FinishAction)).
Post("/generate", new(creating.GenerateAction)).
// 单个IP库
Prefix("/settings/ip-library/library").
Get("", new(library.IndexAction)).
Get("/download", new(library.DownloadAction)).
GetPost("/test", new(library.TestAction)).
// 国家/地区
Prefix("/settings/ip-library/countries").
Get("", new(countries.IndexAction)).
GetPost("/updatePopup", new(countries.UpdatePopupAction)).
// 省份
Prefix("/settings/ip-library/provinces").
Get("", new(provinces.IndexAction)).
GetPost("/updatePopup", new(provinces.UpdatePopupAction)).
// 城市
Prefix("/settings/ip-library/cities").
Get("", new(cities.IndexAction)).
GetPost("/updatePopup", new(cities.UpdatePopupAction)).
Post("/provinceOptions", new(cities.ProvinceOptionsAction)).
// 区县
Prefix("/settings/ip-library/towns").
Get("", new(towns.IndexAction)).
GetPost("/updatePopup", new(towns.UpdatePopupAction)).
Post("/provinceOptions", new(towns.ProvinceOptionsAction)).
Post("/cityOptions", new(towns.CityOptionsAction)).
// ISP
Prefix("/settings/ip-library/providers").
Get("", new(providers.IndexAction)).
GetPost("/updatePopup", new(providers.UpdatePopupAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,9 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibraryutils
import "github.com/iwind/TeaGo/Tea"
func CanAccess() bool {
return Tea.IsTesting()
}

View File

@@ -0,0 +1,50 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package libraries
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/iplibraryutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type CreateAction struct {
actionutils.ParentAction
}
func (this *CreateAction) Init() {
this.Nav("", "", "create")
}
func (this *CreateAction) RunGet(params struct {
LibraryFileId int64
}) {
// 检查权限
if !iplibraryutils.CanAccess() {
return
}
this.Data["canAccess"] = true
// 初始化
this.Data["updatingLibraryFile"] = nil
if params.LibraryFileId > 0 {
libraryFileResp, err := this.RPC().IPLibraryFileRPC().FindIPLibraryFile(this.AdminContext(), &pb.FindIPLibraryFileRequest{IpLibraryFileId: params.LibraryFileId})
if err != nil {
this.ErrorPage(err)
return
}
var libraryFile = libraryFileResp.IpLibraryFile
if libraryFile != nil {
this.Data["updatingLibraryFile"] = maps.Map{
"id": libraryFile.Id,
"name": libraryFile.Name,
"template": libraryFile.Template,
"emptyValues": libraryFile.EmptyValues,
}
}
}
this.Show()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package libraries
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
LibraryFileId int64
}) {
defer this.CreateLogInfo(codes.IPLibraryFile_LogDeleteIPLibraryFile, params.LibraryFileId)
_, err := this.RPC().IPLibraryFileRPC().DeleteIPLibraryFile(this.AdminContext(), &pb.DeleteIPLibraryFileRequest{IpLibraryFileId: params.LibraryFileId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,48 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package libraries
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/iplibraryutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "library")
}
func (this *IndexAction) RunGet(params struct{}) {
this.Data["canAccess"] = iplibraryutils.CanAccess()
librariesResp, err := this.RPC().IPLibraryFileRPC().FindAllFinishedIPLibraryFiles(this.AdminContext(), &pb.FindAllFinishedIPLibraryFilesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var libraryMaps = []maps.Map{}
for _, library := range librariesResp.IpLibraryFiles {
if library.EmptyValues == nil {
library.EmptyValues = []string{}
}
libraryMaps = append(libraryMaps, maps.Map{
"id": library.Id,
"name": library.Name,
"template": library.Template,
"emptyValues": library.EmptyValues,
"generatedTime": timeutil.FormatTime("Y-m-d H:i:s", library.GeneratedAt),
"generatedFileId": library.GeneratedFileId,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", library.CreatedAt),
})
}
this.Data["libraries"] = libraryMaps
this.Show()
}

View File

@@ -0,0 +1,73 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package library
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
)
type DownloadAction struct {
actionutils.ParentAction
}
func (this *DownloadAction) Init() {
this.Nav("", "", "")
}
func (this *DownloadAction) RunGet(params struct {
LibraryFileId int64
}) {
libraryResp, err := this.RPC().IPLibraryFileRPC().FindIPLibraryFile(this.AdminContext(), &pb.FindIPLibraryFileRequest{IpLibraryFileId: params.LibraryFileId})
if err != nil {
this.ErrorPage(err)
return
}
var library = libraryResp.IpLibraryFile
if library == nil {
this.NotFound("IPLibraryFile", params.LibraryFileId)
return
}
var fileId = library.GeneratedFileId
if fileId <= 0 {
this.WriteString("ip library file not generated")
return
}
fileResp, err := this.RPC().FileRPC().FindEnabledFile(this.AdminContext(), &pb.FindEnabledFileRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
return
}
var file = fileResp.File
if file == nil {
this.WriteString("file not found")
return
}
chunkIdsResp, err := this.RPC().FileChunkRPC().FindAllFileChunkIds(this.AdminContext(), &pb.FindAllFileChunkIdsRequest{FileId: file.Id})
if err != nil {
this.ErrorPage(err)
return
}
this.AddHeader("Content-Disposition", "attachment; filename=\"ip-library.db\";")
this.AddHeader("Content-Length", types.String(file.Size))
for _, chunkId := range chunkIdsResp.FileChunkIds {
chunkResp, err := this.RPC().FileChunkRPC().DownloadFileChunk(this.AdminContext(), &pb.DownloadFileChunkRequest{FileChunkId: chunkId})
if err != nil {
this.ErrorPage(err)
return
}
var chunk = chunkResp.FileChunk
if chunk == nil {
break
}
_, _ = this.Write(chunkResp.FileChunk.Data)
}
this.Show()
}

View File

@@ -0,0 +1,18 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package library
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
// TODO
this.Show()
}

View File

@@ -0,0 +1,157 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package library
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"os"
)
type TestAction struct {
actionutils.ParentAction
}
func (this *TestAction) Init() {
this.Nav("", "ipLibrary", "test")
}
func (this *TestAction) RunGet(params struct{}) {
// 通过 RPC 查询 EdgeAPI 的 MaxMind 文件状态
statusResp, err := this.RPC().IPLibraryRPC().FindMaxMindFileStatus(this.AdminContext(), &pb.FindMaxMindFileStatusRequest{})
if err != nil {
// RPC 调用失败,记录错误并使用本地检查作为后备
logs.Println("[IP_LIBRARY_TEST]RPC call failed: " + err.Error())
// RPC 调用失败,使用本地检查作为后备
iplibDir := Tea.Root + "/data/iplibrary"
cityDBPath := iplibDir + "/maxmind-city.mmdb"
asnDBPath := iplibDir + "/maxmind-asn.mmdb"
uploadedCityExists := false
uploadedASNExists := false
if _, err := os.Stat(cityDBPath); err == nil {
uploadedCityExists = true
}
if _, err := os.Stat(asnDBPath); err == nil {
uploadedASNExists = true
}
// 检查是否使用了MaxMind库通过测试查询来判断
testIP := "8.8.8.8"
testResult := iplibrary.LookupIP(testIP)
usingMaxMind := false
if testResult != nil && testResult.IsOk() {
if testResult.CountryId() == 0 && len(testResult.CountryName()) > 0 {
usingMaxMind = true
}
}
this.Data["maxMindCityExists"] = uploadedCityExists
this.Data["maxMindASNExists"] = uploadedASNExists
this.Data["usingMaxMind"] = usingMaxMind
this.Data["usingEmbeddedMaxMind"] = usingMaxMind && !uploadedCityExists
} else {
// 使用 RPC 返回的状态
this.Data["maxMindCityExists"] = statusResp.CityExists
this.Data["maxMindASNExists"] = statusResp.AsnExists
this.Data["usingMaxMind"] = statusResp.UsingMaxMind
this.Data["usingEmbeddedMaxMind"] = statusResp.UsingEmbeddedMaxMind
}
this.Show()
}
func (this *TestAction) RunPost(params struct {
Ip string
Must *actions.Must
}) {
if len(params.Ip) == 0 {
this.Fail("请输入IP地址")
return
}
// 查询IP信息
var result = iplibrary.LookupIP(params.Ip)
if result == nil || !result.IsOk() {
this.Data["result"] = maps.Map{
"isOk": false,
"ip": params.Ip,
"error": "未找到该IP的地理位置信息",
}
this.Success()
return
}
// 判断IP库类型
// MaxMind库的特征CountryId 和 ProvinceId 通常为 0因为MaxMind不使用ID系统
// 只要查询结果中CountryId为0且有国家名称就认为是MaxMind不管文件是否存在可能是嵌入的
iplibDir := Tea.Root + "/data/iplibrary"
cityDBPath := iplibDir + "/maxmind-city.mmdb"
var libraryType = "默认IP库"
var libraryVersion = ""
// 如果查询结果中CountryId为0MaxMind特征且有国家名称则认为是MaxMind
// 不管文件是否存在,因为可能是使用嵌入的 MaxMind 库
if result.CountryId() == 0 && len(result.CountryName()) > 0 {
libraryType = "MaxMind GeoIP2"
libraryVersion = "3"
}
// 通过 RPC 查询 EdgeAPI 的 MaxMind 文件状态
statusResp, err := this.RPC().IPLibraryRPC().FindMaxMindFileStatus(this.AdminContext(), &pb.FindMaxMindFileStatusRequest{})
uploadedCityExists := false
uploadedASNExists := false
if err == nil {
uploadedCityExists = statusResp.CityExists
uploadedASNExists = statusResp.AsnExists
} else {
// RPC 调用失败,使用本地检查作为后备
if _, err := os.Stat(cityDBPath); err == nil {
uploadedCityExists = true
}
asnDBPath := iplibDir + "/maxmind-asn.mmdb"
if _, err := os.Stat(asnDBPath); err == nil {
uploadedASNExists = true
}
}
// 判断是否使用了MaxMind库即使没有上传文件也可能使用嵌入的默认MaxMind库
usingMaxMind := false
if result.CountryId() == 0 && len(result.CountryName()) > 0 {
usingMaxMind = true
}
this.Data["result"] = maps.Map{
"isDone": true,
"isOk": true,
"ip": params.Ip,
"libraryType": libraryType,
"libraryVersion": libraryVersion,
"country": result.CountryName(),
"countryId": result.CountryId(),
"province": result.ProvinceName(),
"provinceId": result.ProvinceId(),
"city": result.CityName(),
"cityId": result.CityId(),
"town": result.TownName(),
"townId": result.TownId(),
"provider": result.ProviderName(),
"providerId": result.ProviderId(),
"summary": result.Summary(),
"regionSummary": result.RegionSummary(),
}
this.Data["maxMindCityExists"] = uploadedCityExists
this.Data["maxMindASNExists"] = uploadedASNExists
this.Data["usingMaxMind"] = usingMaxMind
this.Data["usingEmbeddedMaxMind"] = usingMaxMind && !uploadedCityExists
this.Success()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package providers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/iplibraryutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "provider")
}
func (this *IndexAction) RunGet(params struct{}) {
// 检查权限
if !iplibraryutils.CanAccess() {
return
}
this.Data["canAccess"] = true
providersResp, err := this.RPC().RegionProviderRPC().FindAllRegionProviders(this.AdminContext(), &pb.FindAllRegionProvidersRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var providerMaps = []maps.Map{}
for _, provider := range providersResp.RegionProviders {
if provider.Codes == nil {
provider.Codes = []string{}
}
if provider.CustomCodes == nil {
provider.CustomCodes = []string{}
}
providerMaps = append(providerMaps, maps.Map{
"id": provider.Id,
"name": provider.Name,
"codes": provider.Codes,
"customName": provider.CustomName,
"customCodes": provider.CustomCodes,
})
}
this.Data["providers"] = providerMaps
this.Show()
}

View File

@@ -0,0 +1,75 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package providers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
ProviderId int64
}) {
providerResp, err := this.RPC().RegionProviderRPC().FindRegionProvider(this.AdminContext(), &pb.FindRegionProviderRequest{RegionProviderId: params.ProviderId})
if err != nil {
this.ErrorPage(err)
return
}
var provider = providerResp.RegionProvider
if provider == nil {
this.NotFound("regionProvider", params.ProviderId)
return
}
if provider.Codes == nil {
provider.Codes = []string{}
}
if provider.CustomCodes == nil {
provider.CustomCodes = []string{}
}
this.Data["provider"] = maps.Map{
"id": provider.Id,
"name": provider.Name,
"codes": provider.Codes,
"customName": provider.CustomName,
"customCodes": provider.CustomCodes,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
ProviderId int64
CustomName string
CustomCodes []string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.RegionProvider_LogUpdateRegionProviderCustom, params.ProviderId)
_, err := this.RPC().RegionProviderRPC().UpdateRegionProviderCustom(this.AdminContext(), &pb.UpdateRegionProviderCustomRequest{
RegionProviderId: params.ProviderId,
CustomName: params.CustomName,
CustomCodes: params.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,78 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package provinces
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/iplibraryutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "province")
}
func (this *IndexAction) RunGet(params struct {
CountryId int64
}) {
// 检查权限
if !iplibraryutils.CanAccess() {
return
}
this.Data["canAccess"] = true
if params.CountryId <= 0 {
params.CountryId = 1 // china
}
this.Data["countryId"] = params.CountryId
// 所有国家/地区
countriesResp, err := this.RPC().RegionCountryRPC().FindAllRegionCountries(this.AdminContext(), &pb.FindAllRegionCountriesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var countryMaps = []maps.Map{}
for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{
"id": country.Id,
"name": country.DisplayName,
})
}
this.Data["countries"] = countryMaps
// 当前国家/地区下的省份
provincesResp, err := this.RPC().RegionProvinceRPC().FindAllRegionProvincesWithRegionCountryId(this.AdminContext(), &pb.FindAllRegionProvincesWithRegionCountryIdRequest{
RegionCountryId: params.CountryId,
})
if err != nil {
this.ErrorPage(err)
return
}
var provinceMaps = []maps.Map{}
for _, province := range provincesResp.RegionProvinces {
if province.Codes == nil {
province.Codes = []string{}
}
if province.CustomCodes == nil {
province.CustomCodes = []string{}
}
provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id,
"name": province.Name,
"codes": province.Codes,
"customName": province.CustomName,
"customCodes": province.CustomCodes,
})
}
this.Data["provinces"] = provinceMaps
this.Show()
}

View File

@@ -0,0 +1,72 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package provinces
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
ProvinceId int64
}) {
provinceResp, err := this.RPC().RegionProvinceRPC().FindRegionProvince(this.AdminContext(), &pb.FindRegionProvinceRequest{RegionProvinceId: params.ProvinceId})
if err != nil {
this.ErrorPage(err)
return
}
var province = provinceResp.RegionProvince
if province == nil {
this.NotFound("regionProvince", params.ProvinceId)
return
}
if province.Codes == nil {
province.Codes = []string{}
}
if province.CustomCodes == nil {
province.CustomCodes = []string{}
}
this.Data["province"] = maps.Map{
"id": province.Id,
"name": province.Name,
"codes": province.Codes,
"customName": province.CustomName,
"customCodes": province.CustomCodes,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
ProvinceId int64
CustomName string
CustomCodes []string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.RegionProvince_LogUpdateRegionProvinceCustom, params.ProvinceId)
_, err := this.RPC().RegionProvinceRPC().UpdateRegionProvinceCustom(this.AdminContext(), &pb.UpdateRegionProvinceCustomRequest{
RegionProvinceId: params.ProvinceId,
CustomName: params.CustomName,
CustomCodes: params.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,36 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package towns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type CityOptionsAction struct {
actionutils.ParentAction
}
func (this *CityOptionsAction) RunPost(params struct {
ProvinceId int64
}) {
citiesResp, err := this.RPC().RegionCityRPC().FindAllRegionCitiesWithRegionProvinceId(this.AdminContext(), &pb.FindAllRegionCitiesWithRegionProvinceIdRequest{
RegionProvinceId: params.ProvinceId,
})
if err != nil {
this.ErrorPage(err)
return
}
var cityMaps = []maps.Map{}
for _, city := range citiesResp.RegionCities {
cityMaps = append(cityMaps, maps.Map{
"id": city.Id,
"name": city.DisplayName,
})
}
this.Data["cities"] = cityMaps
this.Success()
}

View File

@@ -0,0 +1,84 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package towns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/iplibraryutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "town")
}
func (this *IndexAction) RunGet(params struct {
CountryId int64
ProvinceId int64
CityId int64
}) {
// 检查权限
if !iplibraryutils.CanAccess() {
return
}
this.Data["canAccess"] = true
if params.CountryId <= 0 || params.ProvinceId <= 0 || params.CityId <= 0 {
params.CountryId = 1 // china
params.ProvinceId = 1 // beijing
params.CityId = 1 // beijing
}
this.Data["countryId"] = params.CountryId
this.Data["provinceId"] = params.ProvinceId
this.Data["cityId"] = params.CityId
// 所有国家/地区
countriesResp, err := this.RPC().RegionCountryRPC().FindAllRegionCountries(this.AdminContext(), &pb.FindAllRegionCountriesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var countryMaps = []maps.Map{}
for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{
"id": country.Id,
"name": country.DisplayName,
})
}
this.Data["countries"] = countryMaps
// 区县列表
townsResp, err := this.RPC().RegionTownRPC().FindAllRegionTownsWithRegionCityId(this.AdminContext(), &pb.FindAllRegionTownsWithRegionCityIdRequest{
RegionCityId: params.CityId,
})
if err != nil {
this.ErrorPage(err)
return
}
var townMaps = []maps.Map{}
for _, town := range townsResp.RegionTowns {
if town.Codes == nil {
town.Codes = []string{}
}
if town.CustomCodes == nil {
town.CustomCodes = []string{}
}
townMaps = append(townMaps, maps.Map{
"id": town.Id,
"name": town.Name,
"codes": town.Codes,
"customName": town.CustomName,
"customCodes": town.CustomCodes,
})
}
this.Data["towns"] = townMaps
this.Show()
}

View File

@@ -0,0 +1,36 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package towns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type ProvinceOptionsAction struct {
actionutils.ParentAction
}
func (this *ProvinceOptionsAction) RunPost(params struct {
CountryId int64
}) {
provincesResp, err := this.RPC().RegionProvinceRPC().FindAllRegionProvincesWithRegionCountryId(this.AdminContext(), &pb.FindAllRegionProvincesWithRegionCountryIdRequest{
RegionCountryId: params.CountryId,
})
if err != nil {
this.ErrorPage(err)
return
}
var provinceMaps = []maps.Map{}
for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id,
"name": province.DisplayName,
})
}
this.Data["provinces"] = provinceMaps
this.Success()
}

View File

@@ -0,0 +1,72 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package towns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
TownId int64
}) {
townResp, err := this.RPC().RegionTownRPC().FindRegionTown(this.AdminContext(), &pb.FindRegionTownRequest{RegionTownId: params.TownId})
if err != nil {
this.ErrorPage(err)
return
}
var town = townResp.RegionTown
if town == nil {
this.NotFound("regionTown", params.TownId)
return
}
if town.Codes == nil {
town.Codes = []string{}
}
if town.CustomCodes == nil {
town.CustomCodes = []string{}
}
this.Data["town"] = maps.Map{
"id": town.Id,
"name": town.Name,
"codes": town.Codes,
"customName": town.CustomName,
"customCodes": town.CustomCodes,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
TownId int64
CustomName string
CustomCodes []string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.RegionTown_LogUpdateRegionTownCustom, params.TownId)
_, err := this.RPC().RegionTownRPC().UpdateRegionTownCustom(this.AdminContext(), &pb.UpdateRegionTownCustomRequest{
RegionTownId: params.TownId,
CustomName: params.CustomName,
CustomCodes: params.CustomCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,35 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibrary
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdatePublicAction struct {
actionutils.ParentAction
}
func (this *UpdatePublicAction) RunPost(params struct {
ArtifactId int64
IsPublic bool
}) {
if params.IsPublic {
defer this.CreateLogInfo(codes.IPLibraryArtifact_LogUseIPLibraryArtifact, params.ArtifactId)
} else {
defer this.CreateLogInfo(codes.IPLibraryArtifact_LogCancelIPLibraryArtifact, params.ArtifactId)
}
_, err := this.RPC().IPLibraryArtifactRPC().UpdateIPLibraryArtifactIsPublic(this.AdminContext(), &pb.UpdateIPLibraryArtifactIsPublicRequest{
IpLibraryArtifactId: params.ArtifactId,
IsPublic: params.IsPublic,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,213 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibrary
import (
"bytes"
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library/iplibraryutils"
iplib "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"io"
"os"
"path/filepath"
"strings"
)
type UploadAction struct {
actionutils.ParentAction
}
func (this *UploadAction) Init() {
this.Nav("", "", "upload")
}
func (this *UploadAction) RunGet(params struct{}) {
this.Data["canAccess"] = iplibraryutils.CanAccess()
this.Show()
}
func (this *UploadAction) RunPost(params struct {
Name string
File *actions.File
Must *actions.Must
CSRF *actionutils.CSRF
}) {
if len(params.Name) == 0 {
this.FailField("name", "请输入IP库名称")
return
}
if params.File == nil {
this.Fail("请选择要上传的IP库文件")
return
}
fp, err := params.File.OriginFile.Open()
if err != nil {
this.Fail("读取IP库文件失败" + err.Error())
return
}
defer func() {
_ = fp.Close()
}()
data, err := io.ReadAll(io.LimitReader(fp, 64*sizes.M)) // 最大不超过64M
if err != nil {
this.Fail("读取IP库文件失败" + err.Error())
return
}
// 只支持 MaxMind 格式文件(.mmdb
filename := strings.ToLower(params.File.Filename)
if !strings.HasSuffix(filename, ".mmdb") {
this.Fail("只支持 MaxMind 格式文件(.mmdb请上传 GeoLite2-City.mmdb 或 GeoLite2-ASN.mmdb 文件")
return
}
// MaxMind 格式文件,保存到 data/iplibrary/ 目录
iplibDir := Tea.Root + "/data/iplibrary"
err = os.MkdirAll(iplibDir, 0755)
if err != nil {
this.Fail("创建IP库目录失败" + err.Error())
return
}
// 根据文件名判断是 City 还是 ASN
var targetPath string
if strings.Contains(filename, "city") {
targetPath = filepath.Join(iplibDir, "maxmind-city.mmdb")
} else if strings.Contains(filename, "asn") {
targetPath = filepath.Join(iplibDir, "maxmind-asn.mmdb")
} else {
this.Fail("MaxMind 文件名必须包含 'city' 或 'asn'")
return
}
// 保存文件(使用临时文件原子替换)
tmpPath := targetPath + ".tmp"
err = os.WriteFile(tmpPath, data, 0644)
if err != nil {
this.Fail("保存IP库文件失败" + err.Error())
return
}
// 原子替换
err = os.Rename(tmpPath, targetPath)
if err != nil {
os.Remove(tmpPath)
this.Fail("替换IP库文件失败" + err.Error())
return
}
// 通过 RPC 将文件上传到 EdgeAPI
_, err = this.RPC().IPLibraryRPC().UploadMaxMindFile(this.AdminContext(), &pb.UploadMaxMindFileRequest{
Filename: params.File.Filename,
Data: data,
})
if err != nil {
logs.Println("[IP_LIBRARY]upload MaxMind file to EdgeAPI failed: " + err.Error())
// 继续执行,不影响本地保存
}
// 创建简单的 Meta
meta := &iplib.Meta{
Version: 3,
Code: "maxmind",
Author: "MaxMind",
}
meta.Init()
// TODO 检查是否要自动创建省市区
// 上传IP库文件到数据库
fileResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.File.Filename,
Size: int64(len(data)),
IsPublic: false,
MimeType: "",
Type: "ipLibraryArtifact",
})
if err != nil {
this.ErrorPage(err)
return
}
var fileId = fileResp.FileId
var buf = make([]byte, 256*1024)
var dataReader = bytes.NewReader(data)
for {
n, err := dataReader.Read(buf)
if n > 0 {
_, chunkErr := this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: buf[:n],
})
if chunkErr != nil {
this.Fail("上传文件到数据库失败:" + chunkErr.Error())
return
}
}
if err != nil {
break
}
}
// 创建IP库信息
metaJSON, err := json.Marshal(meta)
if err != nil {
this.Fail("元数据编码失败:" + err.Error())
return
}
createResp, err := this.RPC().IPLibraryArtifactRPC().CreateIPLibraryArtifact(this.AdminContext(), &pb.CreateIPLibraryArtifactRequest{
FileId: fileId,
MetaJSON: metaJSON,
Name: params.Name,
})
if err != nil {
this.Fail("创建IP库失败" + err.Error())
return
}
// 获取所有IP库记录将其他记录的 isPublic 设为 false新上传的设为 true
artifactsResp, err := this.RPC().IPLibraryArtifactRPC().FindAllIPLibraryArtifacts(this.AdminContext(), &pb.FindAllIPLibraryArtifactsRequest{})
if err != nil {
// 如果获取列表失败,不影响上传成功,只记录日志
logs.Println("[IP_LIBRARY]failed to get all artifacts: " + err.Error())
} else {
// 将所有其他记录设为未使用
for _, artifact := range artifactsResp.IpLibraryArtifacts {
if artifact.Id != createResp.IpLibraryArtifactId {
if artifact.IsPublic {
// 将其他使用中的记录设为未使用
_, err = this.RPC().IPLibraryArtifactRPC().UpdateIPLibraryArtifactIsPublic(this.AdminContext(), &pb.UpdateIPLibraryArtifactIsPublicRequest{
IpLibraryArtifactId: artifact.Id,
IsPublic: false,
})
if err != nil {
logs.Println("[IP_LIBRARY]failed to update artifact " + fmt.Sprintf("%d", artifact.Id) + " isPublic: " + err.Error())
}
}
}
}
// 将新上传的记录设为使用中
_, err = this.RPC().IPLibraryArtifactRPC().UpdateIPLibraryArtifactIsPublic(this.AdminContext(), &pb.UpdateIPLibraryArtifactIsPublicRequest{
IpLibraryArtifactId: createResp.IpLibraryArtifactId,
IsPublic: true,
})
if err != nil {
logs.Println("[IP_LIBRARY]failed to set new artifact as public: " + err.Error())
}
}
this.Success()
}

View File

@@ -0,0 +1,17 @@
package lang
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Prefix("/settings/lang").
Post("/switch", new(SwitchAction)).
EndAll()
})
}

View File

@@ -0,0 +1,36 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package lang
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type SwitchAction struct {
actionutils.ParentAction
}
func (this *SwitchAction) Init() {
this.Nav("", "", "")
}
func (this *SwitchAction) RunPost(params struct{}) {
var langCode = this.LangCode()
if len(langCode) == 0 || langCode == "zh-cn" {
langCode = "en-us"
} else {
langCode = "zh-cn"
}
configloaders.UpdateAdminLang(this.AdminId(), langCode)
_, err := this.RPC().AdminRPC().UpdateAdminLang(this.AdminContext(), &pb.UpdateAdminLangRequest{LangCode: langCode})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,89 @@
package login
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: this.AdminId()})
if err != nil {
this.ErrorPage(err)
return
}
admin := adminResp.Admin
if admin == nil {
this.NotFound("admin", this.AdminId())
return
}
this.Data["admin"] = maps.Map{
"username": admin.Username,
"fullname": admin.Fullname,
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
Username string
Password string
Password2 string
Must *actions.Must
}) {
defer this.CreateLogInfo(codes.AdminLogin_LogUpdateLogin)
params.Must.
Field("username", params.Username).
Require("请输入登录用户名").
Match(`^[a-zA-Z0-9_]+$`, "用户名中只能包含英文、数字或下划线")
existsResp, err := this.RPC().AdminRPC().CheckAdminUsername(this.AdminContext(), &pb.CheckAdminUsernameRequest{
AdminId: this.AdminId(),
Username: params.Username,
})
if err != nil {
this.ErrorPage(err)
return
}
if existsResp.Exists {
this.FailField("username", "此用户名已经被别的管理员使用,请换一个")
}
if len(params.Password) > 0 {
if params.Password != params.Password2 {
this.FailField("password2", "两次输入的密码不一致")
}
}
_, err = this.RPC().AdminRPC().UpdateAdminLogin(this.AdminContext(), &pb.UpdateAdminLoginRequest{
AdminId: this.AdminId(),
Username: params.Username,
Password: params.Password,
})
if err != nil {
this.ErrorPage(err)
return
}
// 通知更新
err = configloaders.NotifyAdminModuleMappingChange()
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,19 @@
package login
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.AdminModuleCodeCommon)).
Helper(settingutils.NewHelper("login")).
Prefix("/settings/login").
GetPost("", new(IndexAction)).
EndAll()
})
}

View File

@@ -0,0 +1,67 @@
package database
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: this.AdminId()})
if err != nil {
this.ErrorPage(err)
return
}
admin := adminResp.Admin
if admin == nil {
this.NotFound("admin", this.AdminId())
return
}
this.Data["admin"] = maps.Map{
"fullname": admin.Fullname,
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
Fullname string
Must *actions.Must
}) {
defer this.CreateLogInfo(codes.AdminProfile_LogUpdateProfile)
params.Must.
Field("fullname", params.Fullname).
Require("请输入你的姓名")
_, err := this.RPC().AdminRPC().UpdateAdminInfo(this.AdminContext(), &pb.UpdateAdminInfoRequest{
AdminId: this.AdminId(),
Fullname: params.Fullname,
})
if err != nil {
this.ErrorPage(err)
return
}
// 通知更新
err = configloaders.NotifyAdminModuleMappingChange()
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,19 @@
package database
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.AdminModuleCodeCommon)).
Helper(settingutils.NewHelper("profile")).
Prefix("/settings/profile").
GetPost("", new(IndexAction)).
EndAll()
})
}

View File

@@ -0,0 +1,18 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package security
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
)
type DismissXFFPromptAction struct {
actionutils.ParentAction
}
func (this *DismissXFFPromptAction) RunPost(params struct{}) {
helpers.DisableXFFPrompt()
this.Success()
}

View File

@@ -0,0 +1,180 @@
package security
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
ShowAll bool
}) {
this.Data["showAll"] = params.ShowAll
config, err := configloaders.LoadSecurityConfig()
if err != nil {
this.ErrorPage(err)
return
}
if config.AllowIPs == nil {
config.AllowIPs = []string{}
}
// 国家和地区
var countryMaps = []maps.Map{}
for _, countryId := range config.AllowCountryIds {
countryResp, err := this.RPC().RegionCountryRPC().FindRegionCountry(this.AdminContext(), &pb.FindRegionCountryRequest{RegionCountryId: countryId})
if err != nil {
this.ErrorPage(err)
return
}
var country = countryResp.RegionCountry
if country != nil {
countryMaps = append(countryMaps, maps.Map{
"id": country.Id,
"name": country.DisplayName,
})
}
}
this.Data["countries"] = countryMaps
// 省份
var provinceMaps = []maps.Map{}
for _, provinceId := range config.AllowProvinceIds {
provinceResp, err := this.RPC().RegionProvinceRPC().FindRegionProvince(this.AdminContext(), &pb.FindRegionProvinceRequest{RegionProvinceId: provinceId})
if err != nil {
this.ErrorPage(err)
return
}
var province = provinceResp.RegionProvince
if province != nil {
provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id,
"name": province.DisplayName,
})
}
}
this.Data["provinces"] = provinceMaps
this.Data["config"] = config
this.Show()
}
func (this *IndexAction) RunPost(params struct {
Frame string
CountryIdsJSON []byte
ProvinceIdsJSON []byte
AllowLocal bool
AllowIPs []string
AllowRememberLogin bool
ClientIPHeaderNames string
ClientIPHeaderOnly bool
DenySearchEngines bool
DenySpiders bool
CheckClientFingerprint bool
CheckClientRegion bool
DomainsJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.AdminSecurity_LogUpdateSecuritySettings)
config, err := configloaders.LoadSecurityConfig()
if err != nil {
this.ErrorPage(err)
return
}
// 框架
config.Frame = params.Frame
// 国家和地区
var countryIds = []int64{}
if len(params.CountryIdsJSON) > 0 {
err = json.Unmarshal(params.CountryIdsJSON, &countryIds)
if err != nil {
this.ErrorPage(err)
return
}
}
config.AllowCountryIds = countryIds
// 省份
var provinceIds = []int64{}
if len(params.ProvinceIdsJSON) > 0 {
err = json.Unmarshal(params.ProvinceIdsJSON, &provinceIds)
if err != nil {
this.ErrorPage(err)
return
}
}
config.AllowProvinceIds = provinceIds
// 允许的IP
if len(params.AllowIPs) > 0 {
for _, ip := range params.AllowIPs {
_, err := shared.ParseIPRange(ip)
if err != nil {
this.Fail("允许访问的IP '" + ip + "' 格式错误:" + err.Error())
}
}
config.AllowIPs = params.AllowIPs
} else {
config.AllowIPs = []string{}
}
// 允许本地
config.AllowLocal = params.AllowLocal
// 客户端IP获取方式
config.ClientIPHeaderNames = params.ClientIPHeaderNames
config.ClientIPHeaderOnly = params.ClientIPHeaderOnly
// 禁止搜索引擎和爬虫
config.DenySearchEngines = params.DenySearchEngines
config.DenySpiders = params.DenySpiders
// 允许的域名
var domains = []string{}
if len(params.DomainsJSON) > 0 {
err = json.Unmarshal(params.DomainsJSON, &domains)
if err != nil {
this.Fail("解析允许访问的域名失败:" + err.Error())
}
}
config.AllowDomains = domains
// 允许记住登录
config.AllowRememberLogin = params.AllowRememberLogin
// Cookie检查
config.CheckClientFingerprint = params.CheckClientFingerprint
config.CheckClientRegion = params.CheckClientRegion
err = configloaders.UpdateSecurityConfig(config)
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,20 @@
package security
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.NewHelper("security")).
Prefix("/settings/security").
GetPost("", new(IndexAction)).
Post("/dismissXFFPrompt", new(DismissXFFPromptAction)).
EndAll()
})
}

View File

@@ -0,0 +1,86 @@
package adminserverutils
import (
"errors"
"github.com/iwind/TeaGo"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"gopkg.in/yaml.v3"
"net"
"os"
"time"
)
var ServerConfigIsChanged = false
const configFilename = "server.yaml"
// LoadServerConfig 读取当前服务配置
func LoadServerConfig() (*TeaGo.ServerConfig, error) {
configFile := Tea.ConfigFile(configFilename)
data, err := os.ReadFile(configFile)
if err != nil {
return nil, err
}
var serverConfig = &TeaGo.ServerConfig{}
err = yaml.Unmarshal(data, serverConfig)
if err != nil {
return nil, err
}
return serverConfig, nil
}
// WriteServerConfig 保存当前服务配置
func WriteServerConfig(serverConfig *TeaGo.ServerConfig) error {
data, err := yaml.Marshal(serverConfig)
if err != nil {
return err
}
err = os.WriteFile(Tea.ConfigFile(configFilename), data, 0666)
if err != nil {
return err
}
ServerConfigIsChanged = true
return nil
}
// ReadServerHTTPS 检查HTTPS地址
func ReadServerHTTPS() (port int, err error) {
config, err := LoadServerConfig()
if err != nil {
return 0, err
}
if config == nil {
return 0, errors.New("could not load server config")
}
if config.Https.On && len(config.Https.Listen) > 0 {
for _, listen := range config.Https.Listen {
_, portString, splitErr := net.SplitHostPort(listen)
if splitErr == nil {
var portInt = types.Int(portString)
if portInt > 0 {
// 是否已经启动
checkErr := func() error {
conn, connErr := net.DialTimeout("tcp", ":"+portString, 1*time.Second)
if connErr != nil {
return connErr
}
_ = conn.Close()
return nil
}()
if checkErr != nil {
continue
}
port = portInt
err = nil
break
}
}
}
}
return
}

View File

@@ -0,0 +1,28 @@
package server
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
this.Data["serverIsChanged"] = adminserverutils.ServerConfigIsChanged
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["serverConfig"] = serverConfig
this.Show()
}

View File

@@ -0,0 +1,21 @@
package server
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.AdminModuleCodeServer)).
Helper(settingutils.NewHelper("server")).
Prefix("/settings/server").
Get("", new(IndexAction)).
GetPost("/updateHTTPPopup", new(UpdateHTTPPopupAction)).
GetPost("/updateHTTPSPopup", new(UpdateHTTPSPopupAction)).
EndAll()
})
}

View File

@@ -0,0 +1,69 @@
package server
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/iwind/TeaGo/actions"
"net"
)
type UpdateHTTPPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateHTTPPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateHTTPPopupAction) RunGet(params struct{}) {
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["serverConfig"] = serverConfig
this.Show()
}
func (this *UpdateHTTPPopupAction) RunPost(params struct {
IsOn bool
Listens []string
Must *actions.Must
}) {
defer this.CreateLogInfo(codes.AdminServer_LogUpdateServerHTTPSettings)
if len(params.Listens) == 0 {
this.Fail("请输入绑定地址")
}
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.Fail("保存失败:" + err.Error())
}
serverConfig.Http.On = params.IsOn
listen := []string{}
for _, addr := range params.Listens {
addr = utils.FormatAddress(addr)
if len(addr) == 0 {
continue
}
if _, _, err := net.SplitHostPort(addr); err != nil {
addr += ":80"
}
listen = append(listen, addr)
}
serverConfig.Http.Listen = listen
err = adminserverutils.WriteServerConfig(serverConfig)
if err != nil {
this.Fail("保存失败:" + err.Error())
}
this.Success()
}

View File

@@ -0,0 +1,145 @@
package server
import (
"context"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
"net"
"os"
)
type UpdateHTTPSPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateHTTPSPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateHTTPSPopupAction) RunGet(params struct{}) {
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["serverConfig"] = serverConfig
// 证书
certConfigs := []*sslconfigs.SSLCertConfig{}
if len(serverConfig.Https.Cert) > 0 && len(serverConfig.Https.Key) > 0 {
certData, err := os.ReadFile(Tea.Root + "/" + serverConfig.Https.Cert)
if err != nil {
this.ErrorPage(err)
return
}
keyData, err := os.ReadFile(Tea.Root + "/" + serverConfig.Https.Key)
if err != nil {
this.ErrorPage(err)
return
}
certConfig := &sslconfigs.SSLCertConfig{
Id: 0,
Name: "-",
CertData: certData,
KeyData: keyData,
}
_ = certConfig.Init(context.TODO())
certConfig.CertData = nil
certConfig.KeyData = nil
certConfigs = append(certConfigs, certConfig)
}
this.Data["certConfigs"] = certConfigs
this.Show()
}
func (this *UpdateHTTPSPopupAction) RunPost(params struct {
IsOn bool
Listens []string
CertIdsJSON []byte
Must *actions.Must
}) {
defer this.CreateLogInfo(codes.AdminServer_LogUpdateServerHTTPSSettings)
if len(params.Listens) == 0 {
this.Fail("请输入绑定地址")
}
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.Fail("保存失败:" + err.Error())
}
serverConfig.Https.On = params.IsOn
listen := []string{}
for _, addr := range params.Listens {
addr = utils.FormatAddress(addr)
if len(addr) == 0 {
continue
}
if _, _, err := net.SplitHostPort(addr); err != nil {
addr += ":80"
}
listen = append(listen, addr)
}
serverConfig.Https.Listen = listen
// 证书
certIds := []int64{}
err = json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil {
this.ErrorPage(err)
return
}
if params.IsOn && len(certIds) == 0 {
this.Fail("要启用HTTPS需要先选择或上传一个可用的证书")
}
// 保存证书到本地
if len(certIds) > 0 && certIds[0] != 0 {
certResp, err := this.RPC().SSLCertRPC().FindEnabledSSLCertConfig(this.AdminContext(), &pb.FindEnabledSSLCertConfigRequest{
SslCertId: certIds[0],
})
if err != nil {
this.ErrorPage(err)
return
}
if len(certResp.SslCertJSON) == 0 {
this.Fail("选择的证书已失效,请换一个")
}
certConfig := &sslconfigs.SSLCertConfig{}
err = json.Unmarshal(certResp.SslCertJSON, certConfig)
if err != nil {
this.ErrorPage(err)
return
}
err = os.WriteFile(Tea.ConfigFile("https.key.pem"), certConfig.KeyData, 0666)
if err != nil {
this.Fail("保存密钥失败:" + err.Error())
}
err = os.WriteFile(Tea.ConfigFile("https.cert.pem"), certConfig.CertData, 0666)
if err != nil {
this.Fail("保存证书失败:" + err.Error())
}
serverConfig.Https.Key = "configs/https.key.pem"
serverConfig.Https.Cert = "configs/https.cert.pem"
}
err = adminserverutils.WriteServerConfig(serverConfig)
if err != nil {
this.Fail("保存配置失败:" + err.Error())
}
this.Success()
}

View File

@@ -0,0 +1,50 @@
//go:build !plus
package settingutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/iwind/TeaGo/actions"
)
type AdvancedHelper struct {
helpers.LangHelper
tab string
}
func NewAdvancedHelper(tab string) *AdvancedHelper {
return &AdvancedHelper{
tab: tab,
}
}
func (this *AdvancedHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
goNext = true
var action = actionPtr.Object()
// 左侧菜单
action.Data["teaMenu"] = "settings"
action.Data["teaSubMenu"] = "advanced"
// 标签栏
var tabbar = actionutils.NewTabbar()
var session = action.Session()
var adminId = session.GetInt64(teaconst.SessionAdminId)
if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabTransfer), "", "/settings/database", "", this.tab == "database")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAPINodes), "", "/settings/api", "", this.tab == "apiNodes")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAccessLogDatabases), "", "/db", "", this.tab == "dbNodes")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabTransfer), "", "/settings/transfer", "", this.tab == "transfer")
//tabbar.Add(codes.AdminSettingsTabBackup, "", "/settings/backup", "", this.tab == "backup")
}
actionutils.SetTabbar(actionPtr, tabbar)
return
}

View File

@@ -0,0 +1,81 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package settingutils
import (
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
)
type AdvancedHelper struct {
helpers.LangHelper
tab string
}
func NewAdvancedHelper(tab string) *AdvancedHelper {
return &AdvancedHelper{
tab: tab,
}
}
func (this *AdvancedHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
goNext = true
var action = actionPtr.Object()
// 左侧菜单
action.Data["teaMenu"] = "settings"
action.Data["teaSubMenu"] = "advanced"
// 标签栏
var tabbar = actionutils.NewTabbar()
var session = action.Session()
var adminId = session.GetInt64(teaconst.SessionAdminId)
if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) {
if this.tab != "authority" {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabDatabase), "", "/settings/database", "", this.tab == "database")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAPINodes), "", "/settings/api", "", this.tab == "apiNodes")
if plus.AllowComponent(plus.ComponentCodeUser) {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabUserNodes), "", "/settings/userNodes", "", this.tab == "userNodes")
}
// 外层始终显示「日志数据库」与「ClickHouse 配置」两个标签,不随点击变化
path := action.Request.URL.Path
langCode := strings.ToLower(configloaders.FindAdminLangForAction(actionPtr))
mysqlTabName := "Log Databases (MySQL)"
clickHouseTabName := "Log Databases (ClickHouse)"
if strings.HasPrefix(langCode, "zh") {
mysqlTabName = "日志数据库MySQL"
clickHouseTabName = "日志数据库ClickHouse"
}
tabbar.Add(mysqlTabName, "", "/db", "", (path == "/db" || strings.HasPrefix(path, "/db/")) && path != "/db/clickhouse")
tabbar.Add(clickHouseTabName, "", "/db/clickhouse", "", path == "/db/clickhouse")
if teaconst.IsPlus {
// 目前仅在调试模式下使用
if Tea.IsTesting() {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabIPLibrary), "", "/settings/ip-library", "", this.tab == "ipLibrary")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabClientOperationSystems), "", "/settings/client-systems", "", this.tab == "clientSystem")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabClientBrowsers), "", "/settings/client-browsers", "", this.tab == "clientBrowser")
}
}
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabTransfer), "", "/settings/transfer", "", this.tab == "transfer")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAuthority), "", "/settings/authority", "", this.tab == "authority") // 兼容以往版本
//tabbar.Add(this.Lang(actionPtr, codes.AdminSettings_TabBackup), "", "/settings/backup", "", this.tab == "backup")
} else {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAuthority), "", "/settings/authority", "", this.tab == "authority")
}
}
actionutils.SetTabbar(actionPtr, tabbar)
return
}

View File

@@ -0,0 +1,52 @@
//go:build !plus
package settingutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
helpers.LangHelper
tab string
}
func NewHelper(tab string) *Helper {
return &Helper{
tab: tab,
}
}
func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
goNext = true
var action = actionPtr.Object()
// 左侧菜单
action.Data["teaMenu"] = "settings"
action.Data["teaSubMenu"] = "basic"
// 标签栏
var tabbar = actionutils.NewTabbar()
var session = action.Session()
var adminId = session.GetInt64(teaconst.SessionAdminId)
if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminServer), "", "/settings/server", "", this.tab == "server")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminUI), "", "/settings/ui", "", this.tab == "ui")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminSecuritySettings), "", "/settings/security", "", this.tab == "security")
if teaconst.IsPlus {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabIPLibrary), "", "/settings/ip-library", "", this.tab == "ipLibrary")
}
}
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabProfile), "", "/settings/profile", "", this.tab == "profile")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabLogin), "", "/settings/login", "", this.tab == "login")
actionutils.SetTabbar(actionPtr, tabbar)
return
}

View File

@@ -0,0 +1,53 @@
//go:build plus
package settingutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
helpers.LangHelper
tab string
}
func NewHelper(tab string) *Helper {
return &Helper{
tab: tab,
}
}
func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
goNext = true
var action = actionPtr.Object()
// 左侧菜单
action.Data["teaMenu"] = "settings"
action.Data["teaSubMenu"] = "basic"
// 标签栏
var tabbar = actionutils.NewTabbar()
var session = action.Session()
var adminId = session.GetInt64(teaconst.SessionAdminId)
if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminServer), "", "/settings/server", "", this.tab == "server")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminUI), "", "/settings/ui", "", this.tab == "ui")
if teaconst.IsPlus {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabUserUI), "", "/settings/user-ui", "", this.tab == "userUI")
}
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminSecuritySettings), "", "/settings/security", "", this.tab == "security")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabIPLibrary), "", "/settings/ip-library", "", this.tab == "ipLibrary")
}
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabProfile), "", "/settings/profile", "", this.tab == "profile")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabLogin), "", "/settings/login", "", this.tab == "login")
actionutils.SetTabbar(actionPtr, tabbar)
return
}

Some files were not shown because too many files have changed in this diff Show More