Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42148a66bd | ||
|
|
96878715bf | ||
|
|
1a5f3342e7 | ||
|
|
3613d13a2b | ||
|
|
7786140d85 | ||
|
|
3a23b57f1b | ||
|
|
5a6e6fba69 | ||
|
|
910b3a6162 | ||
|
|
0dc19bed45 | ||
|
|
a7bdb64301 | ||
|
|
4739072a85 | ||
|
|
07bdae2488 | ||
|
|
b84035d821 | ||
|
|
dc0a7b9dae | ||
|
|
2937bd8de0 | ||
|
|
37315ef4d9 | ||
|
|
1986fece07 | ||
|
|
16b1657f35 | ||
|
|
c115c62cd9 | ||
|
|
d2df7f8d5b | ||
|
|
6cb79864e6 | ||
|
|
982d28c7b4 | ||
|
|
0f57516fdc | ||
|
|
75a89defcb | ||
|
|
22a6c52060 | ||
|
|
37607e4a41 | ||
|
|
5936155998 | ||
|
|
8d76de935f | ||
|
|
9baa530064 | ||
|
|
103414b338 | ||
|
|
72fe68ebfe | ||
|
|
cfed31958b | ||
|
|
3d5fca2d36 | ||
|
|
a5710286ec | ||
|
|
d0ce0c6c58 | ||
|
|
779e2cf0f2 | ||
|
|
2108474777 | ||
|
|
e25e0f1747 | ||
|
|
e8e74b639c | ||
|
|
a14fcd1e50 | ||
|
|
485c0e0891 | ||
|
|
00a19e9d43 | ||
|
|
67d0dc0783 | ||
|
|
3718c35842 | ||
|
|
567ffc80b6 | ||
|
|
5d15a08ac8 | ||
|
|
2b84037346 | ||
|
|
536f11e617 | ||
|
|
5d367a384e |
1
docker/.gitignore
vendored
Normal file
1
docker/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.zip
|
||||
36
docker/Dockerfile
Normal file
36
docker/Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="iwind.liu@gmail.com"
|
||||
ENV TZ "Asia/Shanghai"
|
||||
ENV VERSION 0.5.7
|
||||
ENV ROOT_DIR /usr/local/goedge
|
||||
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
|
||||
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"
|
||||
|
||||
RUN apk add --no-cache tzdata
|
||||
|
||||
RUN apk add wget
|
||||
RUN mkdir ${ROOT_DIR}; \
|
||||
cd ${ROOT_DIR}; \
|
||||
wget ${TAR_URL} -O ${TAR_FILE}; \
|
||||
apk add unzip; \
|
||||
unzip ${TAR_FILE}; \
|
||||
rm -f ${TAR_FILE}
|
||||
|
||||
RUN apk add mysql mysql-client; \
|
||||
sed -e "s/\[mysqld\]/\[mysqld\]\n\ndatadir=\/var\/lib\/mysql\nport=3306\ninnodb_flush_log_at_trx_commit=2\nmax_connections=256\nmax_prepared_stmt_count=65535\nbinlog_cache_size=1M\nbinlog_stmt_cache_size=1M\nthread_cache_size=32\nbinlog_expire_logs_seconds=1209600\n\n/" /etc/my.cnf > /tmp/my.cnf; \
|
||||
cp /tmp/my.cnf /etc/my.cnf; \
|
||||
sed -e "s/skip-networking/#skip-networking/" /etc/my.cnf.d/mariadb-server.cnf > /tmp/mariadb-server.cnf; \
|
||||
cp /tmp/mariadb-server.cnf /etc/my.cnf.d/mariadb-server.cnf; \
|
||||
mysql_install_db --user=mysql
|
||||
RUN mysqld_safe --user=mysql & \
|
||||
sleep 5; \
|
||||
mysql -uroot -hlocalhost --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';"
|
||||
|
||||
RUN echo -e "#!/usr/bin/env sh\n\nmysqld_safe --user=mysql &\n/usr/local/goedge/edge-admin/bin/edge-admin\n" > ${ROOT_DIR}/run.sh; \
|
||||
chmod u+x ${ROOT_DIR}/run.sh
|
||||
|
||||
EXPOSE 7788
|
||||
EXPOSE 8001
|
||||
EXPOSE 3306
|
||||
|
||||
ENTRYPOINT [ "/usr/local/goedge/run.sh" ]
|
||||
5
docker/build.sh
Executable file
5
docker/build.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION=latest
|
||||
|
||||
docker build --no-cache -t goedge/edge-admin:${VERSION} .
|
||||
5
docker/run.sh
Executable file
5
docker/run.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION=latest
|
||||
|
||||
docker run -d -p 7788:7788 -p 8001:8001 -p 3306:3306 --name edge-admin goedge/edge-admin:${VERSION}
|
||||
@@ -1,96 +0,0 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var sharedUserUIConfig *systemconfigs.UserUIConfig = nil
|
||||
|
||||
const (
|
||||
UserUISettingName = "userUIConfig"
|
||||
)
|
||||
|
||||
func LoadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadUserUIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.UserUIConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func UpdateUserUIConfig(uiConfig *systemconfigs.UserUIConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(uiConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: UserUISettingName,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedUserUIConfig = uiConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
if sharedUserUIConfig != nil {
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: UserUISettingName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedUserUIConfig = defaultUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
|
||||
config := &systemconfigs.UserUIConfig{}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[UI_MANAGER]" + err.Error())
|
||||
sharedUserUIConfig = defaultUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
sharedUserUIConfig = config
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
|
||||
func defaultUserUIConfig() *systemconfigs.UserUIConfig {
|
||||
return &systemconfigs.UserUIConfig{
|
||||
ProductName: "GoEdge",
|
||||
UserSystemName: "GoEdge用户系统",
|
||||
ShowOpenSourceInfo: true,
|
||||
ShowVersion: true,
|
||||
ShowFinance: true,
|
||||
BandwidthUnit: systemconfigs.BandwidthUnitBit,
|
||||
ShowBandwidthCharts: true,
|
||||
ShowTrafficCharts: true,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.5.6"
|
||||
Version = "0.5.8"
|
||||
|
||||
APINodeVersion = "0.5.6"
|
||||
APINodeVersion = "0.5.8"
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
@@ -571,41 +571,30 @@ func (this *RPCClient) pickConn() *grpc.ClientConn {
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 检查连接状态
|
||||
if len(this.conns) > 0 {
|
||||
var availableConns = []*grpc.ClientConn{}
|
||||
for _, state := range []connectivity.State{connectivity.Ready, connectivity.Idle, connectivity.Connecting} {
|
||||
var countConns = len(this.conns)
|
||||
if countConns > 0 {
|
||||
if countConns == 1 {
|
||||
return this.conns[0]
|
||||
}
|
||||
for _, state := range []connectivity.State{
|
||||
connectivity.Ready,
|
||||
connectivity.Idle,
|
||||
connectivity.Connecting,
|
||||
connectivity.TransientFailure,
|
||||
} {
|
||||
var availableConns = []*grpc.ClientConn{}
|
||||
for _, conn := range this.conns {
|
||||
if conn.GetState() == state {
|
||||
availableConns = append(availableConns, conn)
|
||||
}
|
||||
}
|
||||
if len(availableConns) > 0 {
|
||||
break
|
||||
return this.randConn(availableConns)
|
||||
}
|
||||
}
|
||||
|
||||
if len(availableConns) > 0 {
|
||||
return availableConns[rands.Int(0, len(availableConns)-1)]
|
||||
}
|
||||
|
||||
// 关闭
|
||||
for _, conn := range this.conns {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// 重新初始化
|
||||
err := this.init()
|
||||
if err != nil {
|
||||
// 错误提示已经在构造对象时打印过,所以这里不再重复打印
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(this.conns) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.conns[rands.Int(0, len(this.conns)-1)]
|
||||
return this.randConn(this.conns)
|
||||
}
|
||||
|
||||
// Close 关闭
|
||||
@@ -639,3 +628,14 @@ func (this *RPCClient) localIPAddrs() []string {
|
||||
}
|
||||
return localIPAddrs
|
||||
}
|
||||
|
||||
func (this *RPCClient) randConn(conns []*grpc.ClientConn) *grpc.ClientConn {
|
||||
var l = len(conns)
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
if l == 1 {
|
||||
return conns[0]
|
||||
}
|
||||
return conns[rands.Int(0, l-1)]
|
||||
}
|
||||
|
||||
55
internal/utils/time.go
Normal file
55
internal/utils/time.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// RangeTimes 计算时间点
|
||||
func RangeTimes(timeFrom string, timeTo string, everyMinutes int32) (result []string, err error) {
|
||||
if everyMinutes <= 0 {
|
||||
return nil, errors.New("invalid 'everyMinutes'")
|
||||
}
|
||||
|
||||
var reg = regexp.MustCompile(`^\d{4}$`)
|
||||
if !reg.MatchString(timeFrom) {
|
||||
return nil, errors.New("invalid timeFrom '" + timeFrom + "'")
|
||||
}
|
||||
if !reg.MatchString(timeTo) {
|
||||
return nil, errors.New("invalid timeTo '" + timeTo + "'")
|
||||
}
|
||||
|
||||
if timeFrom > timeTo {
|
||||
// swap
|
||||
timeFrom, timeTo = timeTo, timeFrom
|
||||
}
|
||||
|
||||
var everyMinutesInt = int(everyMinutes)
|
||||
|
||||
var fromHour = types.Int(timeFrom[:2])
|
||||
var fromMinute = types.Int(timeFrom[2:])
|
||||
var toHour = types.Int(timeTo[:2])
|
||||
var toMinute = types.Int(timeTo[2:])
|
||||
|
||||
if fromMinute%everyMinutesInt == 0 {
|
||||
result = append(result, timeFrom)
|
||||
}
|
||||
|
||||
for {
|
||||
fromMinute += everyMinutesInt
|
||||
if fromMinute > 59 {
|
||||
fromHour += fromMinute / 60
|
||||
fromMinute = fromMinute % 60
|
||||
}
|
||||
if fromHour > toHour || (fromHour == toHour && fromMinute > toMinute) {
|
||||
break
|
||||
}
|
||||
result = append(result, fmt.Sprintf("%02d%02d", fromHour, fromMinute))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -49,7 +49,11 @@ func (this *ParentAction) ErrorText(err string) {
|
||||
}
|
||||
|
||||
func (this *ParentAction) NotFound(name string, itemId int64) {
|
||||
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
|
||||
if itemId > 0 {
|
||||
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
|
||||
} else {
|
||||
this.ErrorPage(errors.New(name + " is not found"))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ParentAction) NewPage(total int64, size ...int64) *Page {
|
||||
@@ -126,11 +130,8 @@ func (this *ParentAction) RPC() *rpc.RPCClient {
|
||||
}
|
||||
|
||||
// AdminContext 获取Context
|
||||
// 每个请求的context都必须是一个新的实例
|
||||
func (this *ParentAction) AdminContext() context.Context {
|
||||
if this.ctx != nil {
|
||||
return this.ctx
|
||||
}
|
||||
|
||||
if this.rpcClient == nil {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
|
||||
@@ -22,7 +22,7 @@ func init() {
|
||||
Post("/options", new(OptionsAction)).
|
||||
|
||||
// AccessKeys
|
||||
Prefix("/admins/accessKeys").
|
||||
Prefix("/admins/accesskeys").
|
||||
Get("", new(accesskeys.IndexAction)).
|
||||
GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
|
||||
Post("/delete", new(accesskeys.DeleteAction)).
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
@@ -40,11 +42,11 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Field("name", params.Name).
|
||||
Require("请输入API节点名称")
|
||||
|
||||
httpConfig := &serverconfigs.HTTPProtocolConfig{}
|
||||
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
|
||||
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
|
||||
// 监听地址
|
||||
listens := []*serverconfigs.NetworkAddressConfig{}
|
||||
var listens = []*serverconfigs.NetworkAddressConfig{}
|
||||
err := json.Unmarshal(params.ListensJSON, &listens)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -64,15 +66,19 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// Rest监听地址
|
||||
restHTTPConfig := &serverconfigs.HTTPProtocolConfig{}
|
||||
restHTTPSConfig := &serverconfigs.HTTPSProtocolConfig{}
|
||||
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
if params.RestIsOn {
|
||||
restListens := []*serverconfigs.NetworkAddressConfig{}
|
||||
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
|
||||
@@ -82,10 +88,35 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 证书
|
||||
certIds := []int64{}
|
||||
var certIds = []int64{}
|
||||
if len(params.CertIdsJSON) > 0 {
|
||||
err = json.Unmarshal(params.CertIdsJSON, &certIds)
|
||||
if err != nil {
|
||||
@@ -97,7 +128,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
this.Fail("请添加至少一个证书")
|
||||
}
|
||||
|
||||
certRefs := []*sslconfigs.SSLCertRef{}
|
||||
var certRefs = []*sslconfigs.SSLCertRef{}
|
||||
for _, certId := range certIds {
|
||||
certRefs = append(certRefs, &sslconfigs.SSLCertRef{
|
||||
IsOn: true,
|
||||
@@ -131,7 +162,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// 访问地址
|
||||
accessAddrs := []*serverconfigs.NetworkAddressConfig{}
|
||||
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
err = json.Unmarshal(params.AccessAddrsJSON, &accessAddrs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
@@ -175,12 +177,16 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
if params.RestIsOn {
|
||||
restListens := []*serverconfigs.NetworkAddressConfig{}
|
||||
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
|
||||
@@ -190,6 +196,31 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 证书
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"time"
|
||||
@@ -300,6 +301,22 @@ func (this *DetailAction) RunGet(params struct {
|
||||
lnAddrs = []string{}
|
||||
}
|
||||
|
||||
// API节点地址
|
||||
var apiNodeAddrStrings = []string{}
|
||||
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
if len(node.ApiNodeAddrsJSON) > 0 {
|
||||
err = json.Unmarshal(node.ApiNodeAddrsJSON, &apiNodeAddrs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, addr := range apiNodeAddrs {
|
||||
if addr.Init() == nil {
|
||||
apiNodeAddrStrings = append(apiNodeAddrStrings, addr.FullAddresses()...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": node.Id,
|
||||
"name": node.Name,
|
||||
@@ -319,6 +336,7 @@ func (this *DetailAction) RunGet(params struct {
|
||||
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
|
||||
"lnAddrs": lnAddrs,
|
||||
"enableIPLists": node.EnableIPLists,
|
||||
"apiNodeAddrs": apiNodeAddrStrings,
|
||||
|
||||
"status": maps.Map{
|
||||
"isActive": status.IsActive,
|
||||
@@ -338,6 +356,8 @@ func (this *DetailAction) RunGet(params struct {
|
||||
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
|
||||
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
|
||||
"exePath": status.ExePath,
|
||||
"apiSuccessPercent": status.APISuccessPercent,
|
||||
"apiAvgCostSeconds": status.APIAvgCostSeconds,
|
||||
},
|
||||
|
||||
"group": groupMap,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
|
||||
"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"
|
||||
@@ -57,9 +58,19 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
}
|
||||
|
||||
var diskSubDirs = []*serverconfigs.CacheDir{}
|
||||
if len(node.CacheDiskSubDirsJSON) > 0 {
|
||||
err = json.Unmarshal(node.CacheDiskSubDirsJSON, &diskSubDirs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var nodeMap = this.Data["node"].(maps.Map)
|
||||
nodeMap["maxCacheDiskCapacity"] = maxCacheDiskCapacity
|
||||
nodeMap["cacheDiskDir"] = node.CacheDiskDir
|
||||
nodeMap["cacheDiskSubDirs"] = diskSubDirs
|
||||
nodeMap["maxCacheMemoryCapacity"] = maxCacheMemoryCapacity
|
||||
|
||||
this.Show()
|
||||
@@ -69,6 +80,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
MaxCacheDiskCapacityJSON []byte
|
||||
CacheDiskDir string
|
||||
CacheDiskSubDirsJSON []byte
|
||||
MaxCacheMemoryCapacityJSON []byte
|
||||
|
||||
Must *actions.Must
|
||||
@@ -105,10 +117,20 @@ func (this *IndexAction) RunPost(params struct {
|
||||
}
|
||||
}
|
||||
|
||||
if len(params.CacheDiskSubDirsJSON) > 0 {
|
||||
var cacheSubDirs = []*serverconfigs.CacheDir{}
|
||||
err := json.Unmarshal(params.CacheDiskSubDirsJSON, &cacheSubDirs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err := this.RPC().NodeRPC().UpdateNodeCache(this.AdminContext(), &pb.UpdateNodeCacheRequest{
|
||||
NodeId: params.NodeId,
|
||||
MaxCacheDiskCapacity: pbMaxCacheDiskCapacity,
|
||||
CacheDiskDir: params.CacheDiskDir,
|
||||
CacheDiskSubDirsJSON: params.CacheDiskSubDirsJSON,
|
||||
MaxCacheMemoryCapacity: pbMaxCacheMemoryCapacity,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
@@ -50,6 +51,22 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["dnsResolverConfig"] = dnsResolverConfig
|
||||
|
||||
// API相关
|
||||
apiConfigResp, err := this.RPC().NodeRPC().FindNodeAPIConfig(this.AdminContext(), &pb.FindNodeAPIConfigRequest{NodeId: params.NodeId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
if len(apiConfigResp.ApiNodeAddrsJSON) > 0 {
|
||||
err = json.Unmarshal(apiConfigResp.ApiNodeAddrsJSON, &apiNodeAddrs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Data["apiNodeAddrs"] = apiNodeAddrs
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -59,6 +76,8 @@ func (this *IndexAction) RunPost(params struct {
|
||||
|
||||
DnsResolverJSON []byte
|
||||
|
||||
ApiNodeAddrsJSON []byte
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -68,6 +87,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
this.Fail("CPU线程数不能小于0")
|
||||
}
|
||||
|
||||
// 系统设置
|
||||
_, err := this.RPC().NodeRPC().UpdateNodeSystem(this.AdminContext(), &pb.UpdateNodeSystemRequest{
|
||||
NodeId: params.NodeId,
|
||||
MaxCPU: params.MaxCPU,
|
||||
@@ -77,6 +97,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
// DNS解析设置
|
||||
var dnsResolverConfig = nodeconfigs.DefaultDNSResolverConfig()
|
||||
err = json.Unmarshal(params.DnsResolverJSON, dnsResolverConfig)
|
||||
if err != nil {
|
||||
@@ -98,5 +119,28 @@ func (this *IndexAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
// API节点设置
|
||||
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
if len(params.ApiNodeAddrsJSON) > 0 {
|
||||
err = json.Unmarshal(params.ApiNodeAddrsJSON, &apiNodeAddrs)
|
||||
if err != nil {
|
||||
this.Fail("API节点地址校验错误:" + err.Error())
|
||||
}
|
||||
for _, addr := range apiNodeAddrs {
|
||||
err = addr.Init()
|
||||
if err != nil {
|
||||
this.Fail("API节点地址校验错误:" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = this.RPC().NodeRPC().UpdateNodeAPIConfig(this.AdminContext(), &pb.UpdateNodeAPIConfigRequest{
|
||||
NodeId: params.NodeId,
|
||||
ApiNodeAddrsJSON: params.ApiNodeAddrsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
AutoOpenPorts bool
|
||||
ClockAutoSync bool
|
||||
ClockServer string
|
||||
ClockCheckChrony bool
|
||||
AutoRemoteStart bool
|
||||
AutoInstallNftables bool
|
||||
|
||||
@@ -154,6 +155,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
var clockConfig = nodeconfigs.DefaultClockConfig()
|
||||
clockConfig.AutoSync = params.ClockAutoSync
|
||||
clockConfig.Server = params.ClockServer
|
||||
clockConfig.CheckChrony = params.ClockCheckChrony
|
||||
clockConfigJSON, err := json.Marshal(clockConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -60,7 +60,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
|
||||
var tabbar = actionutils.NewTabbar()
|
||||
tabbar.Add("集群列表", "", "/clusters", "", false)
|
||||
if teaconst.IsPlus {
|
||||
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "board", selectedTabbar == "board")
|
||||
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "chart line area", selectedTabbar == "board")
|
||||
}
|
||||
tabbar.Add("集群节点", "", "/clusters/cluster/nodes?clusterId="+clusterIdString, "server", selectedTabbar == "node")
|
||||
tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting")
|
||||
|
||||
@@ -71,7 +71,10 @@ func ValidateRecordName(name string) bool {
|
||||
}
|
||||
|
||||
pieces := strings.Split(name, ".")
|
||||
for _, piece := range pieces {
|
||||
for index, piece := range pieces {
|
||||
if index == 0 && piece == "*" {
|
||||
continue
|
||||
}
|
||||
if piece == "-" ||
|
||||
strings.HasPrefix(piece, "-") ||
|
||||
strings.HasSuffix(piece, "-") ||
|
||||
|
||||
@@ -32,6 +32,7 @@ func (this *CreateRulePopupAction) RunGet(params struct {
|
||||
"params": checkpoint.Params,
|
||||
"options": checkpoint.Options,
|
||||
"isComposed": checkpoint.IsComposed,
|
||||
"dataType": checkpoint.DataType,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,10 +39,11 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
KeepArgs bool
|
||||
|
||||
// 域名
|
||||
DomainsAll bool
|
||||
DomainsBeforeJSON []byte
|
||||
DomainAfter string
|
||||
DomainAfterScheme string
|
||||
DomainsAll bool
|
||||
DomainsBeforeJSON []byte
|
||||
DomainBeforeIgnorePorts bool
|
||||
DomainAfter string
|
||||
DomainAfterScheme string
|
||||
|
||||
// 端口
|
||||
PortsAll bool
|
||||
@@ -128,6 +129,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
}
|
||||
config.DomainBeforeIgnorePorts = params.DomainBeforeIgnorePorts
|
||||
if len(params.DomainAfter) == 0 {
|
||||
this.FailField("domainAfter", "请输入跳转后域名")
|
||||
return
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
package userui
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"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"
|
||||
"io"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
config, err := configloaders.LoadUserUIConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["config"] = config
|
||||
|
||||
// 时区
|
||||
this.Data["timeZoneGroups"] = nodeconfigs.FindAllTimeZoneGroups()
|
||||
this.Data["timeZoneLocations"] = nodeconfigs.FindAllTimeZoneLocations()
|
||||
|
||||
if len(config.TimeZone) == 0 {
|
||||
config.TimeZone = nodeconfigs.DefaultTimeZoneLocation
|
||||
}
|
||||
this.Data["timeZoneLocation"] = nodeconfigs.FindTimeZoneLocation(config.TimeZone)
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
ProductName string
|
||||
UserSystemName string
|
||||
ShowOpenSourceInfo bool
|
||||
ShowVersion bool
|
||||
Version string
|
||||
ShowFinance bool
|
||||
FaviconFile *actions.File
|
||||
LogoFile *actions.File
|
||||
TimeZone string
|
||||
|
||||
ShowTrafficCharts bool
|
||||
ShowBandwidthCharts bool
|
||||
BandwidthUnit string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("productName", params.ProductName).
|
||||
Require("请输入产品名称").
|
||||
Field("userSystemName", params.UserSystemName).
|
||||
Require("请输入管理员系统名称")
|
||||
|
||||
config, err := configloaders.LoadUserUIConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
config.ProductName = params.ProductName
|
||||
config.UserSystemName = params.UserSystemName
|
||||
config.ShowOpenSourceInfo = params.ShowOpenSourceInfo
|
||||
config.ShowVersion = params.ShowVersion
|
||||
config.Version = params.Version
|
||||
config.ShowFinance = params.ShowFinance
|
||||
config.ShowTrafficCharts = params.ShowTrafficCharts
|
||||
config.ShowBandwidthCharts = params.ShowBandwidthCharts
|
||||
config.BandwidthUnit = params.BandwidthUnit
|
||||
config.TimeZone = params.TimeZone
|
||||
|
||||
// 上传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)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package userui
|
||||
|
||||
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("userUI")).
|
||||
Prefix("/settings/user-ui").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -31,10 +32,21 @@ func (this *DetectDBAction) RunPost(params struct{}) {
|
||||
localPort = "3306"
|
||||
|
||||
var username = "root"
|
||||
for _, pass := range []string{"", "123456", "654321", "Aa_123456"} {
|
||||
var passwords = []string{"", "123456", "654321", "Aa_123456", "111111"}
|
||||
|
||||
// 使用 foolish-mysql 安装的MySQL
|
||||
localGeneratedPasswordData, err := os.ReadFile("/usr/local/mysql/generated-password.txt")
|
||||
if err == nil {
|
||||
var localGeneratedPassword = strings.TrimSpace(string(localGeneratedPasswordData))
|
||||
if len(localGeneratedPassword) > 0 {
|
||||
passwords = append(passwords, localGeneratedPassword)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pass := range passwords {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges11111",
|
||||
Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges",
|
||||
Prefix: "",
|
||||
})
|
||||
if err == nil {
|
||||
|
||||
@@ -22,7 +22,7 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
currentHost = host
|
||||
}
|
||||
}
|
||||
if net.ParseIP(currentHost) != nil {
|
||||
if net.ParseIP(currentHost) != nil && currentHost != "localhost" && currentHost != "127.0.0.1" {
|
||||
this.Data["currentHost"] = currentHost
|
||||
} else {
|
||||
this.Data["currentHost"] = ""
|
||||
|
||||
@@ -17,13 +17,17 @@ import (
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type InstallAction struct {
|
||||
actionutils.ParentAction
|
||||
|
||||
apiSetupFinished bool
|
||||
}
|
||||
|
||||
func (this *InstallAction) RunPost(params struct {
|
||||
@@ -40,7 +44,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
|
||||
// API节点配置
|
||||
currentStatusText = "正在检查API节点配置"
|
||||
apiNodeMap := maps.Map{}
|
||||
var apiNodeMap = maps.Map{}
|
||||
err := json.Unmarshal(params.ApiNodeJSON, &apiNodeMap)
|
||||
if err != nil {
|
||||
this.Fail("API节点配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
|
||||
@@ -48,7 +52,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
|
||||
// 数据库
|
||||
currentStatusText = "正在检查数据库配置"
|
||||
dbMap := maps.Map{}
|
||||
var dbMap = maps.Map{}
|
||||
err = json.Unmarshal(params.DbJSON, &dbMap)
|
||||
if err != nil {
|
||||
this.Fail("数据库配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
|
||||
@@ -56,14 +60,14 @@ func (this *InstallAction) RunPost(params struct {
|
||||
|
||||
// 管理员
|
||||
currentStatusText = "正在检查管理员配置"
|
||||
adminMap := maps.Map{}
|
||||
var adminMap = maps.Map{}
|
||||
err = json.Unmarshal(params.AdminJSON, &adminMap)
|
||||
if err != nil {
|
||||
this.Fail("管理员数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
|
||||
}
|
||||
|
||||
// 安装API节点
|
||||
mode := apiNodeMap.GetString("mode")
|
||||
var mode = apiNodeMap.GetString("mode")
|
||||
if mode == "new" {
|
||||
currentStatusText = "准备启动新API节点"
|
||||
|
||||
@@ -74,7 +78,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
// ...
|
||||
|
||||
// 检查环境
|
||||
apiNodeDir := Tea.Root + "/edge-api"
|
||||
var apiNodeDir = Tea.Root + "/edge-api"
|
||||
for _, dir := range []string{"edge-api", "edge-api/configs", "edge-api/bin"} {
|
||||
apiNodeDir := Tea.Root + "/" + dir
|
||||
_, err = os.Stat(apiNodeDir)
|
||||
@@ -87,7 +91,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// 保存数据库配置
|
||||
dsn := dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + configutils.QuoteIP(dbMap.GetString("host")) + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s"
|
||||
var dsn = dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + configutils.QuoteIP(dbMap.GetString("host")) + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s"
|
||||
dbConfig := &dbs.Config{
|
||||
DBs: map[string]*dbs.DBConfig{
|
||||
"prod": {
|
||||
@@ -108,7 +112,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
|
||||
// 生成备份文件
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
backupDirs := []string{"/etc/edge-api"}
|
||||
var backupDirs = []string{"/etc/edge-api"}
|
||||
if len(homeDir) > 0 {
|
||||
backupDirs = append(backupDirs, homeDir+"/.edge-api")
|
||||
}
|
||||
@@ -151,15 +155,21 @@ func (this *InstallAction) RunPost(params struct {
|
||||
var resultMap = maps.Map{}
|
||||
logs.Println("[INSTALL]setup edge-api")
|
||||
{
|
||||
cmd := exec.Command(apiNodeDir+"/bin/edge-api", "setup", "-api-node-protocol=http", "-api-node-host=\""+apiNodeMap.GetString("newHost")+"\"", "-api-node-port=\""+apiNodeMap.GetString("newPort")+"\"")
|
||||
output := bytes.NewBuffer([]byte{})
|
||||
this.apiSetupFinished = false
|
||||
var cmd = exec.Command(apiNodeDir+"/bin/edge-api", "setup", "-api-node-protocol=http", "-api-node-host=\""+apiNodeMap.GetString("newHost")+"\"", "-api-node-port=\""+apiNodeMap.GetString("newPort")+"\"")
|
||||
var output = bytes.NewBuffer([]byte{})
|
||||
cmd.Stdout = output
|
||||
|
||||
// 试图读取执行日志
|
||||
go this.startReadingAPIInstallLog()
|
||||
|
||||
err = cmd.Run()
|
||||
this.apiSetupFinished = true
|
||||
if err != nil {
|
||||
this.Fail("安装失败:" + err.Error())
|
||||
}
|
||||
|
||||
resultData := output.Bytes()
|
||||
var resultData = output.Bytes()
|
||||
err = json.Unmarshal(resultData, &resultMap)
|
||||
if err != nil {
|
||||
this.Fail("安装节点时返回数据错误:" + err.Error() + "(" + string(resultData) + ")")
|
||||
@@ -175,7 +185,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
// 关闭正在运行的API节点,防止冲突
|
||||
logs.Println("[INSTALL]stop edge-api")
|
||||
{
|
||||
cmd := exec.Command(apiNodeDir+"/bin/edge-api", "stop")
|
||||
var cmd = exec.Command(apiNodeDir+"/bin/edge-api", "stop")
|
||||
_ = cmd.Run()
|
||||
}
|
||||
|
||||
@@ -183,7 +193,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
currentStatusText = "正在启动API节点"
|
||||
logs.Println("[INSTALL]start edge-api")
|
||||
{
|
||||
cmd := exec.Command(apiNodeDir + "/bin/edge-api")
|
||||
var cmd = exec.Command(apiNodeDir + "/bin/edge-api")
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
this.Fail("API节点启动失败:" + err.Error())
|
||||
@@ -220,7 +230,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// 写入API节点配置,完成安装
|
||||
apiConfig := &configs.APIConfig{
|
||||
var apiConfig = &configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
@@ -283,7 +293,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
this.Success()
|
||||
} else if mode == "old" {
|
||||
// 构造RPC
|
||||
apiConfig := &configs.APIConfig{
|
||||
var apiConfig = &configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
@@ -303,7 +313,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
}()
|
||||
|
||||
// 设置管理员
|
||||
ctx := client.APIContext(0)
|
||||
var ctx = client.APIContext(0)
|
||||
_, err = client.AdminRPC().CreateOrUpdateAdmin(ctx, &pb.CreateOrUpdateAdminRequest{
|
||||
Username: adminMap.GetString("username"),
|
||||
Password: adminMap.GetString("password"),
|
||||
@@ -344,3 +354,73 @@ func (this *InstallAction) RunPost(params struct {
|
||||
this.Fail("错误的API节点模式:'" + mode + "'")
|
||||
}
|
||||
}
|
||||
|
||||
// 读取API安装时的日志,以便于显示当前正在执行的任务
|
||||
func (this *InstallAction) startReadingAPIInstallLog() {
|
||||
var tmpDir = os.TempDir()
|
||||
if len(tmpDir) == 0 {
|
||||
return
|
||||
}
|
||||
var logFile = tmpDir + "/edge-install.log"
|
||||
|
||||
var logFp *os.File
|
||||
var err error
|
||||
|
||||
// 尝试5秒钟
|
||||
for i := 0; i < 10; i++ {
|
||||
logFp, err = os.Open(logFile)
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if this.apiSetupFinished {
|
||||
_ = logFp.Close()
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
_ = logFp.Close()
|
||||
}()
|
||||
|
||||
var ticker = time.NewTicker(1 * time.Second)
|
||||
var logBuf = make([]byte, 256)
|
||||
for range ticker.C {
|
||||
if this.apiSetupFinished {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = logFp.Seek(-256, io.SeekEnd)
|
||||
if err != nil {
|
||||
currentStatusText = ""
|
||||
return
|
||||
}
|
||||
|
||||
n, err := logFp.Read(logBuf)
|
||||
if err != nil {
|
||||
currentStatusText = ""
|
||||
return
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
var logData = string(logBuf[:n])
|
||||
var lines = strings.Split(logData, "\n")
|
||||
if len(lines) >= 3 {
|
||||
var line = strings.TrimSpace(lines[len(lines)-2])
|
||||
if len(line) > 0 {
|
||||
if !this.apiSetupFinished {
|
||||
currentStatusText = "正在执行 " + line + " ..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func init() {
|
||||
Get("/otpQrcode", new(OtpQrcodeAction)).
|
||||
|
||||
// AccessKeys
|
||||
Prefix("/users/accessKeys").
|
||||
Prefix("/users/accesskeys").
|
||||
Get("", new(accesskeys.IndexAction)).
|
||||
GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
|
||||
Post("/delete", new(accesskeys.DeleteAction)).
|
||||
|
||||
@@ -124,7 +124,6 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ui"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/updates"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/upgrade"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-ui"
|
||||
|
||||
// 恢复
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/recover"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2392,9 +2392,9 @@ Vue.component("ns-access-log-box", {
|
||||
if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
} else if (accessLog.nsRecordId == null || accessLog.nsRecordId == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
|
||||
// 没有找到记录的不需要高亮显示,防止管理员看到红色就心理恐慌
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -3435,7 +3435,7 @@ Vue.component("http-stat-config-box", {
|
||||
<prior-checkbox :v-config="stat" v-if="vIsLocation || vIsGroup" ></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || stat.isPrior">
|
||||
<tr>
|
||||
<td class="title">是否开启统计</td>
|
||||
<td class="title">启用统计</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="stat.isOn"/>
|
||||
@@ -4511,6 +4511,7 @@ Vue.component("http-host-redirect-box", {
|
||||
<div style="margin-top: 0.4em">
|
||||
<grey-label><strong>域名跳转</strong></grey-label>
|
||||
<grey-label v-if="redirect.domainAfterScheme != null && redirect.domainAfterScheme.length > 0">{{redirect.domainAfterScheme}}</grey-label>
|
||||
<grey-label v-if="redirect.domainBeforeIgnorePorts">忽略端口</grey-label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="redirect.type == 'port'">
|
||||
@@ -6679,7 +6680,7 @@ Vue.component("http-websocket-box", {
|
||||
<prior-checkbox :v-config="websocketRef" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="((!vIsLocation && !vIsGroup) || websocketRef.isPrior)">
|
||||
<tr>
|
||||
<td class="title">启用配置</td>
|
||||
<td class="title">启用Websocket</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="websocketRef.isOn"/>
|
||||
@@ -7275,7 +7276,7 @@ Vue.component("domains-box", {
|
||||
<a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment" v-if="supportWildcard">支持普通域名(<code-label>example.com</code-label>)、泛域名(<code-label>*.example.com</code-label>)、域名后缀(以点号开头,如<code-label>.example.com</code-label>)和正则表达式(以波浪号开头,如<code-label>~.*.example.com</code-label>)。</p>
|
||||
<p class="comment" v-if="supportWildcard">支持普通域名(<code-label>example.com</code-label>)、泛域名(<code-label>*.example.com</code-label>)<span v-if="vSupportWildcard == undefined">、域名后缀(以点号开头,如<code-label>.example.com</code-label>)和正则表达式(以波浪号开头,如<code-label>~.*.example.com</code-label>)</span>;如果域名后有端口,请加上端口号。</p>
|
||||
<p class="comment" v-if="!supportWildcard">只支持普通域名(<code-label>example.com</code-label>、<code-label>www.example.com</code-label>)。</p>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
@@ -7352,14 +7353,14 @@ Vue.component("http-referers-config-box", {
|
||||
<tr>
|
||||
<td>允许的来源域名</td>
|
||||
<td>
|
||||
<values-box :values="config.allowDomains" @change="changeAllowDomains"></values-box>
|
||||
<domains-box :v-domains="config.allowDomains" @change="changeAllowDomains">></domains-box>
|
||||
<p class="comment">允许的其他来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>禁止的来源域名</td>
|
||||
<td>
|
||||
<values-box :values="config.denyDomains" @change="changeDenyDomains"></values-box>
|
||||
<domains-box :v-domains="config.denyDomains" @change="changeDenyDomains"></domains-box>
|
||||
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -9106,7 +9107,7 @@ Vue.component("http-compression-config-box", {
|
||||
brotliRef: null,
|
||||
minLength: {count: 0, "unit": "kb"},
|
||||
maxLength: {count: 0, "unit": "kb"},
|
||||
mimeTypes: ["text/*", "application/*", "font/*"],
|
||||
mimeTypes: ["text/*", "application/javascript", "application/json", "application/atom+xml", "application/rss+xml", "application/xhtml+xml", "font/*", "image/svg+xml"],
|
||||
extensions: [".js", ".json", ".html", ".htm", ".xml", ".css", ".woff2", ".txt"],
|
||||
conds: null
|
||||
}
|
||||
@@ -9237,7 +9238,7 @@ Vue.component("http-compression-config-box", {
|
||||
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用内容压缩</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="config.isOn"/>
|
||||
@@ -9432,7 +9433,7 @@ Vue.component("http-charsets-box", {
|
||||
<prior-checkbox :v-config="charsetConfig" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || charsetConfig.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用字符编码</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="charsetConfig.isOn"/>
|
||||
@@ -10063,7 +10064,7 @@ Vue.component("http-access-log-config-box", {
|
||||
<prior-checkbox :v-config="accessLog" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || accessLog.isPrior">
|
||||
<tr>
|
||||
<td class="title">开启访问日志</td>
|
||||
<td class="title">启用访问日志</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="accessLog.isOn"/>
|
||||
@@ -11180,7 +11181,7 @@ Vue.component("http-access-log-search-box", {
|
||||
<a class="ui label basic" :class="{disabled: keyword.length == 0}" @click.prevent="cleanKeyword"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码:status:200<br/>状态码范围:status:500-504<br/>查询IP:ip:192.168.1.100<br/>查询URL:https://goedge.cn/docs<br/>查询路径部分:requestPath:/hello/world<br/>查询协议版本:proto:HTTP/1.1<br/>协议:scheme:http"></tip-icon></div>
|
||||
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码:status:200<br/>状态码范围:status:500-504<br/>查询IP:ip:192.168.1.100<br/>查询URL:https://goedge.cn/docs<br/>查询路径部分:requestPath:/hello/world<br/>查询协议版本:proto:HTTP/1.1<br/>协议:scheme:http<br/>请求方法:method:POST"></tip-icon></div>
|
||||
</div>
|
||||
<div class="ui fields inline" style="margin-top: 0.5em">
|
||||
<div class="ui field">
|
||||
@@ -11446,7 +11447,7 @@ Vue.component("http-web-root-box", {
|
||||
<prior-checkbox :v-config="rootConfig" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || rootConfig.isPrior">
|
||||
<tr>
|
||||
<td class="title">是否开启静态资源分发</td>
|
||||
<td class="title">启用静态资源分发</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="rootConfig.isOn"/>
|
||||
@@ -11585,7 +11586,7 @@ Vue.component("http-webp-config-box", {
|
||||
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用WebP压缩</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="config.isOn"/>
|
||||
@@ -11848,6 +11849,7 @@ Vue.component("http-firewall-rules-box", {
|
||||
window.UPDATING_RULE = null
|
||||
let that = this
|
||||
teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
|
||||
height: "30em",
|
||||
callback: function (resp) {
|
||||
that.rules.push(resp.data.rule)
|
||||
}
|
||||
@@ -11857,6 +11859,7 @@ Vue.component("http-firewall-rules-box", {
|
||||
window.UPDATING_RULE = rule
|
||||
let that = this
|
||||
teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
|
||||
height: "30em",
|
||||
callback: function (resp) {
|
||||
Vue.set(that.rules, index, resp.data.rule)
|
||||
}
|
||||
@@ -13537,7 +13540,7 @@ Vue.component("firewall-syn-flood-config-box", {
|
||||
<td class="title">启用</td>
|
||||
<td>
|
||||
<checkbox v-model="config.isOn"></checkbox>
|
||||
<p class="comment">启用后,WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用Firewalld。</p>
|
||||
<p class="comment">启用后,WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用nftables或Firewalld。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -14636,8 +14639,6 @@ Vue.component("datetime-input", {
|
||||
},
|
||||
methods: {
|
||||
change: function () {
|
||||
let date = new Date()
|
||||
|
||||
// day
|
||||
if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)) {
|
||||
this.hasDayError = true
|
||||
@@ -14645,21 +14646,18 @@ Vue.component("datetime-input", {
|
||||
}
|
||||
let pieces = this.day.split("-")
|
||||
let year = parseInt(pieces[0])
|
||||
date.setFullYear(year)
|
||||
|
||||
let month = parseInt(pieces[1])
|
||||
if (month < 1 || month > 12) {
|
||||
this.hasDayError = true
|
||||
return
|
||||
}
|
||||
date.setMonth(month - 1)
|
||||
|
||||
let day = parseInt(pieces[2])
|
||||
if (day < 1 || day > 32) {
|
||||
this.hasDayError = true
|
||||
return
|
||||
}
|
||||
date.setDate(day)
|
||||
|
||||
this.hasDayError = false
|
||||
|
||||
@@ -14678,7 +14676,6 @@ Vue.component("datetime-input", {
|
||||
return
|
||||
}
|
||||
this.hasHourError = false
|
||||
date.setHours(hour)
|
||||
|
||||
// minute
|
||||
if (!/^\d+$/.test(this.minute)) {
|
||||
@@ -14695,7 +14692,6 @@ Vue.component("datetime-input", {
|
||||
return
|
||||
}
|
||||
this.hasMinuteError = false
|
||||
date.setMinutes(minute)
|
||||
|
||||
// second
|
||||
if (!/^\d+$/.test(this.second)) {
|
||||
@@ -14712,8 +14708,8 @@ Vue.component("datetime-input", {
|
||||
return
|
||||
}
|
||||
this.hasSecondError = false
|
||||
date.setSeconds(second)
|
||||
|
||||
let date = new Date(year, month - 1, day, hour, minute, second)
|
||||
this.timestamp = Math.floor(date.getTime() / 1000)
|
||||
},
|
||||
leadingZero: function (s, l) {
|
||||
@@ -15703,7 +15699,7 @@ Vue.component("checkbox", {
|
||||
this.newValue = ""
|
||||
},
|
||||
isChecked: function () {
|
||||
return this.newValue == this.elementValue
|
||||
return (typeof (this.newValue) == "boolean" && this.newValue) || this.newValue == this.elementValue
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -16340,6 +16336,13 @@ Vue.component("datepicker", {
|
||||
watch: {
|
||||
value: function (v) {
|
||||
this.day = v
|
||||
|
||||
let picker = this.$refs.dayInput.picker
|
||||
if (picker != null) {
|
||||
if (v != null && /^\d+-\d+-\d+$/.test(v)) {
|
||||
picker.setDate(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -16532,6 +16535,86 @@ Vue.component("finance-user-selector", {
|
||||
</div>`
|
||||
})
|
||||
|
||||
Vue.component("node-cache-disk-dirs-box", {
|
||||
props: ["value", "name"],
|
||||
data: function () {
|
||||
let dirs = this.value
|
||||
if (dirs == null) {
|
||||
dirs = []
|
||||
}
|
||||
return {
|
||||
dirs: dirs,
|
||||
|
||||
isEditing: false,
|
||||
isAdding: false,
|
||||
|
||||
addingPath: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.addingPath.focus()
|
||||
}, 100)
|
||||
},
|
||||
confirm: function () {
|
||||
let addingPath = this.addingPath.trim()
|
||||
if (addingPath.length == 0) {
|
||||
let that = this
|
||||
teaweb.warn("请输入要添加的缓存目录", function () {
|
||||
that.$refs.addingPath.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
if (addingPath[0] != "/") {
|
||||
addingPath = "/" + addingPath
|
||||
}
|
||||
this.dirs.push({
|
||||
path: addingPath
|
||||
})
|
||||
this.cancel()
|
||||
},
|
||||
cancel: function () {
|
||||
this.addingPath = ""
|
||||
this.isAdding = false
|
||||
this.isEditing = false
|
||||
},
|
||||
remove: function (index) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除此目录吗?", function () {
|
||||
that.dirs.$remove(index)
|
||||
})
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" :name="name" :value="JSON.stringify(dirs)"/>
|
||||
<div style="margin-bottom: 0.3em">
|
||||
<span class="ui label small basic" v-for="(dir, index) in dirs">
|
||||
<i class="icon folder"></i>{{dir.path}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 添加 -->
|
||||
<div v-if="isAdding">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" style="width: 30em" v-model="addingPath" @keyup.enter="confirm()" @keypress.enter.prevent="1" @keydown.esc="cancel()" ref="addingPath" placeholder="新的缓存目录,比如 /mnt/cache"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button small" type="button" @click.prevent="confirm">确定</button>
|
||||
<a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding">
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
|
||||
// 节点登录推荐端口
|
||||
Vue.component("node-login-suggest-ports", {
|
||||
data: function () {
|
||||
@@ -17819,7 +17902,7 @@ window.REQUEST_COND_COMPONENTS = [{"type":"url-extension","name":"URL扩展名",
|
||||
|
||||
window.REQUEST_COND_OPERATORS = [{"description":"判断是否正则表达式匹配","name":"正则表达式匹配","op":"regexp"},{"description":"判断是否正则表达式不匹配","name":"正则表达式不匹配","op":"not regexp"},{"description":"使用字符串对比参数值是否相等于某个值","name":"字符串等于","op":"eq"},{"description":"参数值包含某个前缀","name":"字符串前缀","op":"prefix"},{"description":"参数值包含某个后缀","name":"字符串后缀","op":"suffix"},{"description":"参数值包含另外一个字符串","name":"字符串包含","op":"contains"},{"description":"参数值不包含另外一个字符串","name":"字符串不包含","op":"not contains"},{"description":"使用字符串对比参数值是否不相等于某个值","name":"字符串不等于","op":"not"},{"description":"判断参数值在某个列表中","name":"在列表中","op":"in"},{"description":"判断参数值不在某个列表中","name":"不在列表中","op":"not in"},{"description":"判断小写的扩展名(不带点)在某个列表中","name":"扩展名","op":"file ext"},{"description":"判断MimeType在某个列表中,支持类似于image/*的语法","name":"MimeType","op":"mime type"},{"description":"判断版本号在某个范围内,格式为version1,version2","name":"版本号范围","op":"version range"},{"description":"将参数转换为整数数字后进行对比","name":"整数等于","op":"eq int"},{"description":"将参数转换为可以有小数的浮点数字进行对比","name":"浮点数等于","op":"eq float"},{"description":"将参数转换为数字进行对比","name":"数字大于","op":"gt"},{"description":"将参数转换为数字进行对比","name":"数字大于等于","op":"gte"},{"description":"将参数转换为数字进行对比","name":"数字小于","op":"lt"},{"description":"将参数转换为数字进行对比","name":"数字小于等于","op":"lte"},{"description":"对整数参数值取模,除数为10,对比值为余数","name":"整数取模10","op":"mod 10"},{"description":"对整数参数值取模,除数为100,对比值为余数","name":"整数取模100","op":"mod 100"},{"description":"对整数参数值取模,对比值格式为:除数,余数,比如10,1","name":"整数取模","op":"mod"},{"description":"将参数转换为IP进行对比","name":"IP等于","op":"eq ip"},{"description":"将参数转换为IP进行对比","name":"IP大于","op":"gt ip"},{"description":"将参数转换为IP进行对比","name":"IP大于等于","op":"gte ip"},{"description":"将参数转换为IP进行对比","name":"IP小于","op":"lt ip"},{"description":"将参数转换为IP进行对比","name":"IP小于等于","op":"lte ip"},{"description":"IP在某个范围之内,范围格式可以是英文逗号分隔的ip1,ip2,或者CIDR格式的ip/bits","name":"IP范围","op":"ip range"},{"description":"对IP参数值取模,除数为10,对比值为余数","name":"IP取模10","op":"ip mod 10"},{"description":"对IP参数值取模,除数为100,对比值为余数","name":"IP取模100","op":"ip mod 100"},{"description":"对IP参数值取模,对比值格式为:除数,余数,比如10,1","name":"IP取模","op":"ip mod"},{"description":"判断参数值解析后的文件是否存在","name":"文件存在","op":"file exist"},{"description":"判断参数值解析后的文件是否不存在","name":"文件不存在","op":"file not exist"}]
|
||||
|
||||
window.REQUEST_VARIABLES = [{"code":"${edgeVersion}","description":"","name":"边缘节点版本"},{"code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适合前端有别的反向代理服务时使用,存在伪造的风险","name":"客户端地址(IP)"},{"code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","name":"客户端地址(IP)"},{"code":"${remotePort}","description":"","name":"客户端端口"},{"code":"${remoteUser}","description":"","name":"客户端用户名"},{"code":"${requestURI}","description":"比如/hello?name=lily","name":"请求URI"},{"code":"${requestPath}","description":"比如/hello","name":"请求路径(不包括参数)"},{"code":"${requestURL}","description":"比如https://example.com/hello?name=lily","name":"完整的请求URL"},{"code":"${requestLength}","description":"","name":"请求内容长度"},{"code":"${requestMethod}","description":"比如GET、POST","name":"请求方法"},{"code":"${requestFilename}","description":"","name":"请求文件路径"},{"code":"${scheme}","description":"","name":"请求协议,http或https"},{"code":"${proto}","description:":"类似于HTTP/1.0","name":"包含版本的HTTP请求协议"},{"code":"${timeISO8601}","description":"比如2018-07-16T23:52:24.839+08:00","name":"ISO 8601格式的时间"},{"code":"${timeLocal}","description":"比如17/Jul/2018:09:52:24 +0800","name":"本地时间"},{"code":"${msec}","description":"比如1531756823.054","name":"带有毫秒的时间"},{"code":"${timestamp}","description":"","name":"unix时间戳,单位为秒"},{"code":"${host}","description":"","name":"主机名"},{"code":"${serverName}","description":"","name":"接收请求的服务器名"},{"code":"${serverPort}","description":"","name":"接收请求的服务器端口"},{"code":"${referer}","description":"","name":"请求来源URL"},{"code":"${referer.host}","description":"","name":"请求来源URL域名"},{"code":"${userAgent}","description":"","name":"客户端信息"},{"code":"${contentType}","description":"","name":"请求头部的Content-Type"},{"code":"${cookies}","description":"","name":"所有cookie组合字符串"},{"code":"${cookie.NAME}","description":"","name":"单个cookie值"},{"code":"${isArgs}","description":"如果URL有参数,则值为`?`;否则,则值为空","name":"问号(?)标记"},{"code":"${args}","description":"","name":"所有参数组合字符串"},{"code":"${arg.NAME}","description":"","name":"单个参数值"},{"code":"${headers}","description":"","name":"所有Header信息组合字符串"},{"code":"${header.NAME}","description":"","name":"单个Header值"},{"code":"${geo.country.name}","description":"","name":"国家/地区名称"},{"code":"${geo.country.id}","description":"","name":"国家/地区ID"},{"code":"${geo.province.name}","description":"目前只包含中国省份","name":"省份名称"},{"code":"${geo.province.id}","description":"目前只包含中国省份","name":"省份ID"},{"code":"${geo.city.name}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${geo.city.id}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${isp.name}","description":"","name":"ISP服务商名称"},{"code":"${isp.id}","description":"","name":"ISP服务商ID"},{"code":"${browser.os.name}","description":"客户端所在操作系统名称","name":"操作系统名称"},{"code":"${browser.os.version}","description":"客户端所在操作系统版本","name":"操作系统版本"},{"code":"${browser.name}","description":"客户端浏览器名称","name":"浏览器名称"},{"code":"${browser.version}","description":"客户端浏览器版本","name":"浏览器版本"},{"code":"${browser.isMobile}","description":"如果客户端是手机,则值为1,否则为0","name":"手机标识"}]
|
||||
window.REQUEST_VARIABLES = [{"code":"${edgeVersion}","description":"","name":"边缘节点版本"},{"code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适合前端有别的反向代理服务时使用,存在伪造的风险","name":"客户端地址(IP)"},{"code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","name":"客户端地址(IP)"},{"code":"${remotePort}","description":"","name":"客户端端口"},{"code":"${remoteUser}","description":"","name":"客户端用户名"},{"code":"${requestURI}","description":"比如/hello?name=lily","name":"请求URI"},{"code":"${requestPath}","description":"比如/hello","name":"请求路径(不包括参数)"},{"code":"${requestURL}","description":"比如https://example.com/hello?name=lily","name":"完整的请求URL"},{"code":"${requestLength}","description":"","name":"请求内容长度"},{"code":"${requestMethod}","description":"比如GET、POST","name":"请求方法"},{"code":"${requestFilename}","description":"","name":"请求文件路径"},{"code":"${scheme}","description":"","name":"请求协议,http或https"},{"code":"${proto}","description:":"类似于HTTP/1.0","name":"包含版本的HTTP请求协议"},{"code":"${timeISO8601}","description":"比如2018-07-16T23:52:24.839+08:00","name":"ISO 8601格式的时间"},{"code":"${timeLocal}","description":"比如17/Jul/2018:09:52:24 +0800","name":"本地时间"},{"code":"${msec}","description":"比如1531756823.054","name":"带有毫秒的时间"},{"code":"${timestamp}","description":"","name":"unix时间戳,单位为秒"},{"code":"${host}","description":"","name":"主机名"},{"code":"${cname}","description":"比如38b48e4f.goedge.cn","name":"当前网站的CNAME"},{"code":"${serverName}","description":"","name":"接收请求的服务器名"},{"code":"${serverPort}","description":"","name":"接收请求的服务器端口"},{"code":"${referer}","description":"","name":"请求来源URL"},{"code":"${referer.host}","description":"","name":"请求来源URL域名"},{"code":"${userAgent}","description":"","name":"客户端信息"},{"code":"${contentType}","description":"","name":"请求头部的Content-Type"},{"code":"${cookies}","description":"","name":"所有cookie组合字符串"},{"code":"${cookie.NAME}","description":"","name":"单个cookie值"},{"code":"${isArgs}","description":"如果URL有参数,则值为`?`;否则,则值为空","name":"问号(?)标记"},{"code":"${args}","description":"","name":"所有参数组合字符串"},{"code":"${arg.NAME}","description":"","name":"单个参数值"},{"code":"${headers}","description":"","name":"所有Header信息组合字符串"},{"code":"${header.NAME}","description":"","name":"单个Header值"},{"code":"${geo.country.name}","description":"","name":"国家/地区名称"},{"code":"${geo.country.id}","description":"","name":"国家/地区ID"},{"code":"${geo.province.name}","description":"目前只包含中国省份","name":"省份名称"},{"code":"${geo.province.id}","description":"目前只包含中国省份","name":"省份ID"},{"code":"${geo.city.name}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${geo.city.id}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${isp.name}","description":"","name":"ISP服务商名称"},{"code":"${isp.id}","description":"","name":"ISP服务商ID"},{"code":"${browser.os.name}","description":"客户端所在操作系统名称","name":"操作系统名称"},{"code":"${browser.os.version}","description":"客户端所在操作系统版本","name":"操作系统版本"},{"code":"${browser.name}","description":"客户端浏览器名称","name":"浏览器名称"},{"code":"${browser.version}","description":"客户端浏览器版本","name":"浏览器版本"},{"code":"${browser.isMobile}","description":"如果客户端是手机,则值为1,否则为0","name":"手机标识"}]
|
||||
|
||||
window.METRIC_HTTP_KEYS = [{"name":"客户端地址(IP)","code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取,适用于前端可能有别的反向代理的情形,存在被伪造的可能","icon":""},{"name":"直接客户端地址(IP)","code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","icon":""},{"name":"客户端用户名","code":"${remoteUser}","description":"通过基本认证填入的用户名","icon":""},{"name":"请求URI","code":"${requestURI}","description":"包含参数,比如/hello?name=lily","icon":""},{"name":"请求路径","code":"${requestPath}","description":"不包含参数,比如/hello","icon":""},{"name":"完整URL","code":"${requestURL}","description":"比如https://example.com/hello?name=lily","icon":""},{"name":"请求方法","code":"${requestMethod}","description":"比如GET、POST等","icon":""},{"name":"请求协议Scheme","code":"${scheme}","description":"http或https","icon":""},{"name":"文件扩展名","code":"${requestPathExtension}","description":"请求路径中的文件扩展名,包括点符号,比如.html、.png","icon":""},{"name":"主机名","code":"${host}","description":"通常是请求的域名","icon":""},{"name":"请求协议Proto","code":"${proto}","description":"包含版本的HTTP请求协议,类似于HTTP/1.0","icon":""},{"name":"HTTP协议","code":"${proto}","description":"包含版本的HTTP请求协议,类似于HTTP/1.0","icon":""},{"name":"URL参数值","code":"${arg.NAME}","description":"单个URL参数值","icon":""},{"name":"请求来源URL","code":"${referer}","description":"请求来源Referer URL","icon":""},{"name":"请求来源URL域名","code":"${referer.host}","description":"请求来源Referer URL域名","icon":""},{"name":"Header值","code":"${header.NAME}","description":"单个Header值,比如${header.User-Agent}","icon":""},{"name":"Cookie值","code":"${cookie.NAME}","description":"单个cookie值,比如${cookie.sid}","icon":""},{"name":"状态码","code":"${status}","description":"","icon":""},{"name":"响应的Content-Type值","code":"${response.contentType}","description":"","icon":""}]
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Vue.component("checkbox", {
|
||||
this.newValue = ""
|
||||
},
|
||||
isChecked: function () {
|
||||
return this.newValue == this.elementValue
|
||||
return (typeof (this.newValue) == "boolean" && this.newValue) || this.newValue == this.elementValue
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -38,6 +38,13 @@ Vue.component("datepicker", {
|
||||
watch: {
|
||||
value: function (v) {
|
||||
this.day = v
|
||||
|
||||
let picker = this.$refs.dayInput.picker
|
||||
if (picker != null) {
|
||||
if (v != null && /^\d+-\d+-\d+$/.test(v)) {
|
||||
picker.setDate(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -54,8 +54,6 @@ Vue.component("datetime-input", {
|
||||
},
|
||||
methods: {
|
||||
change: function () {
|
||||
let date = new Date()
|
||||
|
||||
// day
|
||||
if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)) {
|
||||
this.hasDayError = true
|
||||
@@ -63,21 +61,18 @@ Vue.component("datetime-input", {
|
||||
}
|
||||
let pieces = this.day.split("-")
|
||||
let year = parseInt(pieces[0])
|
||||
date.setFullYear(year)
|
||||
|
||||
let month = parseInt(pieces[1])
|
||||
if (month < 1 || month > 12) {
|
||||
this.hasDayError = true
|
||||
return
|
||||
}
|
||||
date.setMonth(month - 1)
|
||||
|
||||
let day = parseInt(pieces[2])
|
||||
if (day < 1 || day > 32) {
|
||||
this.hasDayError = true
|
||||
return
|
||||
}
|
||||
date.setDate(day)
|
||||
|
||||
this.hasDayError = false
|
||||
|
||||
@@ -96,7 +91,6 @@ Vue.component("datetime-input", {
|
||||
return
|
||||
}
|
||||
this.hasHourError = false
|
||||
date.setHours(hour)
|
||||
|
||||
// minute
|
||||
if (!/^\d+$/.test(this.minute)) {
|
||||
@@ -113,7 +107,6 @@ Vue.component("datetime-input", {
|
||||
return
|
||||
}
|
||||
this.hasMinuteError = false
|
||||
date.setMinutes(minute)
|
||||
|
||||
// second
|
||||
if (!/^\d+$/.test(this.second)) {
|
||||
@@ -130,8 +123,8 @@ Vue.component("datetime-input", {
|
||||
return
|
||||
}
|
||||
this.hasSecondError = false
|
||||
date.setSeconds(second)
|
||||
|
||||
let date = new Date(year, month - 1, day, hour, minute, second)
|
||||
this.timestamp = Math.floor(date.getTime() / 1000)
|
||||
},
|
||||
leadingZero: function (s, l) {
|
||||
|
||||
79
web/public/js/components/node/node-cache-disk-dirs-box.js
Normal file
79
web/public/js/components/node/node-cache-disk-dirs-box.js
Normal file
@@ -0,0 +1,79 @@
|
||||
Vue.component("node-cache-disk-dirs-box", {
|
||||
props: ["value", "name"],
|
||||
data: function () {
|
||||
let dirs = this.value
|
||||
if (dirs == null) {
|
||||
dirs = []
|
||||
}
|
||||
return {
|
||||
dirs: dirs,
|
||||
|
||||
isEditing: false,
|
||||
isAdding: false,
|
||||
|
||||
addingPath: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.addingPath.focus()
|
||||
}, 100)
|
||||
},
|
||||
confirm: function () {
|
||||
let addingPath = this.addingPath.trim()
|
||||
if (addingPath.length == 0) {
|
||||
let that = this
|
||||
teaweb.warn("请输入要添加的缓存目录", function () {
|
||||
that.$refs.addingPath.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
if (addingPath[0] != "/") {
|
||||
addingPath = "/" + addingPath
|
||||
}
|
||||
this.dirs.push({
|
||||
path: addingPath
|
||||
})
|
||||
this.cancel()
|
||||
},
|
||||
cancel: function () {
|
||||
this.addingPath = ""
|
||||
this.isAdding = false
|
||||
this.isEditing = false
|
||||
},
|
||||
remove: function (index) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除此目录吗?", function () {
|
||||
that.dirs.$remove(index)
|
||||
})
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" :name="name" :value="JSON.stringify(dirs)"/>
|
||||
<div style="margin-bottom: 0.3em">
|
||||
<span class="ui label small basic" v-for="(dir, index) in dirs">
|
||||
<i class="icon folder"></i>{{dir.path}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 添加 -->
|
||||
<div v-if="isAdding">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" style="width: 30em" v-model="addingPath" @keyup.enter="confirm()" @keypress.enter.prevent="1" @keydown.esc="cancel()" ref="addingPath" placeholder="新的缓存目录,比如 /mnt/cache"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button small" type="button" @click.prevent="confirm">确定</button>
|
||||
<a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding">
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
@@ -13,9 +13,9 @@ Vue.component("ns-access-log-box", {
|
||||
if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
} else if (accessLog.nsRecordId == null || accessLog.nsRecordId == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
|
||||
// 没有找到记录的不需要高亮显示,防止管理员看到红色就心理恐慌
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -227,7 +227,7 @@ Vue.component("domains-box", {
|
||||
<a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment" v-if="supportWildcard">支持普通域名(<code-label>example.com</code-label>)、泛域名(<code-label>*.example.com</code-label>)、域名后缀(以点号开头,如<code-label>.example.com</code-label>)和正则表达式(以波浪号开头,如<code-label>~.*.example.com</code-label>)。</p>
|
||||
<p class="comment" v-if="supportWildcard">支持普通域名(<code-label>example.com</code-label>)、泛域名(<code-label>*.example.com</code-label>)<span v-if="vSupportWildcard == undefined">、域名后缀(以点号开头,如<code-label>.example.com</code-label>)和正则表达式(以波浪号开头,如<code-label>~.*.example.com</code-label>)</span>;如果域名后有端口,请加上端口号。</p>
|
||||
<p class="comment" v-if="!supportWildcard">只支持普通域名(<code-label>example.com</code-label>、<code-label>www.example.com</code-label>)。</p>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ Vue.component("firewall-syn-flood-config-box", {
|
||||
<td class="title">启用</td>
|
||||
<td>
|
||||
<checkbox v-model="config.isOn"></checkbox>
|
||||
<p class="comment">启用后,WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用Firewalld。</p>
|
||||
<p class="comment">启用后,WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用nftables或Firewalld。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -58,7 +58,7 @@ Vue.component("http-access-log-config-box", {
|
||||
<prior-checkbox :v-config="accessLog" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || accessLog.isPrior">
|
||||
<tr>
|
||||
<td class="title">开启访问日志</td>
|
||||
<td class="title">启用访问日志</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="accessLog.isOn"/>
|
||||
|
||||
@@ -82,7 +82,7 @@ Vue.component("http-access-log-search-box", {
|
||||
<a class="ui label basic" :class="{disabled: keyword.length == 0}" @click.prevent="cleanKeyword"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码:status:200<br/>状态码范围:status:500-504<br/>查询IP:ip:192.168.1.100<br/>查询URL:https://goedge.cn/docs<br/>查询路径部分:requestPath:/hello/world<br/>查询协议版本:proto:HTTP/1.1<br/>协议:scheme:http"></tip-icon></div>
|
||||
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码:status:200<br/>状态码范围:status:500-504<br/>查询IP:ip:192.168.1.100<br/>查询URL:https://goedge.cn/docs<br/>查询路径部分:requestPath:/hello/world<br/>查询协议版本:proto:HTTP/1.1<br/>协议:scheme:http<br/>请求方法:method:POST"></tip-icon></div>
|
||||
</div>
|
||||
<div class="ui fields inline" style="margin-top: 0.5em">
|
||||
<div class="ui field">
|
||||
|
||||
@@ -26,7 +26,7 @@ Vue.component("http-charsets-box", {
|
||||
<prior-checkbox :v-config="charsetConfig" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || charsetConfig.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用字符编码</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="charsetConfig.isOn"/>
|
||||
|
||||
@@ -22,7 +22,7 @@ Vue.component("http-compression-config-box", {
|
||||
brotliRef: null,
|
||||
minLength: {count: 0, "unit": "kb"},
|
||||
maxLength: {count: 0, "unit": "kb"},
|
||||
mimeTypes: ["text/*", "application/*", "font/*"],
|
||||
mimeTypes: ["text/*", "application/javascript", "application/json", "application/atom+xml", "application/rss+xml", "application/xhtml+xml", "font/*", "image/svg+xml"],
|
||||
extensions: [".js", ".json", ".html", ".htm", ".xml", ".css", ".woff2", ".txt"],
|
||||
conds: null
|
||||
}
|
||||
@@ -153,7 +153,7 @@ Vue.component("http-compression-config-box", {
|
||||
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用内容压缩</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="config.isOn"/>
|
||||
|
||||
@@ -14,6 +14,7 @@ Vue.component("http-firewall-rules-box", {
|
||||
window.UPDATING_RULE = null
|
||||
let that = this
|
||||
teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
|
||||
height: "30em",
|
||||
callback: function (resp) {
|
||||
that.rules.push(resp.data.rule)
|
||||
}
|
||||
@@ -23,6 +24,7 @@ Vue.component("http-firewall-rules-box", {
|
||||
window.UPDATING_RULE = rule
|
||||
let that = this
|
||||
teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
|
||||
height: "30em",
|
||||
callback: function (resp) {
|
||||
Vue.set(that.rules, index, resp.data.rule)
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ Vue.component("http-host-redirect-box", {
|
||||
<div style="margin-top: 0.4em">
|
||||
<grey-label><strong>域名跳转</strong></grey-label>
|
||||
<grey-label v-if="redirect.domainAfterScheme != null && redirect.domainAfterScheme.length > 0">{{redirect.domainAfterScheme}}</grey-label>
|
||||
<grey-label v-if="redirect.domainBeforeIgnorePorts">忽略端口</grey-label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="redirect.type == 'port'">
|
||||
|
||||
@@ -65,14 +65,14 @@ Vue.component("http-referers-config-box", {
|
||||
<tr>
|
||||
<td>允许的来源域名</td>
|
||||
<td>
|
||||
<values-box :values="config.allowDomains" @change="changeAllowDomains"></values-box>
|
||||
<domains-box :v-domains="config.allowDomains" @change="changeAllowDomains">></domains-box>
|
||||
<p class="comment">允许的其他来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>禁止的来源域名</td>
|
||||
<td>
|
||||
<values-box :values="config.denyDomains" @change="changeDenyDomains"></values-box>
|
||||
<domains-box :v-domains="config.denyDomains" @change="changeDenyDomains"></domains-box>
|
||||
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -18,7 +18,7 @@ Vue.component("http-stat-config-box", {
|
||||
<prior-checkbox :v-config="stat" v-if="vIsLocation || vIsGroup" ></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || stat.isPrior">
|
||||
<tr>
|
||||
<td class="title">是否开启统计</td>
|
||||
<td class="title">启用统计</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="stat.isOn"/>
|
||||
|
||||
@@ -47,7 +47,7 @@ Vue.component("http-web-root-box", {
|
||||
<prior-checkbox :v-config="rootConfig" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || rootConfig.isPrior">
|
||||
<tr>
|
||||
<td class="title">是否开启静态资源分发</td>
|
||||
<td class="title">启用静态资源分发</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="rootConfig.isOn"/>
|
||||
|
||||
@@ -69,7 +69,7 @@ Vue.component("http-webp-config-box", {
|
||||
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用WebP压缩</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="config.isOn"/>
|
||||
|
||||
@@ -80,7 +80,7 @@ Vue.component("http-websocket-box", {
|
||||
<prior-checkbox :v-config="websocketRef" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="((!vIsLocation && !vIsGroup) || websocketRef.isPrior)">
|
||||
<tr>
|
||||
<td class="title">启用配置</td>
|
||||
<td class="title">启用Websocket</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="websocketRef.isOn"/>
|
||||
|
||||
@@ -85,7 +85,6 @@ window.teaweb = {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (typeof (element) == "string") {
|
||||
element = document.getElementById(element);
|
||||
}
|
||||
@@ -112,6 +111,8 @@ window.teaweb = {
|
||||
},
|
||||
reposition: !bottomLeft
|
||||
})
|
||||
|
||||
element.picker = picker
|
||||
},
|
||||
formatBytes: function (bytes) {
|
||||
bytes = Math.ceil(bytes);
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
<span class="item">|</span>
|
||||
<menu-item :href="'/admins/admin?adminId=' + admin.id" code="index">"{{admin.fullname}}" 详情</menu-item>
|
||||
<menu-item :href="'/admins/update?adminId=' + admin.id" code="update">修改</menu-item>
|
||||
<menu-item :href="'/admins/accessKeys?adminId=' + admin.id" code="accessKey">API AccessKey({{admin.countAccessKeys}})</menu-item>
|
||||
<menu-item :href="'/admins/accesskeys?adminId=' + admin.id" code="accessKey">API AccessKey({{admin.countAccessKeys}})</menu-item>
|
||||
</first-menu>
|
||||
@@ -1,6 +1,6 @@
|
||||
Tea.context(function () {
|
||||
this.createAccessKey = function () {
|
||||
teaweb.popup("/admins/accessKeys/createPopup?adminId=" + this.admin.id, {
|
||||
teaweb.popup("/admins/accesskeys/createPopup?adminId=" + this.admin.id, {
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
teaweb.reload()
|
||||
|
||||
@@ -41,10 +41,10 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="restIsOn">
|
||||
<td class="color-border">HTTP API监听端口</td>
|
||||
<td class="color-border">HTTP API监听端口 *</td>
|
||||
<td>
|
||||
<network-addresses-box :v-name="'restListensJSON'" :v-server-type="'httpWeb'" @change="changeRestListens"></network-addresses-box>
|
||||
<p class="comment">HTTP API节点进程监听的网络端口。</p>
|
||||
<p class="comment">HTTP API节点进程监听的网络端口,需要和当前节点的GRPC端口不同。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -54,7 +54,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>是否启用</td>
|
||||
<td>启用当前节点</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="isOn" value="1" checked="checked"/>
|
||||
|
||||
@@ -18,7 +18,7 @@ Tea.context(function () {
|
||||
|
||||
this.hasHTTPS = this.grpcAddrs.$any(function (k, v) {
|
||||
return v.protocol == "https"
|
||||
}) || (this.node.restIsOn && this.restAddrs.$any(function (k, v) {
|
||||
}) || (this.restIsOn && this.restAddrs.$any(function (k, v) {
|
||||
return v.protocol == "https"
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="node.restIsOn">
|
||||
<td class="color-border">HTTP API监听端口</td>
|
||||
<td class="color-border">HTTP API监听端口 *</td>
|
||||
<td>
|
||||
<network-addresses-box :v-name="'restListensJSON'" :v-server-type="'httpWeb'" @change="changeRestListens" :v-addresses="node.restListens"></network-addresses-box>
|
||||
<p class="comment">HTTP API节点进程监听的网络端口。</p>
|
||||
<p class="comment">HTTP API节点进程监听的网络端口,需要和当前节点的GRPC端口不同。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -66,7 +66,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>启用</td>
|
||||
<td>启用当前节点</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="isOn" value="1" v-model="node.isOn"/>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<menu-item :href="'/clusters/cluster?clusterId=' + clusterId">{{currentClusterName}}</menu-item>
|
||||
<span class="disabled item" style="padding: 0">»</span>
|
||||
<menu-item :href="'/clusters/cluster/nodes?clusterId=' + clusterId" code="index">节点列表</menu-item>
|
||||
<menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">创建节点</menu-item>
|
||||
<span class="disabled item">|</span>
|
||||
<menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">[创建节点]</menu-item>
|
||||
<span class="disabled item">|</span>
|
||||
<menu-item :href="'/clusters/cluster/installManual?clusterId=' + clusterId" code="install">安装升级</menu-item>
|
||||
<menu-item :href="'/clusters/cluster/groups?clusterId=' + clusterId" code="group">节点分组</menu-item>
|
||||
</second-menu>
|
||||
@@ -23,14 +23,14 @@
|
||||
<tr>
|
||||
<td class="title">节点名称 *</td>
|
||||
<td>
|
||||
<input type="text" name="name" maxlength="50" ref="focus"/>
|
||||
<input type="text" name="name" maxlength="50" ref="focus" v-model="name" @input="changeName"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP地址 *</td>
|
||||
<td>
|
||||
<node-ip-addresses-box></node-ip-addresses-box>
|
||||
<p class="comment">用于访问节点和域名解析等。</p>
|
||||
<p class="comment">用于访问节点和域名解析等<span v-if="defaultIP.length > 0"><strong>,如果没有填写默认为{{defaultIP}}</strong></span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="dnsRoutes.length > 0">
|
||||
|
||||
@@ -5,6 +5,7 @@ Tea.context(function () {
|
||||
this.sshPort = ""
|
||||
this.grantId = 0
|
||||
this.step = "info"
|
||||
this.name = ""
|
||||
|
||||
this.success = function (resp) {
|
||||
this.node = resp.data.node
|
||||
@@ -170,4 +171,32 @@ Tea.context(function () {
|
||||
this.createNext = function () {
|
||||
teaweb.reload()
|
||||
}
|
||||
|
||||
this.defaultIP = ""
|
||||
this.changeName = function () {
|
||||
if (this.validateIP(this.name)) {
|
||||
this.defaultIP = this.name
|
||||
} else {
|
||||
this.defaultIP = ""
|
||||
}
|
||||
}
|
||||
|
||||
this.validateIP = function (v) {
|
||||
// 目前只支持ipv4
|
||||
let pieces = v.split(".")
|
||||
if (pieces.length != 4) {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < pieces.length; i++) {
|
||||
if (!/^\d{1,3}$/.test(pieces[i])) {
|
||||
return false
|
||||
}
|
||||
let p = parseInt(pieces[i], 10)
|
||||
if (p > 255) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
})
|
||||
@@ -4,7 +4,7 @@
|
||||
{$template "/code_editor"}
|
||||
|
||||
<div class="right-box">
|
||||
<p>在官网下载节点安装包,然后通过修改节点安装包中的<code-label>configs/cluster.yaml</code-label>,启动后会自动注册节点。</p>
|
||||
<p>在官网下载节点安装包,然后通过修改节点安装包中的<code-label>configs/cluster.yaml</code-label>(如果此配置文件尚未创建,你需要先创建)为以下内容,启动后会自动注册节点。</p>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">cluster.yaml<br/>
|
||||
|
||||
@@ -143,13 +143,6 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CPU线程数</td>
|
||||
<td>
|
||||
<span v-if="node.maxCPU > 0">{{node.maxCPU}}线程</span>
|
||||
<span v-else class="disabled">没有限制。</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>缓存磁盘容量</td>
|
||||
<td>
|
||||
@@ -167,6 +160,22 @@
|
||||
<size-capacity-view :v-value="node.maxCacheMemoryCapacity"></size-capacity-view>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CPU线程数</td>
|
||||
<td>
|
||||
<span v-if="node.maxCPU > 0">{{node.maxCPU}}线程</span>
|
||||
<span v-else class="disabled">没有限制。</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>API节点地址</td>
|
||||
<td>
|
||||
<div v-if="node.apiNodeAddrs != null && node.apiNodeAddrs.length > 0">
|
||||
<span v-for="addr in node.apiNodeAddrs" class="ui label basic small">{{addr}}</span>
|
||||
</div>
|
||||
<span v-else class="disabled">使用全局设置</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -226,6 +235,23 @@
|
||||
<td>主程序位置</td>
|
||||
<td>{{node.status.exePath}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>最近API连接状况</td>
|
||||
<td>
|
||||
<span v-if="node.status.apiSuccessPercent > 0 && node.status.apiAvgCostSeconds > 0">
|
||||
<span v-if="node.status.apiSuccessPercent <= 50" class="red">连接错误异常严重({{round(100 - node.status.apiSuccessPercent)}}%失败),请改善当前节点和API节点之间通讯</span>
|
||||
<span v-else-if="node.status.apiSuccessPercent <= 80" class="red">连接错误较多({{round(100 - node.status.apiSuccessPercent)}}%失败),请改善当前节点和API节点之间通讯</span>
|
||||
<span v-else-if="node.status.apiSuccessPercent < 100" class="orange">有连接错误发生({{round(100 - node.status.apiSuccessPercent)}}%失败),请改善当前节点和API节点之间通讯</span>
|
||||
<span v-else>
|
||||
<span v-if="node.status.apiAvgCostSeconds <= 1" class="green">连接良好</span>
|
||||
<span v-else-if="node.status.apiAvgCostSeconds <= 5" class="orange">连接基本稳定(平均{{round(node.status.apiAvgCostSeconds)}}秒)</span>
|
||||
<span v-else-if="node.status.apiAvgCostSeconds <= 10" class="orange">连接速度较慢(平均{{round(node.status.apiAvgCostSeconds)}}秒)</span>
|
||||
<span v-else class="red">连接非常慢(平均{{round(node.status.apiAvgCostSeconds)}}秒),请改善当前节点和API节点之间通讯</span>
|
||||
</span>
|
||||
</span>
|
||||
<span v-else class="disabled">尚未上报数据</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="nodeDatetime.length > 0">
|
||||
<td>上次更新时间</td>
|
||||
<td>
|
||||
|
||||
@@ -32,4 +32,8 @@ Tea.context(function () {
|
||||
this.isStopping = false
|
||||
})
|
||||
}
|
||||
|
||||
this.round = function (f) {
|
||||
return Math.round(f * 100) / 100
|
||||
}
|
||||
})
|
||||
@@ -15,10 +15,17 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>磁盘缓存目录</td>
|
||||
<td>磁盘缓存主目录</td>
|
||||
<td>
|
||||
<input type="text" name="cacheDiskDir" maxlength="500" value="" v-model="node.cacheDiskDir"/>
|
||||
<p class="comment">存放文件缓存的目录,通常填写绝对路径。不填则表示使用集群缓存策略中定义的目录。</p>
|
||||
<p class="comment">存放文件缓存的主目录,通常填写绝对路径。不填则表示使用集群缓存策略中定义的目录。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>其他磁盘缓存目录</td>
|
||||
<td>
|
||||
<node-cache-disk-dirs-box name="cacheDiskSubDirsJSON" v-model="node.cacheDiskSubDirs"></node-cache-disk-dirs-box>
|
||||
<p class="comment">除了主目录外,可以在这里添加别的可用于缓存的目录;缓存目录有变更时(添加、删除或修改)需要手动将这些缓存目录清空。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<td class="title">SSH主机地址</td>
|
||||
<td>
|
||||
<input type="text" name="sshHost" maxlength="64" v-model="sshHost"/>
|
||||
<p class="comment"><span v-if="hostIsAutoFilled"><strong>已自动填充,需要保存</strong>。</span>比如192.168.1.100。</p>
|
||||
<p class="comment"><span v-if="hostIsAutoFilled" class="red"><strong>已自动填充</strong>,需要点击"保存"按钮后生效。</span>比如192.168.1.100。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -31,7 +31,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="ui message" v-if="isTesting">正在测试是否连接 ...</div>
|
||||
<div class="ui message" v-if="isTesting">正在测试连接 ...</div>
|
||||
<div class="ui message green" v-if="resp != null && resp.isOk">连接成功!</div>
|
||||
<div class="ui message red" v-if="resp != null && !resp.isOk">连接失败:{{resp.error}}</div>
|
||||
|
||||
|
||||
@@ -19,6 +19,19 @@
|
||||
<h4>DNS解析</h4>
|
||||
<dns-resolver-config-box :v-dns-resolver-config="dnsResolverConfig"></dns-resolver-config-box>
|
||||
|
||||
<h4>API相关</h4>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">API节点地址</td>
|
||||
<td>
|
||||
<div style="margin-bottom: 0.5em">
|
||||
<api-node-addresses-box :v-name="'apiNodeAddrsJSON'" :v-addrs="apiNodeAddrs"></api-node-addresses-box>
|
||||
</div>
|
||||
<p class="comment">当前节点单独使用的API节点设置。<pro-warning-label></pro-warning-label></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</div>
|
||||
@@ -19,4 +19,17 @@ a.small {
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
}
|
||||
.node-name-td {
|
||||
position: relative;
|
||||
}
|
||||
.node-name-td .icon.setting {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 50%;
|
||||
margin-top: -1em;
|
||||
}
|
||||
.node-name-td:hover .icon.setting {
|
||||
display: inline;
|
||||
}
|
||||
/*# sourceMappingURL=nodes.css.map */
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["nodes.less"],"names":[],"mappings":"AAAA,MAAO;EACN,oBAAA;;AAGD,CACC;EACC,iCAAA;;AAIF,WACC;EACC,kBAAA;EACA,aAAA;;AAIF,WAAW,UACV;EACC,eAAA;EACA,cAAA;;AAIF,WAAW,MACV;EACC,eAAA;;AAIF,CAAC;EACA,gBAAA;EACA,kBAAA","file":"nodes.css"}
|
||||
{"version":3,"sources":["nodes.less"],"names":[],"mappings":"AAAA,MAAO;EACN,oBAAA;;AAGD,CACC;EACC,iCAAA;;AAIF,WACC;EACC,kBAAA;EACA,aAAA;;AAIF,WAAW,UACV;EACC,eAAA;EACA,cAAA;;AAIF,WAAW,MACV;EACC,eAAA;;AAIF,CAAC;EACA,gBAAA;EACA,kBAAA;;AAGD;EACC,kBAAA;;AADD,aAGC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,gBAAA;;AAIF,aAAa,MACZ,MAAK;EACJ,eAAA","file":"nodes.css"}
|
||||
@@ -73,7 +73,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="node in nodes">
|
||||
<td><a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> L{{node.level}}</span></sup></a>
|
||||
<td class="node-name-td"><a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> L{{node.level}}</span></sup></a>
|
||||
|
||||
<a :href="'/clusters/cluster/node/update?clusterId=' + clusterId + '&nodeId=' + node.id" title="设置"><i class="icon setting grey"></i></a>
|
||||
|
||||
<div v-if="node.region != null">
|
||||
<grey-label>区域:{{node.region.name}}</grey-label>
|
||||
</div>
|
||||
@@ -118,7 +121,7 @@
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td class="center">
|
||||
<span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBytes(node.status.trafficOutBytes/60)}}/s</span>
|
||||
<span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBits(node.status.trafficOutBytes * 8/60)}}</span>
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td class="center">
|
||||
|
||||
@@ -31,4 +31,22 @@ a {
|
||||
a.small {
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.node-name-td {
|
||||
position: relative;
|
||||
|
||||
.icon.setting {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 50%;
|
||||
margin-top: -1em;
|
||||
}
|
||||
}
|
||||
|
||||
.node-name-td:hover {
|
||||
.icon.setting {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,13 @@
|
||||
<p class="comment">可选项。默认使用<code-label>pool.ntp.org</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="cluster.clock.autoSync">
|
||||
<td class="color-border">检查Chrony</td>
|
||||
<td>
|
||||
<checkbox name="clockCheckChrony" v-model="cluster.clock.checkChrony"></checkbox>
|
||||
<p class="comment">选中后,表示如果chrony(另一款时间同步软件)正在运行,则不重复执行时间同步。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>自动远程启动</td>
|
||||
<td>
|
||||
|
||||
@@ -15,7 +15,17 @@
|
||||
.cluster-name-td .icon.opacity {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.cluster-name-td .icon.setting {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
top: 50%;
|
||||
margin-top: -0.7em;
|
||||
}
|
||||
.cluster-name-td:hover .icon.pin {
|
||||
display: inline;
|
||||
}
|
||||
.cluster-name-td:hover .icon.setting {
|
||||
display: inline;
|
||||
}
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,kBAAA;;AADD,gBAGC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,kBAAA;EACA,YAAA;;AATF,gBAYC,MAAK;EACJ,eAAA;;AAbF,gBAgBC,MAAK;EACJ,YAAA;;AAIF,gBAAgB,MACf,MAAK;EACJ,eAAA","file":"index.css"}
|
||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,kBAAA;;AADD,gBAGC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,kBAAA;EACA,YAAA;;AATF,gBAYC,MAAK;EACJ,eAAA;;AAbF,gBAgBC,MAAK;EACJ,YAAA;;AAjBF,gBAoBC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,kBAAA;;AAIF,gBAAgB,MACf,MAAK;EACJ,eAAA;;AAFF,gBAAgB,MAIf,MAAK;EACJ,eAAA","file":"index.css"}
|
||||
@@ -43,6 +43,9 @@
|
||||
<tr v-for="cluster in clusters">
|
||||
<td class="cluster-name-td">
|
||||
<a :href="'/clusters/cluster?clusterId=' + cluster.id"><keyword :v-word="keyword">{{cluster.name}}</keyword></a>
|
||||
|
||||
<a :href="'/clusters/cluster/settings?clusterId=' + cluster.id" title="设置"><i class="icon setting grey"></i></a>
|
||||
|
||||
<div v-if="cluster.timeZone != null && cluster.timeZone.length > 0">
|
||||
<grey-label>时区:{{cluster.timeZone}}</grey-label>
|
||||
</div>
|
||||
|
||||
@@ -17,10 +17,21 @@
|
||||
.icon.opacity {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.icon.setting {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
top: 50%;
|
||||
margin-top: -0.7em;
|
||||
}
|
||||
}
|
||||
|
||||
.cluster-name-td:hover {
|
||||
.icon.pin {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.icon.setting {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
14
web/views/@default/clusters/nodes.css
Normal file
14
web/views/@default/clusters/nodes.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.node-name-td {
|
||||
position: relative;
|
||||
}
|
||||
.node-name-td .icon.setting {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 50%;
|
||||
margin-top: -1em;
|
||||
}
|
||||
.node-name-td:hover .icon.setting {
|
||||
display: inline;
|
||||
}
|
||||
/*# sourceMappingURL=nodes.css.map */
|
||||
1
web/views/@default/clusters/nodes.css.map
Normal file
1
web/views/@default/clusters/nodes.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["nodes.less"],"names":[],"mappings":"AAAA;EACC,kBAAA;;AADD,aAGC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,gBAAA;;AAIF,aAAa,MACZ,MAAK;EACJ,eAAA","file":"nodes.css"}
|
||||
@@ -67,7 +67,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="node in nodes">
|
||||
<td><a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> L{{node.level}}</span></sup></a>
|
||||
<td class="node-name-td"><a :href="'/clusters/cluster/node?clusterId=' + node.cluster.id + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> L{{node.level}}</span></sup></a>
|
||||
|
||||
<a :href="'/clusters/cluster/node/update?clusterId=' + node.cluster.id + '&nodeId=' + node.id" title="设置"><i class="icon setting grey"></i></a>
|
||||
|
||||
<div v-if="node.region != null">
|
||||
<grey-label>区域:{{node.region.name}}</grey-label>
|
||||
</div>
|
||||
@@ -112,7 +115,7 @@
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td class="center">
|
||||
<span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBytes(node.status.trafficOutBytes/60)}}/s</span>
|
||||
<span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBits(node.status.trafficOutBytes * 8/60)}}</span>
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td class="center">
|
||||
|
||||
17
web/views/@default/clusters/nodes.less
Normal file
17
web/views/@default/clusters/nodes.less
Normal file
@@ -0,0 +1,17 @@
|
||||
.node-name-td {
|
||||
position: relative;
|
||||
|
||||
.icon.setting {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 50%;
|
||||
margin-top: -1em;
|
||||
}
|
||||
}
|
||||
|
||||
.node-name-td:hover {
|
||||
.icon.setting {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
{$layout}
|
||||
|
||||
<div class="ui tabular menu tiny">
|
||||
<a href="" class="item" :class="{active: tab == 'domainMatch'}" @click.prevent="selectTab('domainMatch')">域名匹配配置</a>
|
||||
<a href="" class="item" :class="{active: tab == 'domainAuditing'}" @click.prevent="selectTab('domainAuditing')">域名审核配置</a>
|
||||
<a href="" class="item" :class="{active: tab == 'tcpPorts'}" @click.prevent="selectTab('tcpPorts')">TCP/TLS端口</a>
|
||||
</div>
|
||||
@@ -9,11 +8,6 @@
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<input type="hidden" name="globalConfigJSON" :value="JSON.stringify(globalConfig)"/>
|
||||
|
||||
<!-- 域名相关配置 -->
|
||||
<div v-show="tab == 'domainMatch'">
|
||||
<p class="comment">域名匹配相关配置已经转移到集群设置中,请到对应的集群设置中修改。</p>
|
||||
</div>
|
||||
|
||||
<!-- 域名审核相关配置 -->
|
||||
<div v-show="tab == 'domainAuditing'">
|
||||
<table class="ui table definition selectable">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Tea.context(function () {
|
||||
this.tab = "domainMatch"
|
||||
this.tab = "domainAuditing"
|
||||
|
||||
this.$delay(function () {
|
||||
if (window.location.hash != null && window.location.hash.length > 1) {
|
||||
this.selectTab(window.location.hash.substr(1))
|
||||
this.selectTab(window.location.hash.substring(1))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span v-if="rule.operator == 'match' || rule.operator == 'not match'">正则表达式</span>
|
||||
<span v-if="(checkpoint == null || checkpoint.dataType != 'bool') && (rule.operator == 'match' || rule.operator == 'not match')">正则表达式</span>
|
||||
<span v-else>对比值</span>
|
||||
</td>
|
||||
<td>
|
||||
@@ -105,6 +105,15 @@
|
||||
<p class="comment">将二进制进行Base64Encode后放在这里,比如<code-label>Hello</code-label>对应<code-label>SGVsbG8=</code-label>。</p>
|
||||
</div>
|
||||
|
||||
<!-- bool数据 -->
|
||||
<div v-else-if="checkpoint != null && checkpoint.dataType == 'bool'">
|
||||
<select name="value" class="ui selectable auto-width" v-model="rule.value" @change="changeRuleValue">
|
||||
<option value="">[请选择]</option>
|
||||
<option value="1">是(1)</option>
|
||||
<option value="0">否(0)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 其余数据 -->
|
||||
<textarea rows="3" maxlength="4096" name="value" v-model="rule.value" @input="changeRuleValue" v-else></textarea>
|
||||
|
||||
@@ -132,7 +141,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="rule.operator == 'match' || rule.operator == 'not match'">
|
||||
<tr v-if="(checkpoint == null || checkpoint.dataType != 'bool') && (rule.operator == 'match' || rule.operator == 'not match')">
|
||||
<td>正则表达式测试</td>
|
||||
<td>
|
||||
<a href="" v-if="!regexpTestIsOn" @click.prevent="changeRegexpTestIsOn">[输入测试字符串]</a>
|
||||
@@ -147,7 +156,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="operator.case != 'none'">
|
||||
<tr v-if="(checkpoint == null || checkpoint.dataType != 'bool') && operator != null && operator.case != 'none'">
|
||||
<td>不区分大小写</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
|
||||
@@ -52,6 +52,19 @@ Tea.context(function () {
|
||||
this.checkpoint = this.checkpoints.$find(function (k, v) {
|
||||
return v.prefix == that.rule.checkpointPrefix
|
||||
})
|
||||
if (this.checkpoint == null) {
|
||||
return
|
||||
}
|
||||
switch (this.checkpoint.dataType) {
|
||||
case "bool":
|
||||
this.rule.operator = "eq"
|
||||
break
|
||||
case "number":
|
||||
this.rule.operator = "eq"
|
||||
break
|
||||
default:
|
||||
this.rule.operator = "match"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +76,9 @@ Tea.context(function () {
|
||||
this.operator = this.operators.$find(function (k, v) {
|
||||
return v.code == that.rule.operator
|
||||
})
|
||||
if (this.operator == null) {
|
||||
return
|
||||
}
|
||||
if (!this.isUpdating) {
|
||||
this.rule.isCaseInsensitive = (this.operator.case == "yes")
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ Tea.context(function () {
|
||||
this.createSet = function (groupId) {
|
||||
teaweb.popup("/servers/components/waf/createSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + groupId + "&type=" + this.type, {
|
||||
width: "50em",
|
||||
height: "30em",
|
||||
height: "40em",
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
@@ -47,7 +47,7 @@ Tea.context(function () {
|
||||
this.updateSet = function (setId) {
|
||||
teaweb.popup("/servers/components/waf/updateSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + this.group.id + "&type=" + this.type + "&setId=" + setId, {
|
||||
width: "50em",
|
||||
height: "30em",
|
||||
height: "40em",
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
|
||||
@@ -35,7 +35,7 @@ Tea.context(function () {
|
||||
this.createSet = function (groupId) {
|
||||
teaweb.popup("/servers/components/waf/createSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + groupId + "&type=" + this.type, {
|
||||
width: "50em",
|
||||
height: "30em",
|
||||
height: "40em",
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
@@ -48,7 +48,7 @@ Tea.context(function () {
|
||||
this.updateSet = function (setId) {
|
||||
teaweb.popup("/servers/components/waf/updateSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + this.group.id + "&type=" + this.type + "&setId=" + setId, {
|
||||
width: "50em",
|
||||
height: "30em",
|
||||
height: "40em",
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<th>部署集群</th>
|
||||
<th>域名</th>
|
||||
<th>端口</th>
|
||||
<th class="center" style="width: 8em">下行带宽<tip-icon content="最近5分钟峰值带宽,每5分钟更新一次,单位:比特"></tip-icon><sort-arrow name="trafficOutOrder"></sort-arrow></th>
|
||||
<th class="center" style="width: 8em">下行带宽<tip-icon content="最近5分钟峰值带宽,每5分钟更新一次"></tip-icon><sort-arrow name="trafficOutOrder"></sort-arrow></th>
|
||||
<th class="two wide center">状态</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<tr>
|
||||
<td class="title">当前服务CNAME</td>
|
||||
<td>
|
||||
<span id="cname-text">{{dnsName}}.<span v-if="dnsDomain.length > 0">{{dnsDomain}}.</span><span v-else>根域名</span></span> <copy-to-clipboard :v-target="'cname-text'"></copy-to-clipboard> <a href="" @click.prevent="regenerateCNAME()" style="font-size: 0.8em">[重新生成]</a> <a href="" @click.prevent="updateCNAME()" style="font-size: 0.8em">[手动修改]</a>
|
||||
<span id="cname-text">{{dnsName}}.<span v-if="dnsDomain.length > 0">{{dnsDomain}}</span><span v-else>根域名</span></span> <copy-to-clipboard :v-target="'cname-text'"></copy-to-clipboard> <a href="" @click.prevent="regenerateCNAME()" style="font-size: 0.8em">[重新生成]</a> <a href="" @click.prevent="updateCNAME()" style="font-size: 0.8em">[手动修改]</a>
|
||||
<p class="comment">你需要为你的每个<a :href="'/servers/server/settings/serverNames?serverId=' + serverId">网站域名</a>设置一个CNAME解析,值为上面内容。</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<tr>
|
||||
<td class="title">证书包含的域名 *</td>
|
||||
<td>
|
||||
<span v-if="serverNames.length == 0" class="disabled">还没有设置域名,暂时不能申请。</span>
|
||||
<span v-if="serverNames.length == 0" class="disabled">还没有需要申请证书的域名,暂时不能申请。</span>
|
||||
<div v-if="serverNames.length > 0">
|
||||
<div v-for="(serverName, index) in serverNames" class="ui tiny basic label">
|
||||
<input type="hidden" name="serverNames" :value="serverName"/>
|
||||
|
||||
@@ -80,6 +80,13 @@
|
||||
<domains-box name="domainsBeforeJSON" :v-domains="redirect.domainsBefore"></domains-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-border">忽略跳转前端口</td>
|
||||
<td>
|
||||
<checkbox name="domainBeforeIgnorePorts" v-model="redirect.domainBeforeIgnorePorts"></checkbox>
|
||||
<p class="comment">选中后,表示忽略跳转前域名端口,只要域名匹配时就跳转;如不选中,则表示只有域名和端口同时匹配时才会跳转。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-border">跳转后域名 *</td>
|
||||
<td>
|
||||
|
||||
@@ -21,6 +21,7 @@ Tea.context(function () {
|
||||
|
||||
domainsAll: false,
|
||||
domainBefore: [],
|
||||
domainBeforeIgnorePorts: true,
|
||||
domainAfter: "",
|
||||
domainAfterScheme: "",
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Tea.context(function () {
|
||||
this.createSet = function (groupId) {
|
||||
teaweb.popup("/servers/components/waf/createSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + groupId + "&type=" + this.type, {
|
||||
width: "50em",
|
||||
height: "30em",
|
||||
height: "40em",
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
@@ -48,7 +48,7 @@ Tea.context(function () {
|
||||
this.updateSet = function (setId) {
|
||||
teaweb.popup("/servers/components/waf/updateSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + this.group.id + "&type=" + this.type + "&setId=" + setId, {
|
||||
width: "50em",
|
||||
height: "30em",
|
||||
height: "40em",
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
.install-box {
|
||||
width: 50em;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -25em;
|
||||
top: 1em;
|
||||
bottom: 1em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.install-box .button.margin {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.install-box .button.primary {
|
||||
float: right;
|
||||
}
|
||||
.install-box .button.disabled {
|
||||
float: right;
|
||||
}
|
||||
.install-box table td.title {
|
||||
width: 10em;
|
||||
}
|
||||
.install-box .radio {
|
||||
margin-right: 1em;
|
||||
}
|
||||
.install-box .radio label {
|
||||
cursor: pointer !important;
|
||||
font-size: 0.9em !important;
|
||||
}
|
||||
.install-box h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
.install-box .content-box {
|
||||
overflow-y: auto;
|
||||
position: fixed;
|
||||
top: 5em;
|
||||
bottom: 5em;
|
||||
left: 50%;
|
||||
width: 50em;
|
||||
padding-right: 1em;
|
||||
margin-left: -25em;
|
||||
z-index: 1;
|
||||
}
|
||||
.install-box .content-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.install-box .button-group {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -25em;
|
||||
z-index: 1;
|
||||
width: 50em;
|
||||
bottom: 1em;
|
||||
}
|
||||
.install-box .button-group button {
|
||||
z-index: 10;
|
||||
}
|
||||
.install-box .button-group .status-box {
|
||||
position: absolute;
|
||||
top: 3em;
|
||||
left: 5em;
|
||||
right: 5em;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
z-index: 0;
|
||||
}
|
||||
.install-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
/*# sourceMappingURL=@install.css.map */
|
||||
@@ -1 +1 @@
|
||||
undefined
|
||||
{"version":3,"sources":["@install.less"],"names":[],"mappings":"AAAA;EAIC,WAAA;EACA,eAAA;EACA,SAAA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;;AAVD,YAYC,QAAO;EACN,eAAA;;AAbF,YAgBC,QAAO;EACN,YAAA;;AAjBF,YAoBC,QAAO;EACN,YAAA;;AArBF,YAwBC,MACC,GAAE;EACD,WAAA;;AA1BH,YA8BC;EACC,iBAAA;;AA/BF,YA8BC,OAGC;EACC,0BAAA;EACA,gBAAA;;AAnCH,YAuCC;EACC,mBAAA;;AAxCF,YA2CC;EACC,gBAAA;EACA,eAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,WAAA;EACA,kBAAA;EACA,kBAAA;EACA,UAAA;;AApDF,YAuDC,aAAY;EACX,UAAA;;AAxDF,YA2DC;EACC,eAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,WAAA;;AAjEF,YA2DC,cAQC;EACC,WAAA;;AApEH,YA2DC,cAYC;EACC,kBAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;;AAKH,YAAY;EACX,UAAA","file":"@install.css"}
|
||||
@@ -1,10 +1,11 @@
|
||||
.install-box {
|
||||
@width: 50em;
|
||||
@half-width: 25em;
|
||||
|
||||
width: @width;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -@width/2;
|
||||
margin-left: -@half-width;
|
||||
top: 1em;
|
||||
bottom: 1em;
|
||||
overflow-y: auto;
|
||||
@@ -48,7 +49,7 @@
|
||||
left: 50%;
|
||||
width: @width;
|
||||
padding-right: 1em;
|
||||
margin-left: -@width/2;
|
||||
margin-left: -@half-width;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@
|
||||
.button-group {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -@width/2;
|
||||
margin-left: -@half-width;
|
||||
z-index: 1;
|
||||
width: @width;
|
||||
bottom: 1em;
|
||||
@@ -71,8 +72,8 @@
|
||||
.status-box {
|
||||
position: absolute;
|
||||
top: 3em;
|
||||
left: 15em;
|
||||
right: 15em;
|
||||
left: 5em;
|
||||
right: 5em;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
z-index: 0;
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
.install-box .button-group .status-box {
|
||||
position: absolute;
|
||||
top: 3em;
|
||||
left: 15em;
|
||||
right: 15em;
|
||||
left: 5em;
|
||||
right: 5em;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
z-index: 0;
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["@install.less"],"names":[],"mappings":"AAAA;EAGC,WAAA;EACA,eAAA;EACA,SAAA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;;AATD,YAWC,QAAO;EACN,eAAA;;AAZF,YAeC,QAAO;EACN,YAAA;;AAhBF,YAmBC,QAAO;EACN,YAAA;;AApBF,YAuBC,MACC,GAAE;EACD,WAAA;;AAzBH,YA6BC;EACC,iBAAA;;AA9BF,YA6BC,OAGC;EACC,0BAAA;EACA,2BAAA;;AAlCH,YAsCC;EACC,mBAAA;;AAvCF,YA0CC;EACC,gBAAA;EACA,eAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,WAAA;EACA,kBAAA;EACA,kBAAA;EACA,UAAA;;AAnDF,YAsDC,aAAY;EACX,UAAA;;AAvDF,YA0DC;EACC,eAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,WAAA;;AAhEF,YA0DC,cAQC;EACC,WAAA;;AAnEH,YA0DC,cAYC;EACC,kBAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;;AAKH,YAAY;EACX,UAAA","file":"index.css"}
|
||||
{"version":3,"sources":["@install.less"],"names":[],"mappings":"AAAA;EAIC,WAAA;EACA,eAAA;EACA,SAAA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;;AAVD,YAYC,QAAO;EACN,eAAA;;AAbF,YAgBC,QAAO;EACN,YAAA;;AAjBF,YAoBC,QAAO;EACN,YAAA;;AArBF,YAwBC,MACC,GAAE;EACD,WAAA;;AA1BH,YA8BC;EACC,iBAAA;;AA/BF,YA8BC,OAGC;EACC,0BAAA;EACA,gBAAA;;AAnCH,YAuCC;EACC,mBAAA;;AAxCF,YA2CC;EACC,gBAAA;EACA,eAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,WAAA;EACA,kBAAA;EACA,kBAAA;EACA,UAAA;;AApDF,YAuDC,aAAY;EACX,UAAA;;AAxDF,YA2DC;EACC,eAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,WAAA;;AAjEF,YA2DC,cAQC;EACC,WAAA;;AApEH,YA2DC,cAYC;EACC,kBAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;;AAKH,YAAY;EACX,UAAA","file":"index.css"}
|
||||
@@ -47,10 +47,10 @@
|
||||
|
||||
<!-- 介绍 -->
|
||||
<div v-show="step == STEP_INTRO">
|
||||
<div>感谢你选择使用<strong>GoEdge</strong>集群服务系统,下面让我们一起开始配置系统。</div>
|
||||
<div class="margin">在这之前如果你还没有可用的MySQL数据库,请先安装数据库再进行。</div>
|
||||
<div>感谢你选择使用<strong>GoEdge</strong>CDN系统,下面让我们一起开始配置系统。</div>
|
||||
<div class="margin">在这之前如果你还没有可用的MySQL数据库,请先安装MySQL数据库再进行。</div>
|
||||
<div class="margin" style="color: grey">免责声明:GoEdge软件开发者并不对您的软件使用方法、服务对象、服务内容负道德或法律上的约束责任,在使用本软件时产生的一切法律风险自负。</div>
|
||||
<div class="margin" style="color: grey">用户协议:请在遵守中华人民共和国政策、法律、法规的前提下使用本软件;自愿承担因不当使用本软件产生的一切法律后果;承认GoEdge开发者拥有本软件的著作权;点击"开始"安装表示你同意此用户协议。</div>
|
||||
<div class="margin" style="color: grey">用户协议:请在遵守中华人民共和国政策、法律、法规的前提下使用本软件;自愿承担因不当使用本软件产生的一切法律后果;承认GoEdge开发者拥有本软件的所有著作权;点击"开始"安装表示你同意此用户协议。</div>
|
||||
<button class="ui button primary" style="margin-top: 10em" type="button" @click.prevent="goIntroNext()">开始<i class="icon long arrow right"></i></button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
@import "@install";
|
||||
|
||||
@import "@install";
|
||||
@@ -6,5 +6,5 @@
|
||||
<menu-item :href="'/users/update?userId=' + user.id" code="update">修改</menu-item>
|
||||
<menu-item :href="'/users/features?userId=' + user.id" code="feature" v-if="teaIsPlus">功能</menu-item>
|
||||
<menu-item :href="'/users/identity?userId=' + user.id" code="identity" v-if="teaIsPlus">实名认证<span v-if="user.hasNewIndividualIdentity || user.hasNewEnterpriseIdentity" class="red small">(待审核)</span><span v-if="user.identityTag != null && user.identityTag.length > 0" class="green">({{user.identityTag}})</span></menu-item>
|
||||
<menu-item :href="'/users/accessKeys?userId=' + user.id" code="accessKey">API AccessKey({{user.countAccessKeys}})</menu-item>
|
||||
<menu-item :href="'/users/accesskeys?userId=' + user.id" code="accessKey">API AccessKey({{user.countAccessKeys}})</menu-item>
|
||||
</first-menu>
|
||||
@@ -1,6 +1,6 @@
|
||||
Tea.context(function () {
|
||||
this.createAccessKey = function () {
|
||||
teaweb.popup("/users/accessKeys/createPopup?userId=" + this.user.id, {
|
||||
teaweb.popup("/users/accesskeys/createPopup?userId=" + this.user.id, {
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
teaweb.reload()
|
||||
|
||||
Reference in New Issue
Block a user