Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d045f0526f | ||
|
|
0766ec9d5a | ||
|
|
4511b48382 | ||
|
|
151e58fbb9 | ||
|
|
c833ac2e96 | ||
|
|
99983330a3 | ||
|
|
a14e81a28f | ||
|
|
fc714a15a3 | ||
|
|
8a8f9d2912 | ||
|
|
58a0521605 | ||
|
|
e129b52f1c | ||
|
|
5cefd900a4 | ||
|
|
857dc70b4d | ||
|
|
e3036025bc | ||
|
|
76035dc3c2 | ||
|
|
22c55d0b83 | ||
|
|
9780937a1f | ||
|
|
b4eb6e92e0 |
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.0.10"
|
||||
Version = "0.0.12"
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
@@ -342,6 +342,12 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
// 修改配置
|
||||
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
||||
this.apiConfig = config
|
||||
return this.init()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
func (this *RPCClient) init() error {
|
||||
// 重新连接
|
||||
@@ -369,6 +375,8 @@ func (this *RPCClient) init() error {
|
||||
if len(conns) == 0 {
|
||||
return errors.New("[RPC]no available endpoints")
|
||||
}
|
||||
|
||||
// 这里不需要加锁,因为会和pickConn冲突
|
||||
this.conns = conns
|
||||
return nil
|
||||
}
|
||||
|
||||
92
internal/tasks/task_sync_api_nodes.go
Normal file
92
internal/tasks/task_sync_api_nodes.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewSyncAPINodesTask()
|
||||
go task.Start()
|
||||
})
|
||||
}
|
||||
|
||||
// API节点同步任务
|
||||
type SyncAPINodesTask struct {
|
||||
}
|
||||
|
||||
func NewSyncAPINodesTask() *SyncAPINodesTask {
|
||||
return &SyncAPINodesTask{}
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) Start() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
// 快速测试
|
||||
ticker = time.NewTicker(1 * time.Minute)
|
||||
}
|
||||
for range ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][SYNC_API_NODES]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) Loop() error {
|
||||
// 获取所有可用的节点
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := rpcClient.APINodeRPC().FindAllEnabledAPINodes(rpcClient.Context(0), &pb.FindAllEnabledAPINodesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newEndpoints := []string{}
|
||||
for _, node := range resp.Nodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
newEndpoints = append(newEndpoints, node.AccessAddrs...)
|
||||
}
|
||||
|
||||
// 和现有的对比
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if this.isSame(newEndpoints, config.RPC.Endpoints) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 修改RPC对象配置
|
||||
config.RPC.Endpoints = newEndpoints
|
||||
err = rpcClient.UpdateConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) isSame(endpoints1 []string, endpoints2 []string) bool {
|
||||
sort.Strings(endpoints1)
|
||||
sort.Strings(endpoints2)
|
||||
return strings.Join(endpoints1, "&") == strings.Join(endpoints2, "&")
|
||||
}
|
||||
15
internal/tasks/task_sync_api_nodes_test.go
Normal file
15
internal/tasks/task_sync_api_nodes_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSyncAPINodesTask_Loop(t *testing.T) {
|
||||
task := NewSyncAPINodesTask()
|
||||
err := task.Loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func FormatBits(bits int64) string {
|
||||
} else if bits < 1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fMB", float64(bits)/1000/1000)
|
||||
} else if bits < 1000*1000*1000*1000 {
|
||||
return fmt.Sprintf("%.2fGB", float64(bits)/1000/10001000)
|
||||
return fmt.Sprintf("%.2fGB", float64(bits)/1000/1000/1000)
|
||||
} else {
|
||||
return fmt.Sprintf("%.2fPB", float64(bits)/1000/1000/1000/1000)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ func init() {
|
||||
Get("/node/logs", new(node.LogsAction)).
|
||||
Post("/node/start", new(node.StartAction)).
|
||||
Post("/node/stop", new(node.StopAction)).
|
||||
Post("/node/up", new(node.UpAction)).
|
||||
|
||||
// 分组相关
|
||||
Get("/groups", new(groups.IndexAction)).
|
||||
|
||||
28
internal/web/actions/default/clusters/cluster/node/up.go
Normal file
28
internal/web/actions/default/clusters/cluster/node/up.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
// 手动上线
|
||||
type UpAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo("手动上线节点 %d", params.NodeId)
|
||||
|
||||
_, err := this.RPC().NodeRPC().UpdateNodeUp(this.AdminContext(), &pb.UpdateNodeUpRequest{
|
||||
NodeId: params.NodeId,
|
||||
IsUp: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -43,6 +43,9 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
// http api
|
||||
HttpAPIURL string
|
||||
|
||||
// html
|
||||
HtmlContent string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -90,6 +93,13 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
actionParams = &firewallconfigs.FirewallActionHTTPAPIConfig{
|
||||
URL: params.HttpAPIURL,
|
||||
}
|
||||
case firewallconfigs.FirewallActionTypeHTML:
|
||||
params.Must.
|
||||
Field("htmlContent", params.HtmlContent).
|
||||
Require("请输入HTML内容")
|
||||
actionParams = &firewallconfigs.FirewallActionHTMLConfig{
|
||||
Content: params.HtmlContent,
|
||||
}
|
||||
default:
|
||||
this.Fail("选择的类型'" + params.Type + "'暂时不支持")
|
||||
}
|
||||
|
||||
@@ -72,6 +72,9 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
// http api
|
||||
HttpAPIURL string
|
||||
|
||||
// HTML内容
|
||||
HtmlContent string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -119,6 +122,13 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
actionParams = &firewallconfigs.FirewallActionHTTPAPIConfig{
|
||||
URL: params.HttpAPIURL,
|
||||
}
|
||||
case firewallconfigs.FirewallActionTypeHTML:
|
||||
params.Must.
|
||||
Field("htmlContent", params.HtmlContent).
|
||||
Require("请输入HTML内容")
|
||||
actionParams = &firewallconfigs.FirewallActionHTMLConfig{
|
||||
Content: params.HtmlContent,
|
||||
}
|
||||
default:
|
||||
this.Fail("选择的类型'" + params.Type + "'暂时不支持")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package settings
|
||||
package health
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -9,16 +9,16 @@ import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type HealthAction struct {
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *HealthAction) Init() {
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "")
|
||||
this.SecondMenu("health")
|
||||
}
|
||||
|
||||
func (this *HealthAction) RunGet(params struct {
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
configResp, err := this.RPC().NodeClusterRPC().FindNodeClusterHealthCheckConfig(this.AdminContext(), &pb.FindNodeClusterHealthCheckConfigRequest{NodeClusterId: params.ClusterId})
|
||||
@@ -40,7 +40,7 @@ func (this *HealthAction) RunGet(params struct {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *HealthAction) RunPost(params struct {
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
HealthCheckJSON []byte
|
||||
Must *actions.Must
|
||||
@@ -1,4 +1,4 @@
|
||||
package settings
|
||||
package health
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
@@ -7,20 +7,19 @@ import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type HealthRunPopupAction struct {
|
||||
type RunPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *HealthRunPopupAction) Init() {
|
||||
func (this *RunPopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *HealthRunPopupAction) RunGet(params struct{}) {
|
||||
|
||||
func (this *RunPopupAction) RunGet(params struct{}) {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *HealthRunPopupAction) RunPost(params struct {
|
||||
func (this *RunPopupAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
|
||||
Must *actions.Must
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/cache"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/dns"
|
||||
firewallActions "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/firewall-actions"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/health"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/toa"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/waf"
|
||||
@@ -22,8 +23,8 @@ func init() {
|
||||
GetPost("", new(IndexAction)).
|
||||
|
||||
// 健康检查
|
||||
GetPost("/health", new(HealthAction)).
|
||||
GetPost("/healthRunPopup", new(HealthRunPopupAction)).
|
||||
GetPost("/health", new(health.IndexAction)).
|
||||
GetPost("/health/runPopup", new(health.RunPopupAction)).
|
||||
|
||||
// 缓存
|
||||
GetPost("/cache", new(cache.IndexAction)).
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package clusterutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
@@ -86,16 +88,27 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
|
||||
"isActive": selectedItem == "waf",
|
||||
"isOn": cluster.HttpFirewallPolicyId > 0,
|
||||
})
|
||||
items = append(items, maps.Map{
|
||||
"name": "WAF动作",
|
||||
"url": "/clusters/cluster/settings/firewall-actions?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "firewallAction",
|
||||
})
|
||||
items = append(items, maps.Map{
|
||||
"name": "健康检查",
|
||||
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "health",
|
||||
})
|
||||
|
||||
{
|
||||
hasActions, _ := this.checkFirewallActions(cluster.Id)
|
||||
items = append(items, maps.Map{
|
||||
"name": "WAF动作",
|
||||
"url": "/clusters/cluster/settings/firewall-actions?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "firewallAction",
|
||||
"isOn": hasActions,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
healthCheckIsOn, _ := this.checkHealthCheckIsOn(cluster.Id)
|
||||
items = append(items, maps.Map{
|
||||
"name": "健康检查",
|
||||
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "health",
|
||||
"isOn": healthCheckIsOn,
|
||||
})
|
||||
}
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "DNS设置",
|
||||
"url": "/clusters/cluster/settings/dns?clusterId=" + clusterId,
|
||||
@@ -114,3 +127,37 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查健康检查是否开启
|
||||
func (this *ClusterHelper) checkHealthCheckIsOn(clusterId int64) (bool, error) {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
resp, err := rpcClient.NodeClusterRPC().FindNodeClusterHealthCheckConfig(rpcClient.Context(0), &pb.FindNodeClusterHealthCheckConfigRequest{NodeClusterId: clusterId})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(resp.HealthCheckJSON) > 0 {
|
||||
healthCheckConfig := &serverconfigs.HealthCheckConfig{}
|
||||
err = json.Unmarshal(resp.HealthCheckJSON, healthCheckConfig)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return healthCheckConfig.IsOn, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 检查是否有WAF动作
|
||||
func (this *ClusterHelper) checkFirewallActions(clusterId int64) (bool, error) {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
resp, err := rpcClient.NodeClusterFirewallActionRPC().CountAllEnabledNodeClusterFirewallActions(rpcClient.Context(0), &pb.CountAllEnabledNodeClusterFirewallActionsRequest{NodeClusterId: clusterId})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return resp.Count > 0, nil
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
} else {
|
||||
this.Data["version"] = teaconst.Version
|
||||
}
|
||||
this.Data["faviconFileId"] = config.FaviconFileId
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
@@ -28,7 +29,8 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Type string
|
||||
|
||||
// file
|
||||
FileDir string
|
||||
FileDir string
|
||||
FileMemoryCapacityJSON []byte
|
||||
|
||||
CapacityJSON []byte
|
||||
MaxSizeJSON []byte
|
||||
@@ -50,8 +52,21 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("fileDir", params.FileDir).
|
||||
Require("请输入缓存目录")
|
||||
|
||||
memoryCapacity := &shared.SizeCapacity{}
|
||||
if len(params.FileMemoryCapacityJSON) > 0 {
|
||||
err := json.Unmarshal(params.FileMemoryCapacityJSON, memoryCapacity)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
options = &serverconfigs.HTTPFileCacheStorage{
|
||||
Dir: params.FileDir,
|
||||
MemoryPolicy: &serverconfigs.HTTPCachePolicy{
|
||||
Capacity: memoryCapacity,
|
||||
},
|
||||
}
|
||||
case serverconfigs.CachePolicyStorageMemory:
|
||||
options = &serverconfigs.HTTPMemoryCacheStorage{
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
@@ -52,7 +53,8 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
Type string
|
||||
|
||||
// file
|
||||
FileDir string
|
||||
FileDir string
|
||||
FileMemoryCapacityJSON []byte
|
||||
|
||||
CapacityJSON []byte
|
||||
MaxSizeJSON []byte
|
||||
@@ -74,8 +76,21 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("fileDir", params.FileDir).
|
||||
Require("请输入缓存目录")
|
||||
|
||||
memoryCapacity := &shared.SizeCapacity{}
|
||||
if len(params.FileMemoryCapacityJSON) > 0 {
|
||||
err := json.Unmarshal(params.FileMemoryCapacityJSON, memoryCapacity)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
options = &serverconfigs.HTTPFileCacheStorage{
|
||||
Dir: params.FileDir,
|
||||
MemoryPolicy: &serverconfigs.HTTPCachePolicy{
|
||||
Capacity: memoryCapacity,
|
||||
},
|
||||
}
|
||||
case serverconfigs.CachePolicyStorageMemory:
|
||||
options = &serverconfigs.HTTPMemoryCacheStorage{
|
||||
@@ -91,14 +106,14 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
}
|
||||
_, err = this.RPC().HTTPCachePolicyRPC().UpdateHTTPCachePolicy(this.AdminContext(), &pb.UpdateHTTPCachePolicyRequest{
|
||||
HttpCachePolicyId: params.CachePolicyId,
|
||||
IsOn: params.IsOn,
|
||||
Name: params.Name,
|
||||
Description: params.Description,
|
||||
CapacityJSON: params.CapacityJSON,
|
||||
MaxKeys: params.MaxKeys,
|
||||
MaxSizeJSON: params.MaxSizeJSON,
|
||||
Type: params.Type,
|
||||
OptionsJSON: optionsJSON,
|
||||
IsOn: params.IsOn,
|
||||
Name: params.Name,
|
||||
Description: params.Description,
|
||||
CapacityJSON: params.CapacityJSON,
|
||||
MaxKeys: params.MaxKeys,
|
||||
MaxSizeJSON: params.MaxSizeJSON,
|
||||
Type: params.Type,
|
||||
OptionsJSON: optionsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CreateAction struct {
|
||||
@@ -215,6 +216,22 @@ func (this *CreateAction) RunPost(params struct {
|
||||
if err != nil {
|
||||
this.Fail("域名解析失败:" + err.Error())
|
||||
}
|
||||
|
||||
// 检查域名是否已经存在
|
||||
allServerNames := serverconfigs.PlainServerNames(serverNames)
|
||||
if len(allServerNames) > 0 {
|
||||
dupResp, err := this.RPC().ServerRPC().CheckServerNameDuplicationInNodeCluster(this.AdminContext(), &pb.CheckServerNameDuplicationInNodeClusterRequest{
|
||||
ServerNames: allServerNames,
|
||||
NodeClusterId: params.ClusterId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(dupResp.DuplicatedServerNames) > 0 {
|
||||
this.Fail("域名 " + strings.Join(dupResp.DuplicatedServerNames, ", ") + " 已经被其他服务所占用,不能重复使用")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 源站地址
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 域名管理
|
||||
@@ -74,6 +75,34 @@ func (this *IndexAction) RunPost(params struct {
|
||||
this.Fail("域名解析失败:" + err.Error())
|
||||
}
|
||||
|
||||
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: params.ServerId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if serverResp.Server == nil || serverResp.Server.NodeCluster == nil {
|
||||
this.NotFound("server", params.ServerId)
|
||||
return
|
||||
}
|
||||
clusterId := serverResp.Server.NodeCluster.Id
|
||||
|
||||
// 检查域名是否已经存在
|
||||
allServerNames := serverconfigs.PlainServerNames(serverNames)
|
||||
if len(allServerNames) > 0 {
|
||||
dupResp, err := this.RPC().ServerRPC().CheckServerNameDuplicationInNodeCluster(this.AdminContext(), &pb.CheckServerNameDuplicationInNodeClusterRequest{
|
||||
ServerNames: allServerNames,
|
||||
NodeClusterId: clusterId,
|
||||
ExcludeServerId: params.ServerId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(dupResp.DuplicatedServerNames) > 0 {
|
||||
this.Fail("域名 " + strings.Join(dupResp.DuplicatedServerNames, ", ") + " 已经被其他服务所占用,不能重复使用")
|
||||
}
|
||||
}
|
||||
|
||||
_, err = this.RPC().ServerRPC().UpdateServerNames(this.AdminContext(), &pb.UpdateServerNamesRequest{
|
||||
ServerId: params.ServerId,
|
||||
ServerNamesJSON: []byte(params.ServerNames),
|
||||
|
||||
@@ -75,7 +75,7 @@ func (this *ClientsAction) RunGet(params struct {
|
||||
"count": stat.Count,
|
||||
"system": maps.Map{
|
||||
"id": stat.ClientSystem.Id,
|
||||
"name": stat.ClientSystem.Name,
|
||||
"name": stat.ClientSystem.Name + " " + stat.Version,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func (this *ClientsAction) RunGet(params struct {
|
||||
"count": stat.Count,
|
||||
"browser": maps.Map{
|
||||
"id": stat.ClientBrowser.Id,
|
||||
"name": stat.ClientBrowser.Name,
|
||||
"name": stat.ClientBrowser.Name + " " + stat.Version,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ func (this *UploadPopupAction) RunPost(params struct {
|
||||
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 保存
|
||||
|
||||
@@ -3,7 +3,9 @@ package ui
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"io"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -32,10 +34,14 @@ func (this *IndexAction) RunPost(params struct {
|
||||
ShowFinance bool
|
||||
ShowVersion bool
|
||||
Version string
|
||||
FaviconFile *actions.File
|
||||
LogoFile *actions.File
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改管理界面设置")
|
||||
|
||||
params.Must.
|
||||
Field("productName", params.ProductName).
|
||||
Require("请输入产品名称").
|
||||
@@ -53,6 +59,101 @@ func (this *IndexAction) RunPost(params struct {
|
||||
config.ShowFinance = params.ShowFinance
|
||||
config.ShowVersion = params.ShowVersion
|
||||
config.Version = params.Version
|
||||
|
||||
// 上传Favicon文件
|
||||
if params.FaviconFile != nil {
|
||||
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
|
||||
Filename: params.FaviconFile.Filename,
|
||||
Size: params.FaviconFile.Size,
|
||||
IsPublic: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
fileId := createResp.FileId
|
||||
|
||||
// 上传内容
|
||||
buf := make([]byte, 512*1024)
|
||||
reader, err := params.FaviconFile.OriginFile.Open()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
n, err := reader.Read(buf)
|
||||
if n > 0 {
|
||||
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
|
||||
FileId: fileId,
|
||||
Data: buf[:n],
|
||||
})
|
||||
if err != nil {
|
||||
this.Fail("上传失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
this.Fail("上传失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 置为已完成
|
||||
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
}
|
||||
config.FaviconFileId = fileId
|
||||
}
|
||||
|
||||
// 上传Logo文件
|
||||
if params.LogoFile != nil {
|
||||
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
|
||||
Filename: params.LogoFile.Filename,
|
||||
Size: params.LogoFile.Size,
|
||||
IsPublic: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
fileId := createResp.FileId
|
||||
|
||||
// 上传内容
|
||||
buf := make([]byte, 512*1024)
|
||||
reader, err := params.LogoFile.OriginFile.Open()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
n, err := reader.Read(buf)
|
||||
if n > 0 {
|
||||
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
|
||||
FileId: fileId,
|
||||
Data: buf[:n],
|
||||
})
|
||||
if err != nil {
|
||||
this.Fail("上传失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
this.Fail("上传失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 置为已完成
|
||||
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
}
|
||||
config.LogoFileId = fileId
|
||||
}
|
||||
|
||||
err = configloaders.UpdateAdminUIConfig(config)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -3,7 +3,9 @@ package userui
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"io"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -32,6 +34,8 @@ func (this *IndexAction) RunPost(params struct {
|
||||
ShowVersion bool
|
||||
Version string
|
||||
ShowFinance bool
|
||||
FaviconFile *actions.File
|
||||
LogoFile *actions.File
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
@@ -53,6 +57,101 @@ func (this *IndexAction) RunPost(params struct {
|
||||
config.ShowVersion = params.ShowVersion
|
||||
config.Version = params.Version
|
||||
config.ShowFinance = params.ShowFinance
|
||||
|
||||
// 上传Favicon文件
|
||||
if params.FaviconFile != nil {
|
||||
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
|
||||
Filename: params.FaviconFile.Filename,
|
||||
Size: params.FaviconFile.Size,
|
||||
IsPublic: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
fileId := createResp.FileId
|
||||
|
||||
// 上传内容
|
||||
buf := make([]byte, 512*1024)
|
||||
reader, err := params.FaviconFile.OriginFile.Open()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
n, err := reader.Read(buf)
|
||||
if n > 0 {
|
||||
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
|
||||
FileId: fileId,
|
||||
Data: buf[:n],
|
||||
})
|
||||
if err != nil {
|
||||
this.Fail("上传失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
this.Fail("上传失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 置为已完成
|
||||
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
}
|
||||
config.FaviconFileId = fileId
|
||||
}
|
||||
|
||||
// 上传Logo文件
|
||||
if params.LogoFile != nil {
|
||||
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
|
||||
Filename: params.LogoFile.Filename,
|
||||
Size: params.LogoFile.Size,
|
||||
IsPublic: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
fileId := createResp.FileId
|
||||
|
||||
// 上传内容
|
||||
buf := make([]byte, 512*1024)
|
||||
reader, err := params.LogoFile.OriginFile.Open()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
n, err := reader.Read(buf)
|
||||
if n > 0 {
|
||||
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
|
||||
FileId: fileId,
|
||||
Data: buf[:n],
|
||||
})
|
||||
if err != nil {
|
||||
this.Fail("上传失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
this.Fail("上传失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 置为已完成
|
||||
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
}
|
||||
config.LogoFileId = fileId
|
||||
}
|
||||
|
||||
err = configloaders.UpdateUserUIConfig(config)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
68
internal/web/actions/default/ui/image.go
Normal file
68
internal/web/actions/default/ui/image.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 公开的图片,不需要检查用户权限
|
||||
type ImageAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ImageAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *ImageAction) RunGet(params struct {
|
||||
FileId int64
|
||||
}) {
|
||||
fileResp, err := this.RPC().FileRPC().FindEnabledFile(this.AdminContext(), &pb.FindEnabledFileRequest{FileId: params.FileId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
file := fileResp.File
|
||||
if file == nil {
|
||||
this.NotFound("file", params.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
if !file.IsPublic {
|
||||
this.NotFound("file", params.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
chunkIdsResp, err := this.RPC().FileChunkRPC().FindAllFileChunkIds(this.AdminContext(), &pb.FindAllFileChunkIdsRequest{FileId: file.Id})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
mimeType := ""
|
||||
if len(file.Filename) > 0 {
|
||||
ext := filepath.Ext(file.Filename)
|
||||
mimeType = mime.TypeByExtension(ext)
|
||||
}
|
||||
if len(mimeType) == 0 {
|
||||
mimeType = "image/png"
|
||||
}
|
||||
|
||||
this.AddHeader("Last-Modified", "Fri, 06 Sep 2019 08:29:50 GMT")
|
||||
this.AddHeader("Content-Type", mimeType)
|
||||
this.AddHeader("Content-Length", strconv.FormatInt(file.Size, 10))
|
||||
for _, chunkId := range chunkIdsResp.FileChunkIds {
|
||||
chunkResp, err := this.RPC().FileChunkRPC().DownloadFileChunk(this.AdminContext(), &pb.DownloadFileChunkRequest{FileChunkId: chunkId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if chunkResp.FileChunk == nil {
|
||||
continue
|
||||
}
|
||||
this.Write(chunkResp.FileChunk.Data)
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,9 @@ func init() {
|
||||
server.
|
||||
Prefix("/ui").
|
||||
|
||||
// 公共可以访问的链接
|
||||
Get("/image/:fileId", new(ImageAction)).
|
||||
|
||||
// 以下的需要压缩
|
||||
Helper(&actions.Gzip{Level: gzip.BestCompression}).
|
||||
Get("/components.js", new(ComponentsAction)).
|
||||
|
||||
@@ -88,6 +88,8 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
||||
action.Data["teaShowVersion"] = config.ShowVersion
|
||||
action.Data["teaTitle"] = config.AdminSystemName
|
||||
action.Data["teaName"] = config.ProductName
|
||||
action.Data["teaFaviconFileId"] = config.FaviconFileId
|
||||
action.Data["teaLogoFileId"] = config.LogoFileId
|
||||
action.Data["teaUsername"] = configloaders.FindAdminFullname(adminId)
|
||||
|
||||
action.Data["teaUserAvatar"] = ""
|
||||
|
||||
@@ -1,131 +1,150 @@
|
||||
Vue.component("health-check-config-box", {
|
||||
props: ["v-health-check-config"],
|
||||
data: function () {
|
||||
let healthCheckConfig = this.vHealthCheckConfig
|
||||
let urlProtocol = "http"
|
||||
let urlPort = ""
|
||||
let urlRequestURI = "/"
|
||||
props: ["v-health-check-config"],
|
||||
data: function () {
|
||||
let healthCheckConfig = this.vHealthCheckConfig
|
||||
let urlProtocol = "http"
|
||||
let urlPort = ""
|
||||
let urlRequestURI = "/"
|
||||
let urlHost = ""
|
||||
|
||||
if (healthCheckConfig == null) {
|
||||
healthCheckConfig = {
|
||||
isOn: false,
|
||||
url: "",
|
||||
interval: {count: 60, unit: "second"},
|
||||
statusCodes: [200],
|
||||
timeout: {count: 10, unit: "second"},
|
||||
countTries: 3,
|
||||
tryDelay: {count: 100, unit: "ms"},
|
||||
autoDown: true,
|
||||
countUp: 1,
|
||||
countDown: 1
|
||||
}
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.changeURL()
|
||||
}, 500)
|
||||
} else {
|
||||
try {
|
||||
let url = new URL(healthCheckConfig.url)
|
||||
urlProtocol = url.protocol.substring(0, url.protocol.length - 1)
|
||||
urlPort = url.port
|
||||
urlRequestURI = url.pathname
|
||||
if (url.search.length > 0) {
|
||||
urlRequestURI += url.search
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
if (healthCheckConfig == null) {
|
||||
healthCheckConfig = {
|
||||
isOn: false,
|
||||
url: "",
|
||||
interval: {count: 60, unit: "second"},
|
||||
statusCodes: [200],
|
||||
timeout: {count: 10, unit: "second"},
|
||||
countTries: 3,
|
||||
tryDelay: {count: 100, unit: "ms"},
|
||||
autoDown: true,
|
||||
countUp: 1,
|
||||
countDown: 1
|
||||
}
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.changeURL()
|
||||
}, 500)
|
||||
} else {
|
||||
try {
|
||||
let url = new URL(healthCheckConfig.url)
|
||||
urlProtocol = url.protocol.substring(0, url.protocol.length - 1)
|
||||
|
||||
if (healthCheckConfig.statusCodes == null) {
|
||||
healthCheckConfig.statusCodes = [200]
|
||||
}
|
||||
if (healthCheckConfig.interval == null) {
|
||||
healthCheckConfig.interval = {count: 60, unit: "second"}
|
||||
}
|
||||
if (healthCheckConfig.timeout == null) {
|
||||
healthCheckConfig.timeout = {count: 10, unit: "second"}
|
||||
}
|
||||
if (healthCheckConfig.tryDelay == null) {
|
||||
healthCheckConfig.tryDelay = {count: 100, unit: "ms"}
|
||||
}
|
||||
if (healthCheckConfig.countUp == null || healthCheckConfig.countUp < 1) {
|
||||
healthCheckConfig.countUp = 1
|
||||
}
|
||||
if (healthCheckConfig.countDown == null || healthCheckConfig.countDown < 1) {
|
||||
healthCheckConfig.countDown = 1
|
||||
}
|
||||
}
|
||||
console.log(healthCheckConfig.countUp, healthCheckConfig.countDown)
|
||||
return {
|
||||
healthCheck: healthCheckConfig,
|
||||
advancedVisible: false,
|
||||
urlProtocol: urlProtocol,
|
||||
urlPort: urlPort,
|
||||
urlRequestURI: urlRequestURI
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
urlRequestURI: function () {
|
||||
if (this.urlRequestURI.length > 0 && this.urlRequestURI[0] != "/") {
|
||||
this.urlRequestURI = "/" + this.urlRequestURI
|
||||
}
|
||||
this.changeURL()
|
||||
},
|
||||
urlPort: function (v) {
|
||||
let port = parseInt(v)
|
||||
if (!isNaN(port)) {
|
||||
this.urlPort = port.toString()
|
||||
} else {
|
||||
this.urlPort = ""
|
||||
}
|
||||
this.changeURL()
|
||||
},
|
||||
urlProtocol: function () {
|
||||
this.changeURL()
|
||||
},
|
||||
"healthCheck.countTries": function (v) {
|
||||
let count = parseInt(v)
|
||||
if (!isNaN(count)) {
|
||||
this.healthCheck.countTries = count
|
||||
} else {
|
||||
this.healthCheck.countTries = 0
|
||||
}
|
||||
},
|
||||
"healthCheck.countUp": function (v) {
|
||||
let count = parseInt(v)
|
||||
if (!isNaN(count)) {
|
||||
this.healthCheck.countUp = count
|
||||
} else {
|
||||
this.healthCheck.countUp = 0
|
||||
}
|
||||
},
|
||||
"healthCheck.countDown": function (v) {
|
||||
let count = parseInt(v)
|
||||
if (!isNaN(count)) {
|
||||
this.healthCheck.countDown = count
|
||||
} else {
|
||||
this.healthCheck.countDown = 0
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showAdvanced: function () {
|
||||
this.advancedVisible = !this.advancedVisible
|
||||
},
|
||||
changeURL: function () {
|
||||
this.healthCheck.url = this.urlProtocol + "://${host}" + ((this.urlPort.length > 0) ? ":" + this.urlPort : "") + this.urlRequestURI
|
||||
},
|
||||
changeStatus: function (values) {
|
||||
this.healthCheck.statusCodes = values.$map(function (k, v) {
|
||||
let status = parseInt(v)
|
||||
if (isNaN(status)) {
|
||||
return 0
|
||||
} else {
|
||||
return status
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
// 域名
|
||||
urlHost = url.host
|
||||
if (urlHost == "%24%7Bhost%7D") {
|
||||
urlHost = "${host}"
|
||||
}
|
||||
let colonIndex = urlHost.indexOf(":")
|
||||
if (colonIndex > 0) {
|
||||
urlHost = urlHost.substring(0, colonIndex)
|
||||
}
|
||||
|
||||
urlPort = url.port
|
||||
urlRequestURI = url.pathname
|
||||
if (url.search.length > 0) {
|
||||
urlRequestURI += url.search
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
if (healthCheckConfig.statusCodes == null) {
|
||||
healthCheckConfig.statusCodes = [200]
|
||||
}
|
||||
if (healthCheckConfig.interval == null) {
|
||||
healthCheckConfig.interval = {count: 60, unit: "second"}
|
||||
}
|
||||
if (healthCheckConfig.timeout == null) {
|
||||
healthCheckConfig.timeout = {count: 10, unit: "second"}
|
||||
}
|
||||
if (healthCheckConfig.tryDelay == null) {
|
||||
healthCheckConfig.tryDelay = {count: 100, unit: "ms"}
|
||||
}
|
||||
if (healthCheckConfig.countUp == null || healthCheckConfig.countUp < 1) {
|
||||
healthCheckConfig.countUp = 1
|
||||
}
|
||||
if (healthCheckConfig.countDown == null || healthCheckConfig.countDown < 1) {
|
||||
healthCheckConfig.countDown = 1
|
||||
}
|
||||
}
|
||||
return {
|
||||
healthCheck: healthCheckConfig,
|
||||
advancedVisible: false,
|
||||
urlProtocol: urlProtocol,
|
||||
urlHost: urlHost,
|
||||
urlPort: urlPort,
|
||||
urlRequestURI: urlRequestURI
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
urlRequestURI: function () {
|
||||
if (this.urlRequestURI.length > 0 && this.urlRequestURI[0] != "/") {
|
||||
this.urlRequestURI = "/" + this.urlRequestURI
|
||||
}
|
||||
this.changeURL()
|
||||
},
|
||||
urlPort: function (v) {
|
||||
let port = parseInt(v)
|
||||
if (!isNaN(port)) {
|
||||
this.urlPort = port.toString()
|
||||
} else {
|
||||
this.urlPort = ""
|
||||
}
|
||||
this.changeURL()
|
||||
},
|
||||
urlProtocol: function () {
|
||||
this.changeURL()
|
||||
},
|
||||
urlHost: function () {
|
||||
this.changeURL()
|
||||
},
|
||||
"healthCheck.countTries": function (v) {
|
||||
let count = parseInt(v)
|
||||
if (!isNaN(count)) {
|
||||
this.healthCheck.countTries = count
|
||||
} else {
|
||||
this.healthCheck.countTries = 0
|
||||
}
|
||||
},
|
||||
"healthCheck.countUp": function (v) {
|
||||
let count = parseInt(v)
|
||||
if (!isNaN(count)) {
|
||||
this.healthCheck.countUp = count
|
||||
} else {
|
||||
this.healthCheck.countUp = 0
|
||||
}
|
||||
},
|
||||
"healthCheck.countDown": function (v) {
|
||||
let count = parseInt(v)
|
||||
if (!isNaN(count)) {
|
||||
this.healthCheck.countDown = count
|
||||
} else {
|
||||
this.healthCheck.countDown = 0
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showAdvanced: function () {
|
||||
this.advancedVisible = !this.advancedVisible
|
||||
},
|
||||
changeURL: function () {
|
||||
let urlHost = this.urlHost
|
||||
if (urlHost.length == 0) {
|
||||
urlHost = "${host}"
|
||||
}
|
||||
this.healthCheck.url = this.urlProtocol + "://" + urlHost + ((this.urlPort.length > 0) ? ":" + this.urlPort : "") + this.urlRequestURI
|
||||
},
|
||||
changeStatus: function (values) {
|
||||
this.healthCheck.statusCodes = values.$map(function (k, v) {
|
||||
let status = parseInt(v)
|
||||
if (isNaN(status)) {
|
||||
return 0
|
||||
} else {
|
||||
return status
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="healthCheckJSON" :value="JSON.stringify(healthCheck)"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tbody>
|
||||
@@ -143,28 +162,40 @@ Vue.component("health-check-config-box", {
|
||||
<tr>
|
||||
<td>URL *</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" v-model="urlProtocol">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">协议</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="urlProtocol">
|
||||
<option value="http">http://</option>
|
||||
<option value="https">https://</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<var style="color:grey">\${host}</var>
|
||||
</div>
|
||||
<div class="ui field">:</div>
|
||||
<div class="ui field">
|
||||
<input type="text" maxlength="5" style="width:5.4em" placeholder="端口" v-model="urlPort"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<input type="text" v-model="urlRequestURI" placeholder="/" style="width:23em"/>
|
||||
</div>
|
||||
</div>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>域名</td>
|
||||
<td>
|
||||
<input type="text" v-model="urlHost"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>端口</td>
|
||||
<td>
|
||||
<input type="text" maxlength="5" style="width:5.4em" placeholder="端口" v-model="urlPort"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RequestURI</td>
|
||||
<td><input type="text" v-model="urlRequestURI" placeholder="/" style="width:20em"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="ui divider"></div>
|
||||
<p class="comment" v-if="healthCheck.url.length > 0">拼接后的URL:<code-label>{{healthCheck.url}}</code-label>,其中\${host}指的是节点地址。</p>
|
||||
<p class="comment" v-if="healthCheck.url.length > 0">拼接后的URL:<code-label>{{healthCheck.url}}</code-label>,其中\${host}指的是域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>检测时间间隔</td>
|
||||
<td>
|
||||
|
||||
@@ -132,15 +132,21 @@ Vue.component("http-access-log-config-box", {
|
||||
<p class="comment">选中表示只输出日志到日志策略,而停止默认的日志存储。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>是否只记录WAF相关日志</td>
|
||||
<td>
|
||||
<checkbox v-model="accessLog.firewallOnly"></checkbox>
|
||||
<p class="comment">选中后只记录WAF相关的日志。通过此选项可有效减少访问日志数量,降低网络带宽和存储压力。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-show="(!vIsLocation || accessLog.isPrior) && accessLog.isOn">
|
||||
<h4>WAF相关</h4>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">是否只记录WAF相关日志</td>
|
||||
<td>
|
||||
<checkbox v-model="accessLog.firewallOnly"></checkbox>
|
||||
<p class="comment">选中后只记录WAF相关的日志。通过此选项可有效减少访问日志数量,降低网络带宽和存储压力。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
@@ -20,6 +20,8 @@ Vue.component("reverse-proxy-box", {
|
||||
requestHostType: 0,
|
||||
addHeaders: []
|
||||
}
|
||||
} else if (reverseProxyConfig.addHeaders == null) {
|
||||
reverseProxyConfig.addHeaders = []
|
||||
}
|
||||
|
||||
let forwardHeaders = [
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
<title>{$.teaTitle}</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
|
||||
{$if eq .teaFaviconFileId 0}
|
||||
<link rel="shortcut icon" href="/images/favicon.png"/>
|
||||
{$else}
|
||||
<link rel="shortcut icon" href="/ui/image/{$.teaFaviconFileId}"/>
|
||||
{$end}
|
||||
<link rel="stylesheet" type="text/css" href="/_/@default/@layout.css" media="all"/>
|
||||
{$TEA.SEMANTIC}
|
||||
<link rel="stylesheet" type="text/css" href="/_/@default/@layout_override.css" media="all"/>
|
||||
@@ -23,7 +27,7 @@
|
||||
<!-- 顶部导航 -->
|
||||
<div class="ui menu top-nav blue inverted small borderless" v-cloak="">
|
||||
<a href="/" class="item">
|
||||
<i class="ui icon leaf"></i> {{teaTitle}} <sup v-if="teaShowVersion">v{{teaVersion}}</sup>
|
||||
<i class="ui icon leaf" v-if="teaLogoFileId == 0"></i><img v-if="teaLogoFileId > 0" :src="'/ui/image/' + teaLogoFileId"/> {{teaTitle}} <sup v-if="teaShowVersion">v{{teaVersion}}</sup>
|
||||
</a>
|
||||
|
||||
<div class="right menu">
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
Tea.context(function () {
|
||||
this.deleteCluster = function (clusterId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除此集群吗?", function () {
|
||||
that.$post("/clusters/cluster/delete")
|
||||
.params({
|
||||
clusterId: clusterId
|
||||
})
|
||||
.success(function () {
|
||||
window.location = "/clusters"
|
||||
})
|
||||
})
|
||||
}
|
||||
this.deleteCluster = function (clusterId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除此集群吗?", function () {
|
||||
that.$post("/clusters/cluster/delete")
|
||||
.params({
|
||||
clusterId: clusterId
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.success("删除成功", function () {
|
||||
window.location = "/clusters"
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -22,7 +22,7 @@
|
||||
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
|
||||
<td>{{group.name}}</td>
|
||||
<td class="center">
|
||||
<span v-if="group.countNodes.length > 0">{{group.countNodes}}</span>
|
||||
<span v-if="group.countNodes > 0">{{group.countNodes}}</span>
|
||||
<span v-else class="disabled">0</span>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -97,7 +97,10 @@
|
||||
</td>
|
||||
<td class="center">
|
||||
<div v-if="!node.isUp">
|
||||
<span class="red">健康问题</span>
|
||||
<span class="red">健康问题下线</span>
|
||||
<div>
|
||||
<a href="" @click.prevent="upNode(node.id)">[上线]</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!node.isOn">
|
||||
<label-on :v-is-on="node.isOn"></label-on>
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
Tea.context(function () {
|
||||
this.deleteNode = function (nodeId) {
|
||||
teaweb.confirm("确定要删除这个节点吗?", function () {
|
||||
this.$post("/nodes/delete")
|
||||
.params({
|
||||
nodeId: nodeId
|
||||
})
|
||||
.refresh();
|
||||
});
|
||||
};
|
||||
});
|
||||
this.deleteNode = function (nodeId) {
|
||||
teaweb.confirm("确定要删除这个节点吗?", function () {
|
||||
this.$post("/nodes/delete")
|
||||
.params({
|
||||
nodeId: nodeId
|
||||
})
|
||||
.refresh();
|
||||
})
|
||||
}
|
||||
|
||||
this.upNode = function (nodeId) {
|
||||
teaweb.confirm("确定要手动上线此节点吗?", function () {
|
||||
this.$post("/clusters/cluster/node/up")
|
||||
.params({
|
||||
nodeId: nodeId
|
||||
})
|
||||
.refresh()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -92,6 +92,25 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- HTML -->
|
||||
<tbody v-if="type == 'html'">
|
||||
<tr>
|
||||
<td>HTML内容 *</td>
|
||||
<td>
|
||||
<textarea name="htmlContent" maxlength="128000" placeholder="完整的HTML内容"><!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 这里写提示文字 -->
|
||||
|
||||
</body>
|
||||
</html></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -92,6 +92,16 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- HTML -->
|
||||
<tbody v-if="type == 'html'">
|
||||
<tr>
|
||||
<td>HTML内容 *</td>
|
||||
<td>
|
||||
<textarea name="htmlContent" maxlength="128000" placeholder="完整的HTML内容" v-model="action.params.content"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -3,7 +3,7 @@ Tea.context(function () {
|
||||
|
||||
this.run = function () {
|
||||
teaweb.confirm("确定要对当前集群下的所有节点进行健康检查吗?", function () {
|
||||
teaweb.popup("/clusters/cluster/settings/healthRunPopup?clusterId=" + this.clusterId, {
|
||||
teaweb.popup("/clusters/cluster/settings/health/runPopup?clusterId=" + this.clusterId, {
|
||||
height: "25em"
|
||||
})
|
||||
})
|
||||
@@ -2,7 +2,11 @@
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
{$if eq .faviconFileId 0}
|
||||
<link rel="shortcut icon" href="/images/favicon.png"/>
|
||||
{$else}
|
||||
<link rel="shortcut icon" href="/ui/image/{$ .faviconFileId}"/>
|
||||
{$end}
|
||||
<title>登录{$.systemName}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
|
||||
{$TEA.VUE}
|
||||
|
||||
@@ -25,16 +25,32 @@
|
||||
<p class="comment">存放文件缓存的目录,通常填写绝对路径。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-border">文件目录最大容量</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'capacityJSON'" :v-count="0" :v-unit="'gb'"></size-capacity-box>
|
||||
<p class="comment">作为二级缓存的文件目录允许缓存的最大容量,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-border">内存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-count="1" :v-unit="'gb'"></size-capacity-box>
|
||||
<p class="comment">作为一级缓存的内存最大容量,如果为0表示不使用内存作为一级缓存。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody v-if="policyType != 'file'">
|
||||
<tr>
|
||||
<td>内存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'capacityJSON'" :v-count="0" :v-unit="'gb'"></size-capacity-box>
|
||||
<p class="comment">允许缓存的最大容量,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tr>
|
||||
<td>缓存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'capacityJSON'" :v-count="0" :v-unit="'gb'"></size-capacity-box>
|
||||
<p class="comment">允许缓存的最大内容长度,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
|
||||
@@ -19,49 +19,64 @@
|
||||
|
||||
<!-- 文件缓存选项 -->
|
||||
<tbody v-if="cachePolicy.type == 'file'">
|
||||
<tr>
|
||||
<td class="color-border">缓存目录</td>
|
||||
<td>
|
||||
{{cachePolicy.options.dir}}
|
||||
<p class="comment">存放文件缓存的目录,通常填写绝对路径。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-border">缓存目录</td>
|
||||
<td>
|
||||
{{cachePolicy.options.dir}}
|
||||
<p class="comment">存放文件缓存的目录,通常填写绝对路径。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-border">文件目录最大容量</td>
|
||||
<td>
|
||||
<size-capacity-view :v-value="cachePolicy.capacity" :v-default-text="'不限'"></size-capacity-view>
|
||||
<p class="comment">作为二级缓存的文件目录允许缓存的最大容量,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="cachePolicy.options.memoryPolicy != null && cachePolicy.options.memoryPolicy.capacity != null && cachePolicy.options.memoryPolicy.capacity.count > 0">
|
||||
<td class="color-border">内存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-view :v-value="cachePolicy.options.memoryPolicy.capacity"></size-capacity-view>
|
||||
<p class="comment">作为一级缓存的内存最大容量,如果为0表示不使用内存作为一级缓存。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>缓存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-view :v-value="cachePolicy.capacity" :v-default-text="'不限'"></size-capacity-view>
|
||||
<p class="comment">允许缓存的最大内容长度,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody v-if="cachePolicy.type != 'file'">
|
||||
<tr>
|
||||
<td>缓存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-view :v-value="cachePolicy.capacity" :v-default-text="'不限'"></size-capacity-view>
|
||||
<p class="comment">允许缓存的最大容量,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr>
|
||||
<td>最大内容长度</td>
|
||||
<td>
|
||||
<size-capacity-view :v-value="cachePolicy.maxSize" :v-default-text="'不限'"></size-capacity-view>
|
||||
<p class="comment">允许缓存的最大内容长度,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>容纳Key数量</td>
|
||||
<td>
|
||||
<span v-if="cachePolicy.maxKeys > 0">{{cachePolicy.maxKeys}}</span>
|
||||
<span v-else>不限</span>
|
||||
<p class="comment">可以容纳多少数量的Key,0表示不限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>描述</td>
|
||||
<td>
|
||||
<span v-if="cachePolicy.description.length > 0">{{cachePolicy.description}}</span>
|
||||
<span v-else class="disabled">暂时还没有描述。</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>最大内容长度</td>
|
||||
<td>
|
||||
<size-capacity-view :v-value="cachePolicy.maxSize" :v-default-text="'不限'"></size-capacity-view>
|
||||
<p class="comment">允许缓存的最大内容长度,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>容纳Key数量</td>
|
||||
<td>
|
||||
<span v-if="cachePolicy.maxKeys > 0">{{cachePolicy.maxKeys}}</span>
|
||||
<span v-else>不限</span>
|
||||
<p class="comment">可以容纳多少数量的Key,0表示不限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>描述</td>
|
||||
<td>
|
||||
<span v-if="cachePolicy.description.length > 0">{{cachePolicy.description}}</span>
|
||||
<span v-else class="disabled">暂时还没有描述。</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -27,16 +27,38 @@
|
||||
<p class="comment">存放文件缓存的目录,通常填写绝对路径。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-border">文件目录最大容量</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'capacityJSON'" :v-value="cachePolicy.capacity" :v-count="0" :v-unit="'gb'"></size-capacity-box>
|
||||
<p class="comment">作为二级缓存的文件目录允许缓存的最大容量,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="cachePolicy.options.memoryPolicy != null && cachePolicy.options.memoryPolicy.capacity != null">
|
||||
<td class="color-border">内存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-value="cachePolicy.options.memoryPolicy.capacity" :v-count="1" :v-unit="'gb'"></size-capacity-box>
|
||||
<p class="comment">作为一级缓存的内存最大容量,如果为0表示不使用内存作为一级缓存。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="cachePolicy.options.memoryPolicy == null || cachePolicy.options.memoryPolicy.capacity == null">
|
||||
<td class="color-border">内存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-count="0" :v-unit="'gb'"></size-capacity-box>
|
||||
<p class="comment">作为一级缓存的内存最大容量,如果为0表示不使用内存作为一级缓存。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>缓存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'capacityJSON'" :v-value="cachePolicy.capacity" :v-count="0" :v-unit="'gb'"></size-capacity-box>
|
||||
<p class="comment">允许缓存的最大内容长度,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody v-if="policyType != 'file'">
|
||||
<tr>
|
||||
<td>缓存最大容量</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'capacityJSON'" :v-value="cachePolicy.capacity" :v-count="0" :v-unit="'gb'"></size-capacity-box>
|
||||
<p class="comment">允许缓存的最大容量,如果为0表示没有限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
|
||||
@@ -33,7 +33,10 @@ Tea.context(function () {
|
||||
let chart = echarts.init(chartBox)
|
||||
let option = {
|
||||
xAxis: {
|
||||
data: stats.map(xFunc)
|
||||
data: stats.map(xFunc),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
|
||||
@@ -38,7 +38,10 @@ Tea.context(function () {
|
||||
let chart = echarts.init(chartBox)
|
||||
let option = {
|
||||
xAxis: {
|
||||
data: stats.map(xFunc)
|
||||
data: stats.map(xFunc),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
|
||||
@@ -21,7 +21,10 @@ Tea.context(function () {
|
||||
let chart = echarts.init(chartBox)
|
||||
let option = {
|
||||
xAxis: {
|
||||
data: stats.map(xFunc)
|
||||
data: stats.map(xFunc),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
|
||||
@@ -41,6 +41,36 @@
|
||||
<checkbox name="showFinance" v-model="config.showFinance"></checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>浏览器图标</td>
|
||||
<td>
|
||||
<div v-if="config.faviconFileId > 0">
|
||||
<a :href="'/ui/image/' + config.faviconFileId" target="_blank"><img :src="'/ui/image/' + config.faviconFileId" style="width:32px;border:1px #ccc solid;"/></a>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="disabled">还没有上传。</span>
|
||||
</div>
|
||||
<div style="margin-top: 0.8em">
|
||||
<input type="file" name="faviconFile" accept=".png"/>
|
||||
</div>
|
||||
<p class="comment">在浏览器标签栏显示的图标,请使用PNG格式。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Logo</td>
|
||||
<td>
|
||||
<div v-if="config.logoFileId > 0">
|
||||
<a :href="'/ui/image/' + config.logoFileId" target="_blank"><img :src="'/ui/image/' + config.logoFileId" style="width:32px;border:1px #ccc solid;"/></a>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="disabled">还没有上传。</span>
|
||||
</div>
|
||||
<div style="margin-top: 0.8em">
|
||||
<input type="file" name="logoFile" accept=".png"/>
|
||||
</div>
|
||||
<p class="comment">显示在系统界面上的图标,请使用PNG格式。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
|
||||
@@ -41,6 +41,36 @@
|
||||
<checkbox name="showFinance" v-model="config.showFinance"></checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>浏览器图标</td>
|
||||
<td>
|
||||
<div v-if="config.faviconFileId > 0">
|
||||
<a :href="'/ui/image/' + config.faviconFileId" target="_blank"><img :src="'/ui/image/' + config.faviconFileId" style="width:32px;border:1px #ccc solid;"/></a>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="disabled">还没有上传。</span>
|
||||
</div>
|
||||
<div style="margin-top: 0.8em">
|
||||
<input type="file" name="faviconFile" accept=".png"/>
|
||||
</div>
|
||||
<p class="comment">在浏览器标签栏显示的图标,请使用PNG格式。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Logo</td>
|
||||
<td>
|
||||
<div v-if="config.logoFileId > 0">
|
||||
<a :href="'/ui/image/' + config.logoFileId" target="_blank"><img :src="'/ui/image/' + config.logoFileId" style="width:32px;border:1px #ccc solid;"/></a>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="disabled">还没有上传。</span>
|
||||
</div>
|
||||
<div style="margin-top: 0.8em">
|
||||
<input type="file" name="logoFile" accept=".png"/>
|
||||
</div>
|
||||
<p class="comment">显示在系统界面上的图标,请使用PNG格式。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p class="comment">修改后,可能需要等待数分钟才会生效。</p>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user