Compare commits

...

17 Commits

Author SHA1 Message Date
刘祥超
f2e14cf0a6 版本号修改为1.0.4 2023-04-24 10:17:05 +08:00
刘祥超
bc9b6d1595 为集群选择DNS域名时自动排序并不显示已下线域名 2023-04-24 09:38:57 +08:00
刘祥超
fd11b62390 更新components.js 2023-04-23 20:13:23 +08:00
刘祥超
8db00a5ab5 远程升级API节点时自动上传边缘节点安装文件 2023-04-23 19:48:07 +08:00
刘祥超
a6757e9374 申请ACME证书时可以指定平台用户 2023-04-23 15:01:43 +08:00
刘祥超
e0fe385404 证书申请任务可以使用用户筛选 2023-04-23 11:03:07 +08:00
刘祥超
f9f4258ec1 证书列表可以使用用户筛选 2023-04-23 10:44:37 +08:00
刘祥超
f939d563ad 选择证书时,如果证书列表为空,则提示可以搜索未指定用户证书 2023-04-22 12:04:04 +08:00
刘祥超
fad03add54 “网站服务”改为“网站”、“网站列表” 2023-04-21 17:51:25 +08:00
刘祥超
9ab6dab081 SESSION读取失败时不强制重新登录 2023-04-21 15:39:51 +08:00
刘祥超
5a55268830 修复在HTTP/1.x下开多个窗口访问非常慢的问题 2023-04-19 21:03:46 +08:00
刘祥超
fc3769239d 节点列表页CPU、内存使用比例增加百分号 2023-04-19 19:26:49 +08:00
刘祥超
c9fb3153eb 安全设置增加“检查客户端指纹"和"检查客户端区域"选项 2023-04-19 18:25:10 +08:00
刘祥超
a31f9ed9c5 节点列表负载数据如果是0,则显示0.00 2023-04-18 11:23:49 +08:00
刘祥超
2f75828ba4 优化滚动条在firefox上的显示 2023-04-11 16:33:22 +08:00
刘祥超
89679ec358 版本号改为1.1.0 2023-04-10 21:03:41 +08:00
刘祥超
98f77f52df Update Dockerfile 2023-04-10 09:20:59 +08:00
86 changed files with 1096 additions and 214 deletions

View File

@@ -1,7 +1,7 @@
FROM alpine:latest
LABEL maintainer="iwind.liu@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 1.0.0
ENV VERSION 1.0.1
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"

View File

@@ -167,7 +167,7 @@ func AllModuleMaps() []maps.Map {
"url": "/dashboard",
},
{
"name": "网站服务",
"name": "网站列表",
"code": AdminModuleCodeServer,
"url": "/servers",
},

View File

@@ -29,7 +29,7 @@ func LoadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
return nil, err
}
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
return &v, nil
}
@@ -83,7 +83,12 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
return sharedSecurityConfig, nil
}
config := &systemconfigs.SecurityConfig{}
var config = &systemconfigs.SecurityConfig{
Frame: FrameSameOrigin,
AllowLocal: true,
CheckClientFingerprint: false,
CheckClientRegion: true,
}
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[SECURITY_MANAGER]" + err.Error())
@@ -100,7 +105,9 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
func defaultSecurityConfig() *systemconfigs.SecurityConfig {
return &systemconfigs.SecurityConfig{
Frame: FrameSameOrigin,
AllowLocal: true,
Frame: FrameSameOrigin,
AllowLocal: true,
CheckClientFingerprint: false,
CheckClientRegion: true,
}
}

View File

@@ -1,9 +1,9 @@
package teaconst
const (
Version = "1.0.1"
Version = "1.0.4"
APINodeVersion = "1.0.1"
APINodeVersion = "1.0.4"
ProductName = "Edge Admin"
ProcessName = "edge-admin"

View File

@@ -85,6 +85,9 @@ func (this *AdminNode) Run() {
// 启动API节点
this.startAPINode()
// 启动IP库
this.startIPLibrary()
// 启动Web服务
sessionManager, err := NewSessionManager()
if err != nil {

View File

@@ -0,0 +1,18 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/iwind/TeaGo/logs"
)
// 启动IP库
func (this *AdminNode) startIPLibrary() {
logs.Println("[NODE]initializing ip library ...")
err := iplibrary.InitDefault()
if err != nil {
logs.Println("[NODE]initialize ip library failed: "+err.Error())
}
}

View File

@@ -40,6 +40,7 @@ func (this *SessionManager) Read(sid string) map[string]string {
resp, err := rpcClient.LoginSessionRPC().FindLoginSession(rpcClient.Context(0), &pb.FindLoginSessionRequest{Sid: sid})
if err != nil {
logs.Println("SESSION", "read '"+sid+"' failed: "+err.Error())
result["@error"] = err.Error()
return result
}

View File

@@ -2,6 +2,9 @@ package rpc
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"strings"
"sync"
)
@@ -28,3 +31,23 @@ func SharedRPC() (*RPCClient, error) {
sharedRPC = client
return sharedRPC, nil
}
// IsConnError 是否为连接错误
func IsConnError(err error) bool {
if err == nil {
return false
}
// 检查是否为连接错误
statusErr, ok := status.FromError(err)
if ok {
var errorCode = statusErr.Code()
return errorCode == codes.Unavailable || errorCode == codes.Canceled
}
if strings.Contains(err.Error(), "code = Canceled") {
return true
}
return false
}

View File

@@ -0,0 +1,79 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package apinodeutils
import (
"crypto/md5"
"fmt"
"io"
"os"
)
// DeployFile 部署文件描述
type DeployFile struct {
OS string
Arch string
Version string
Path string
}
// Sum 计算概要
func (this *DeployFile) Sum() (string, error) {
fp, err := os.Open(this.Path)
if err != nil {
return "", err
}
defer func() {
_ = fp.Close()
}()
m := md5.New()
buffer := make([]byte, 128*1024)
for {
n, err := fp.Read(buffer)
if err != nil {
if err == io.EOF {
break
}
return "", err
}
_, err = m.Write(buffer[:n])
if err != nil {
return "", err
}
}
sum := m.Sum(nil)
return fmt.Sprintf("%x", sum), nil
}
// Read 读取一个片段数据
func (this *DeployFile) Read(offset int64) (data []byte, newOffset int64, err error) {
fp, err := os.Open(this.Path)
if err != nil {
return nil, offset, err
}
defer func() {
_ = fp.Close()
}()
stat, err := fp.Stat()
if err != nil {
return nil, offset, err
}
if offset >= stat.Size() {
return nil, offset, io.EOF
}
_, err = fp.Seek(offset, io.SeekStart)
if err != nil {
return nil, offset, err
}
buffer := make([]byte, 128*1024)
n, err := fp.Read(buffer)
if err != nil {
return nil, offset, err
}
return buffer[:n], offset + int64(n), nil
}

View File

@@ -0,0 +1,96 @@
package apinodeutils
import (
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
stringutil "github.com/iwind/TeaGo/utils/string"
"regexp"
)
// DeployManager 节点部署文件管理器
// 如果节点部署文件有变化需要重启API节点以便于生效
type DeployManager struct {
dir string
}
// NewDeployManager 获取新节点部署文件管理器
func NewDeployManager() *DeployManager {
var manager = &DeployManager{
dir: Tea.Root + "/edge-api/deploy",
}
manager.LoadNodeFiles()
manager.LoadNSNodeFiles()
return manager
}
// LoadNodeFiles 加载所有边缘节点文件
func (this *DeployManager) LoadNodeFiles() []*DeployFile {
var keyMap = map[string]*DeployFile{} // key => File
var reg = regexp.MustCompile(`^edge-node-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
for _, file := range files.NewFile(this.dir).List() {
var name = file.Name()
if !reg.MatchString(name) {
continue
}
var matches = reg.FindStringSubmatch(name)
var osName = matches[1]
var arch = matches[2]
var version = matches[3]
var key = osName + "_" + arch
oldFile, ok := keyMap[key]
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
continue
}
keyMap[key] = &DeployFile{
OS: osName,
Arch: arch,
Version: version,
Path: file.Path(),
}
}
var result = []*DeployFile{}
for _, v := range keyMap {
result = append(result, v)
}
return result
}
// LoadNSNodeFiles 加载所有NS节点安装文件
func (this *DeployManager) LoadNSNodeFiles() []*DeployFile {
var keyMap = map[string]*DeployFile{} // key => File
var reg = regexp.MustCompile(`^edge-dns-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
for _, file := range files.NewFile(this.dir).List() {
var name = file.Name()
if !reg.MatchString(name) {
continue
}
var matches = reg.FindStringSubmatch(name)
var osName = matches[1]
var arch = matches[2]
var version = matches[3]
var key = osName + "_" + arch
oldFile, ok := keyMap[key]
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
continue
}
keyMap[key] = &DeployFile{
OS: osName,
Arch: arch,
Version: version,
Path: file.Path(),
}
}
var result = []*DeployFile{}
for _, v := range keyMap {
result = append(result, v)
}
return result
}

View File

@@ -4,6 +4,7 @@ package apinodeutils
import (
"compress/gzip"
"context"
"crypto/md5"
"errors"
"fmt"
@@ -62,7 +63,36 @@ func (this *Upgrader) Upgrade() error {
if err != nil {
return err
}
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(sharedClient.Context(0), &pb.FindCurrentAPINodeVersionRequest{})
// 升级边缘节点
err = this.upgradeNodes(sharedClient.Context(0), rpcClient)
if err != nil {
return err
}
// 升级NS节点
err = this.upgradeNSNodes(sharedClient.Context(0), rpcClient)
if err != nil {
return err
}
// 升级API节点
err = this.upgradeAPINode(sharedClient.Context(0), rpcClient)
if err != nil {
return errors.New("upgrade api node failed: " + err.Error())
}
return nil
}
// Progress 查看升级进程
func (this *Upgrader) Progress() *Progress {
return this.progress
}
// 升级API节点
func (this *Upgrader) upgradeAPINode(ctx context.Context, rpcClient *rpc.RPCClient) error {
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(ctx, &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
return err
}
@@ -77,7 +107,7 @@ func (this *Upgrader) Upgrade() error {
return errors.New(reason)
}
localVersion, err := localVersion()
localVersion, err := lookupLocalVersion()
if err != nil {
return errors.New("lookup version failed: " + err.Error())
}
@@ -196,6 +226,124 @@ func (this *Upgrader) Upgrade() error {
return nil
}
func (this *Upgrader) Progress() *Progress {
return this.progress
// 升级边缘节点
func (this *Upgrader) upgradeNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
// 本地的
var manager = NewDeployManager()
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
for _, deployFile := range manager.LoadNodeFiles() {
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
}
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
if err != nil {
return err
}
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
for _, nodeFile := range remoteFilesResp.NodeDeployFiles {
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
}
// 对比版本
for key, deployFile := range localFileMap {
remoteDeployFile, ok := remoteFileMap[key]
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
if err != nil {
return errors.New("upload deploy file '" + filepath.Base(deployFile.Path) + "' failed: " + err.Error())
}
}
}
return nil
}
// 升级NS节点
func (this *Upgrader) upgradeNSNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
// 本地的
var manager = NewDeployManager()
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
for _, deployFile := range manager.LoadNSNodeFiles() {
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
}
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
if err != nil {
return err
}
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
for _, nodeFile := range remoteFilesResp.NsNodeDeployFiles {
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
}
// 对比版本
for key, deployFile := range localFileMap {
remoteDeployFile, ok := remoteFileMap[key]
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
if err != nil {
return errors.New("upload deploy file '" + filepath.Base(deployFile.Path) + "' failed: " + err.Error())
}
}
}
return nil
}
// 上传节点文件
func (this *Upgrader) uploadNodeDeployFile(ctx context.Context, rpcClient *rpc.RPCClient, path string) error {
fp, err := os.Open(path)
if err != nil {
return err
}
defer func() {
_ = fp.Close()
}()
var buf = make([]byte, 128*4096)
var isFirst = true
var hash = md5.New()
for {
n, err := fp.Read(buf)
if n > 0 {
hash.Write(buf[:n])
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
Filename: filepath.Base(path),
Sum: "",
ChunkData: buf[:n],
IsFirstChunk: isFirst,
IsLastChunk: false,
})
if uploadErr != nil {
return uploadErr
}
isFirst = false
}
if err != nil {
if err == io.EOF {
err = nil
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
Filename: filepath.Base(path),
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
ChunkData: nil,
IsFirstChunk: false,
IsLastChunk: true,
})
if uploadErr != nil {
return uploadErr
}
break
}
return err
}
}
return nil
}

View File

@@ -39,7 +39,7 @@ func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool,
return false, "is directory"
}
localVersion, err := localVersion()
localVersion, err := lookupLocalVersion()
if err != nil {
return false, "lookup version failed: " + err.Error()
}
@@ -53,9 +53,7 @@ func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool,
return true, ""
}
func localVersion() (string, error) {
func lookupLocalVersion() (string, error) {
var cmd = exec.Command(apiExe(), "-V")
var output = &bytes.Buffer{}
cmd.Stdout = output
@@ -74,7 +72,6 @@ func localVersion() (string, error) {
return localVersion, nil
}
func apiExe() string {
return Tea.Root + "/edge-api/bin/edge-api"
}
}

View File

@@ -75,7 +75,7 @@ func FormatCount(count int64) string {
return fmt.Sprintf("%.1fB", float32(count)/1000/1000/1000)
}
func FormatFloat(f interface{}, decimal int) string {
func FormatFloat(f any, decimal int) string {
if f == nil {
return ""
}
@@ -101,10 +101,29 @@ func FormatFloat(f interface{}, decimal int) string {
return ""
}
func FormatFloat2(f interface{}) string {
func FormatFloat2(f any) string {
return FormatFloat(f, 2)
}
// PadFloatZero 为浮点型数字字符串填充足够的0
func PadFloatZero(s string, countZero int) string {
if countZero <= 0 {
return s
}
if len(s) == 0 {
s = "0"
}
var index = strings.Index(s, ".")
if index < 0 {
return s + "." + strings.Repeat("0", countZero)
}
var decimalLen = len(s) - 1 - index
if decimalLen < countZero {
return s + strings.Repeat("0", countZero-decimalLen)
}
return s
}
var decimalReg = regexp.MustCompile(`^(\d+\.\d+)([a-zA-Z]+)?$`)
// TrimZeroSuffix 去除小数数字尾部多余的0

View File

@@ -4,6 +4,7 @@ package numberutils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/iwind/TeaGo/assert"
"testing"
)
@@ -49,6 +50,24 @@ func TestFormatFloat(t *testing.T) {
t.Log(numberutils.FormatFloat(-221745.12, 2))
}
func TestFormatFloat2(t *testing.T) {
t.Log(numberutils.FormatFloat2(0))
t.Log(numberutils.FormatFloat2(0.0))
t.Log(numberutils.FormatFloat2(1.23456))
t.Log(numberutils.FormatFloat2(1.0))
}
func TestPadFloatZero(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsTrue(numberutils.PadFloatZero("1", 0) == "1")
a.IsTrue(numberutils.PadFloatZero("1", 2) == "1.00")
a.IsTrue(numberutils.PadFloatZero("1.1", 2) == "1.10")
a.IsTrue(numberutils.PadFloatZero("1.12", 2) == "1.12")
a.IsTrue(numberutils.PadFloatZero("1.123", 2) == "1.123")
a.IsTrue(numberutils.PadFloatZero("10000.123", 2) == "10000.123")
a.IsTrue(numberutils.PadFloatZero("", 2) == "0.00")
}
func TestTrimZeroSuffix(t *testing.T) {
for _, s := range []string{
"1",

View File

@@ -225,7 +225,7 @@ func (this *NodesAction) RunGet(params struct {
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"trafficInBytes": status.TrafficInBytes,
"trafficOutBytes": status.TrafficOutBytes,
"load1m": numberutils.FormatFloat2(status.Load1m),
"load1m": numberutils.PadFloatZero(numberutils.FormatFloat2(status.Load1m), 2),
"countConnections": status.ConnectionCount,
},
"cluster": maps.Map{

View File

@@ -222,12 +222,12 @@ func (this *NodesAction) RunGet(params struct {
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": numberutils.FormatFloat2(status.CPUUsage * 100),
"cpuUsageText": numberutils.FormatFloat2(status.CPUUsage * 100) + "%",
"memUsage": status.MemoryUsage,
"memUsageText": numberutils.FormatFloat2(status.MemoryUsage * 100),
"memUsageText": numberutils.FormatFloat2(status.MemoryUsage * 100) + "%",
"trafficInBytes": status.TrafficInBytes,
"trafficOutBytes": status.TrafficOutBytes,
"load1m": numberutils.FormatFloat2(status.Load1m),
"load1m": numberutils.PadFloatZero(numberutils.FormatFloat2(status.Load1m), 2),
"countConnections": status.ConnectionCount,
},
"cluster": maps.Map{

View File

@@ -15,6 +15,9 @@ func (this *CheckAction) RunPost(params struct {
HasError bool
IsUpdated bool
}) {
var isStream = this.Request.ProtoMajor >= 2
this.Data["isStream"] = isStream
var maxTries = 10
for i := 0; i < maxTries; i++ {
resp, err := this.RPC().NodeTaskRPC().ExistsNodeTasks(this.AdminContext(), &pb.ExistsNodeTasksRequest{
@@ -26,7 +29,7 @@ func (this *CheckAction) RunPost(params struct {
}
// 如果没有数据变化,继续查询
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError && isStream {
time.Sleep(3 * time.Second)
continue
}

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"sort"
)
// DomainOptionsAction 域名列表选项
@@ -21,10 +22,18 @@ func (this *DomainOptionsAction) RunPost(params struct {
this.ErrorPage(err)
return
}
domainMaps := []maps.Map{}
// 排序
if len(domainsResp.DnsDomains) > 0 {
sort.Slice(domainsResp.DnsDomains, func(i, j int) bool {
return domainsResp.DnsDomains[i].Name < domainsResp.DnsDomains[j].Name
})
}
var domainMaps = []maps.Map{}
for _, domain := range domainsResp.DnsDomains {
// 未开启或者已删除的先跳过
if !domain.IsOn || domain.IsDeleted {
if !domain.IsOn || domain.IsDeleted || !domain.IsUp {
continue
}

View File

@@ -15,6 +15,9 @@ func (this *CheckAction) RunPost(params struct {
HasError bool
IsUpdated bool
}) {
var isStream = this.Request.ProtoMajor >= 2
this.Data["isStream"] = isStream
var maxTries = 10
for i := 0; i < maxTries; i++ {
resp, err := this.RPC().DNSTaskRPC().ExistsDNSTasks(this.AdminContext(), &pb.ExistsDNSTasksRequest{})
@@ -24,7 +27,7 @@ func (this *CheckAction) RunPost(params struct {
}
// 如果没有数据变化,继续查询
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError && isStream {
time.Sleep(3 * time.Second)
continue
}

View File

@@ -4,16 +4,41 @@ package loginutils
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/iwind/TeaGo/actions"
stringutil "github.com/iwind/TeaGo/utils/string"
"net"
"net/http"
)
// CalculateClientFingerprint 计算客户端指纹
func CalculateClientFingerprint(action *actions.ActionObject) string {
return stringutil.Md5(action.RequestRemoteIP() + "@" + action.Request.UserAgent())
return stringutil.Md5(RemoteIP(action) + "@" + action.Request.UserAgent())
}
// RemoteIP 获取客户端IP
// TODO 将来增加是否使用代理设置即从X-Real-IP中获取IP
func RemoteIP(action *actions.ActionObject) string {
ip, _, _ := net.SplitHostPort(action.Request.RemoteAddr)
return ip
}
// LookupIPRegion 查找登录区域
func LookupIPRegion(ip string) string {
if len(ip) == 0 {
return ""
}
var result = iplibrary.LookupIP(ip)
if result != nil && result.IsOk() {
// 这里不需要网络运营商信息
return result.CountryName() + "@" + result.ProvinceName() + "@" + result.CityName() + "@" + result.TownName()
}
return ""
}
// SetCookie 设置Cookie
func SetCookie(action *actions.ActionObject, remember bool) {
if remember {
var cookie = &http.Cookie{
@@ -44,6 +69,7 @@ func SetCookie(action *actions.ActionObject, remember bool) {
}
}
// UnsetCookie 重置Cookie
func UnsetCookie(action *actions.ActionObject) {
cookie := &http.Cookie{
Name: teaconst.CookieSID,

View File

@@ -18,31 +18,6 @@ func (this *CreateAction) Init() {
}
func (this *CreateAction) RunGet(params struct{}) {
// 获取所有可用的用户
usersResp, err := this.RPC().ACMEUserRPC().FindAllACMEUsers(this.AdminContext(), &pb.FindAllACMEUsersRequest{
AdminId: this.AdminId(),
UserId: 0,
})
if err != nil {
this.ErrorPage(err)
return
}
userMaps := []maps.Map{}
for _, user := range usersResp.AcmeUsers {
description := user.Description
if len(description) > 0 {
description = "" + description + ""
}
userMaps = append(userMaps, maps.Map{
"id": user.Id,
"description": description,
"email": user.Email,
"providerCode": user.AcmeProviderCode,
})
}
this.Data["users"] = userMaps
// 证书服务商
providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{})
if err != nil {
@@ -81,14 +56,15 @@ func (this *CreateAction) RunGet(params struct{}) {
}
func (this *CreateAction) RunPost(params struct {
TaskId int64
AuthType string
AcmeUserId int64
DnsProviderId int64
DnsDomain string
Domains []string
AutoRenew bool
AuthURL string
PlatformUserId int64
TaskId int64
AuthType string
AcmeUserId int64
DnsProviderId int64
DnsDomain string
Domains []string
AutoRenew bool
AuthURL string
Must *actions.Must
}) {
@@ -117,7 +93,7 @@ func (this *CreateAction) RunPost(params struct {
if len(params.Domains) == 0 {
this.Fail("请输入证书域名列表")
}
realDomains := []string{}
var realDomains = []string{}
for _, domain := range params.Domains {
domain = strings.ToLower(domain)
if params.AuthType == "dns" { // DNS认证
@@ -134,6 +110,7 @@ func (this *CreateAction) RunPost(params struct {
if params.TaskId == 0 {
createResp, err := this.RPC().ACMETaskRPC().CreateACMETask(this.AdminContext(), &pb.CreateACMETaskRequest{
UserId: params.PlatformUserId,
AuthType: params.AuthType,
AcmeUserId: params.AcmeUserId,
DnsProviderId: params.DnsProviderId,

View File

@@ -17,22 +17,48 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct {
UserId int64
Type string
Keyword string
}) {
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
countAll := int64(0)
countAvailable := int64(0)
countExpired := int64(0)
count7Days := int64(0)
count30Days := int64(0)
// 当前用户
this.Data["searchingUserId"] = params.UserId
var userMap = maps.Map{
"id": 0,
"username": "",
"fullname": "",
}
if params.UserId > 0 {
userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
if err != nil {
this.ErrorPage(err)
return
}
var user = userResp.User
if user != nil {
userMap = maps.Map{
"id": user.Id,
"username": user.Username,
"fullname": user.Fullname,
}
}
}
this.Data["user"] = userMap
var countAll int64
var countAvailable int64
var countExpired int64
var count7Days int64
var count30Days int64
// 计算数量
{
// all
resp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
Keyword: params.Keyword,
})
if err != nil {
@@ -43,6 +69,7 @@ func (this *IndexAction) RunGet(params struct {
// available
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
IsAvailable: true,
Keyword: params.Keyword,
})
@@ -54,6 +81,7 @@ func (this *IndexAction) RunGet(params struct {
// expired
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
IsExpired: true,
Keyword: params.Keyword,
})
@@ -65,6 +93,7 @@ func (this *IndexAction) RunGet(params struct {
// expire in 7 days
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
ExpiringDays: 7,
Keyword: params.Keyword,
})
@@ -76,6 +105,7 @@ func (this *IndexAction) RunGet(params struct {
// expire in 30 days
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
ExpiringDays: 30,
Keyword: params.Keyword,
})
@@ -100,25 +130,51 @@ func (this *IndexAction) RunGet(params struct {
case "":
page = this.NewPage(countAll)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "available":
page = this.NewPage(countAvailable)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsAvailable: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
IsAvailable: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "expired":
page = this.NewPage(countExpired)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsExpired: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
IsExpired: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "7days":
page = this.NewPage(count7Days)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 7, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
ExpiringDays: 7,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "30days":
page = this.NewPage(count30Days)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 30, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
ExpiringDays: 30,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
default:
page = this.NewPage(countAll)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
@@ -131,7 +187,7 @@ func (this *IndexAction) RunGet(params struct {
this.Data["page"] = page.AsHTML()
taskMaps := []maps.Map{}
var taskMaps = []maps.Map{}
for _, task := range tasksResp.AcmeTasks {
if task.AcmeUser == nil {
continue

View File

@@ -12,6 +12,8 @@ type RunAction struct {
func (this *RunAction) RunPost(params struct {
TaskId int64
}) {
defer this.CreateLogInfo("执行ACME任务 %d", params.TaskId)
runResp, err := this.RPC().ACMETaskRPC().RunACMETask(this.AdminContext(), &pb.RunACMETaskRequest{AcmeTaskId: params.TaskId})
if err != nil {
this.ErrorPage(err)

View File

@@ -0,0 +1,44 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package acme
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type UserOptionsAction struct {
actionutils.ParentAction
}
func (this *UserOptionsAction) RunPost(params struct {
PlatformUserId int64
}) {
// 获取所有可用的用户
usersResp, err := this.RPC().ACMEUserRPC().FindAllACMEUsers(this.AdminContext(), &pb.FindAllACMEUsersRequest{
AdminId: 0,
UserId: params.PlatformUserId,
})
if err != nil {
this.ErrorPage(err)
return
}
var userMaps = []maps.Map{}
for _, user := range usersResp.AcmeUsers {
description := user.Description
if len(description) > 0 {
description = "" + description + ""
}
userMaps = append(userMaps, maps.Map{
"id": user.Id,
"description": description,
"email": user.Email,
"providerCode": user.AcmeProviderCode,
})
}
this.Data["users"] = userMaps
this.Success()
}

View File

@@ -16,10 +16,30 @@ func (this *CreatePopupAction) Init() {
}
func (this *CreatePopupAction) RunGet(params struct {
ProviderCode string
PlatformUserId int64
ProviderCode string
}) {
this.Data["platformUserId"] = params.PlatformUserId
this.Data["providerCode"] = params.ProviderCode
// 平台用户信息
this.Data["platformUser"] = nil
if params.PlatformUserId > 0 {
platformUserResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.PlatformUserId})
if err != nil {
this.ErrorPage(err)
return
}
var platformUser = platformUserResp.User
if platformUser != nil {
this.Data["platformUser"] = maps.Map{
"id": platformUser.Id,
"username": platformUser.Username,
"fullname": platformUser.Fullname,
}
}
}
// 服务商
providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{})
if err != nil {
@@ -40,10 +60,11 @@ func (this *CreatePopupAction) RunGet(params struct {
}
func (this *CreatePopupAction) RunPost(params struct {
Email string
ProviderCode string
AccountId int64
Description string
PlatformUserId int64
Email string
ProviderCode string
AccountId int64
Description string
Must *actions.Must
CSRF *actionutils.CSRF
@@ -85,6 +106,7 @@ func (this *CreatePopupAction) RunPost(params struct {
}
createResp, err := this.RPC().ACMEUserRPC().CreateACMEUser(this.AdminContext(), &pb.CreateACMEUserRequest{
UserId: params.PlatformUserId,
Email: params.Email,
Description: params.Description,
AcmeProviderCode: params.ProviderCode,

View File

@@ -19,12 +19,37 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct {
UserId int64
Type string
Keyword string
}) {
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
// 当前用户
this.Data["searchingUserId"] = params.UserId
var userMap = maps.Map{
"id": 0,
"username": "",
"fullname": "",
}
if params.UserId > 0 {
userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
if err != nil {
this.ErrorPage(err)
return
}
var user = userResp.User
if user != nil {
userMap = maps.Map{
"id": user.Id,
"username": user.Username,
"fullname": user.Fullname,
}
}
}
this.Data["user"] = userMap
var countAll = int64(0)
var countCA = int64(0)
var countAvailable = int64(0)
@@ -36,6 +61,7 @@ func (this *IndexAction) RunGet(params struct {
{
// all
resp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
Keyword: params.Keyword,
})
if err != nil {
@@ -46,6 +72,7 @@ func (this *IndexAction) RunGet(params struct {
// CA
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
IsCA: true,
Keyword: params.Keyword,
})
@@ -57,6 +84,7 @@ func (this *IndexAction) RunGet(params struct {
// available
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
IsAvailable: true,
Keyword: params.Keyword,
})
@@ -68,6 +96,7 @@ func (this *IndexAction) RunGet(params struct {
// expired
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
IsExpired: true,
Keyword: params.Keyword,
})
@@ -79,6 +108,7 @@ func (this *IndexAction) RunGet(params struct {
// expire in 7 days
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
ExpiringDays: 7,
Keyword: params.Keyword,
})
@@ -90,6 +120,7 @@ func (this *IndexAction) RunGet(params struct {
// expire in 30 days
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
ExpiringDays: 30,
Keyword: params.Keyword,
})
@@ -115,28 +146,60 @@ func (this *IndexAction) RunGet(params struct {
case "":
page = this.NewPage(countAll)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "ca":
page = this.NewPage(countCA)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsCA: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
IsCA: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "available":
page = this.NewPage(countAvailable)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsAvailable: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
IsAvailable: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "expired":
page = this.NewPage(countExpired)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsExpired: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
IsExpired: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "7days":
page = this.NewPage(count7Days)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{ExpiringDays: 7, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
ExpiringDays: 7,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "30days":
page = this.NewPage(count30Days)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{ExpiringDays: 30, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
ExpiringDays: 30,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
default:
page = this.NewPage(countAll)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
@@ -158,7 +221,9 @@ func (this *IndexAction) RunGet(params struct {
var certMaps = []maps.Map{}
var nowTime = time.Now().Unix()
for _, certConfig := range certConfigs {
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithSSLCertId(this.AdminContext(), &pb.CountAllEnabledServersWithSSLCertIdRequest{SslCertId: certConfig.Id})
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithSSLCertId(this.AdminContext(), &pb.CountAllEnabledServersWithSSLCertIdRequest{
SslCertId: certConfig.Id,
})
if err != nil {
this.ErrorPage(err)
return

View File

@@ -41,6 +41,7 @@ func init() {
Post("/run", new(acme.RunAction)).
GetPost("/updateTaskPopup", new(acme.UpdateTaskPopupAction)).
Post("/deleteTask", new(acme.DeleteTaskAction)).
Post("/userOptions", new(acme.UserOptionsAction)).
// ACME用户
Prefix("/servers/certs/acme/users").

View File

@@ -34,6 +34,8 @@ func (this *SelectPopupAction) RunGet(params struct {
SelectedCertIds string
Keyword string
}) {
this.Data["searchingServerId"] = params.ServerId
// 服务相关
if params.ServerId > 0 {
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: params.ServerId})
@@ -66,7 +68,8 @@ func (this *SelectPopupAction) RunGet(params struct {
}
// 用户相关
this.Data["userId"] = params.UserId
this.Data["userId"] = params.UserId // 可变
this.Data["searchingUserId"] = params.UserId
// 域名搜索相关
var url = this.Request.URL.Path
@@ -82,6 +85,7 @@ func (this *SelectPopupAction) RunGet(params struct {
if len(searchingDomains) > maxDomains {
searchingDomains = searchingDomains[:maxDomains]
}
this.Data["allSearchingDomains"] = params.SearchingDomains
this.Data["searchingDomains"] = searchingDomains
this.Data["keyword"] = params.Keyword

View File

@@ -45,7 +45,7 @@ func KeyFailReason(reasonCode string) string {
case "requireDomain":
return "找不到Key对应的域名"
case "requireServer":
return "找不到Key对应的网站服务"
return "找不到Key对应的网站"
case "requireUser":
return "该域名不属于当前用户"
case "requireClusterId":

View File

@@ -64,7 +64,7 @@ func (this *IndexAction) RunPost(params struct {
DefaultDomain string
}) {
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "保存网站服务全局配置")
defer this.CreateLog(oplogs.LevelInfo, "保存网站全局配置")
if len(params.GlobalConfigJSON) == 0 {
this.Fail("错误的配置信息,请刷新当前页面后重试")

View File

@@ -107,7 +107,7 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
// TABBAR
selectedTabbar, _ := action.Data["mainTab"]
tabbar := actionutils.NewTabbar()
tabbar.Add("服务列表", "", "/servers", "", false)
tabbar.Add("网站列表", "", "/servers", "", false)
if teaconst.IsPlus {
tabbar.Add("看板", "", "/servers/server/boards?serverId="+serverIdString, "dashboard", selectedTabbar == "board")
}

View File

@@ -79,6 +79,9 @@ func (this *IndexAction) RunPost(params struct {
DenySearchEngines bool
DenySpiders bool
CheckClientFingerprint bool
CheckClientRegion bool
DomainsJSON []byte
Must *actions.Must
@@ -150,6 +153,10 @@ func (this *IndexAction) RunPost(params struct {
// 允许记住登录
config.AllowRememberLogin = params.AllowRememberLogin
// Cookie检查
config.CheckClientFingerprint = params.CheckClientFingerprint
config.CheckClientRegion = params.CheckClientRegion
err = configloaders.UpdateSecurityConfig(config)
if err != nil {
this.ErrorPage(err)

View File

@@ -19,8 +19,8 @@ func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64, countUnread
{
"code": "servers",
"module": configloaders.AdminModuleCodeServer,
"name": "网站服务",
"subtitle": "服务列表",
"name": "网站列表",
"subtitle": "",
"icon": "clone outsize",
"subItems": []maps.Map{
{

View File

@@ -170,18 +170,42 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
var adminId = session.GetInt64("adminId")
if adminId <= 0 {
var errString = session.GetString("@error")
if len(errString) > 0 {
action.WriteString("read session failed: " + errString)
return false
}
this.login(action)
return false
}
// 检查指纹
var clientFingerprint = session.GetString("@fingerprint")
if len(clientFingerprint) > 0 && clientFingerprint != loginutils.CalculateClientFingerprint(action) {
loginutils.UnsetCookie(action)
session.Delete()
if securityConfig != nil && securityConfig.CheckClientFingerprint {
var clientFingerprint = session.GetString("@fingerprint")
if len(clientFingerprint) > 0 && clientFingerprint != loginutils.CalculateClientFingerprint(action) {
loginutils.UnsetCookie(action)
session.Delete()
this.login(action)
return false
this.login(action)
return false
}
}
// 检查区域
if securityConfig != nil && securityConfig.CheckClientRegion {
var oldClientIP = session.GetString("@ip")
var currentClientIP = loginutils.RemoteIP(action)
if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP {
var oldRegion = loginutils.LookupIPRegion(oldClientIP)
var newRegion = loginutils.LookupIPRegion(currentClientIP)
if newRegion != oldRegion {
loginutils.UnsetCookie(action)
session.Delete()
this.login(action)
return false
}
}
}
// 检查用户是否存在

View File

@@ -58,6 +58,7 @@ func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool) {
var session = this.action.Session()
session.Write("adminId", numberutils.FormatInt64(adminId))
session.Write("@fingerprint", loginutils.CalculateClientFingerprint(this.action))
session.Write("@ip", loginutils.RemoteIP(this.action))
}
func (this *UserShouldAuth) IsUser() bool {

View File

@@ -1962,7 +1962,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
<td class="title">启用WAF</td>
<td>
<checkbox v-model="firewall.isOn"></checkbox>
<p class="comment">选中后,表示启用当前网站服务的WAF功能。</p>
<p class="comment">选中后表示启用当前网站的WAF功能。</p>
</td>
</tr>
</tbody>
@@ -2443,7 +2443,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
<input type="text" placeholder="搜索域名" ref="keywordRef" class="ui input tiny" v-model="keyword"/>
</div>
</div>
</div>`}),Vue.component("uam-config-box",{props:["v-uam-config","v-is-location","v-is-group"],data:function(){let e=this.vUamConfig;return null==(e=null==e?{isPrior:!1,isOn:!1,onlyURLPatterns:[],exceptURLPatterns:[]}:e).onlyURLPatterns&&(e.onlyURLPatterns=[]),null==e.exceptURLPatterns&&(e.exceptURLPatterns=[]),{config:e,moreOptionsVisible:!1}},methods:{showMoreOptions:function(){this.moreOptionsVisible=!this.moreOptionsVisible}},template:`<div>
</div>`}),Vue.component("uam-config-box",{props:["v-uam-config","v-is-location","v-is-group"],data:function(){let e=this.vUamConfig;return null==(e=null==e?{isPrior:!1,isOn:!1,addToWhiteList:!0,onlyURLPatterns:[],exceptURLPatterns:[]}:e).onlyURLPatterns&&(e.onlyURLPatterns=[]),null==e.exceptURLPatterns&&(e.exceptURLPatterns=[]),{config:e,moreOptionsVisible:!1}},methods:{showMoreOptions:function(){this.moreOptionsVisible=!this.moreOptionsVisible}},template:`<div>
<input type="hidden" name="uamJSON" :value="JSON.stringify(config)"/>
<table class="ui table definition selectable">
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
@@ -2462,6 +2462,13 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
</tr>
</tbody>
<tbody v-show="moreOptionsVisible">
<tr>
<td>加入IP白名单</td>
<td>
<checkbox v-model="config.addToWhiteList"></checkbox>
<p class="comment">选中后表示验证通过后将访问者IP加入到临时白名单中此IP下次访问时不再校验5秒盾此白名单只对5秒盾有效不影响其他规则。此选项主要用于可能无法正常使用Cookie的网站。</p>
</td>
</tr>
<tr>
<td>例外URL</td>
<td>
@@ -2788,8 +2795,8 @@ example2.com
<option value="service">当前服务</option>
<option value="global">所有服务</option>
</select>
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站服务的访问,其他服务不受影响。</p>
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站服务的访问。</p>
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站的访问,其他服务不受影响。</p>
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站的访问。</p>
</td>
</tr>
<tr v-if="actionCode == 'block'">
@@ -3060,8 +3067,8 @@ example2.com
<button class="ui button small" type="button" @click.prevent="add">+添加鉴权方式</button>
</div>
<div class="margin"></div>
</div>`}),Vue.component("user-selector",{props:["v-user-id","data-url"],data:function(){let e=this.vUserId,t=(null==e&&(e=0),this.dataUrl);return null!=t&&0!=t.length||(t="/servers/users/options"),{users:[],userId:e,dataURL:t}},methods:{change:function(e){null!=e?this.$emit("change",e.id):this.$emit("change",0)}},template:`<div>
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change"></combo-box>
</div>`}),Vue.component("user-selector",{props:["v-user-id","data-url"],data:function(){let e=this.vUserId,t=(null==e&&(e=0),this.dataUrl);return null!=t&&0!=t.length||(t="/servers/users/options"),{users:[],userId:e,dataURL:t}},methods:{change:function(e){null!=e?this.$emit("change",e.id):this.$emit("change",0)},clear:function(){this.$refs.comboBox.clear()}},template:`<div>
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change" ref="comboBox"></combo-box>
</div>`}),Vue.component("http-header-policy-box",{props:["v-request-header-policy","v-request-header-ref","v-response-header-policy","v-response-header-ref","v-params","v-is-location","v-is-group","v-has-group-request-config","v-has-group-response-config","v-group-setting-url"],data:function(){let e="response";"#request"==window.location.hash&&(e="request");let t=this.vRequestHeaderRef,i=(null==t&&(t={isPrior:!1,isOn:!0,headerPolicyId:0}),this.vResponseHeaderRef),n=(null==i&&(i={isPrior:!1,isOn:!0,headerPolicyId:0}),[]),s=[];var o=this.vRequestHeaderPolicy;null!=o&&(null!=o.setHeaders&&(n=o.setHeaders),null!=o.deleteHeaders&&(s=o.deleteHeaders));let a=[],l=[];o=this.vResponseHeaderPolicy;null!=o&&(null!=o.setHeaders&&(a=o.setHeaders),null!=o.deleteHeaders&&(l=o.deleteHeaders));let c={isOn:!1};return null!=o.cors&&(c=o.cors),{type:e,typeName:"request"==e?"请求":"响应",requestHeaderRef:t,responseHeaderRef:i,requestSettingHeaders:n,requestDeletingHeaders:s,responseSettingHeaders:a,responseDeletingHeaders:l,responseCORS:c}},methods:{selectType:function(e){this.type=e,window.location.hash="#"+e,window.location.reload()},addSettingHeader:function(e){teaweb.popup("/servers/server/settings/headers/createSetPopup?"+this.vParams+"&headerPolicyId="+e+"&type="+this.type,{callback:function(){teaweb.successRefresh("保存成功")}})},addDeletingHeader:function(e,t){teaweb.popup("/servers/server/settings/headers/createDeletePopup?"+this.vParams+"&headerPolicyId="+e+"&type="+t,{callback:function(){teaweb.successRefresh("保存成功")}})},updateSettingPopup:function(e,t){teaweb.popup("/servers/server/settings/headers/updateSetPopup?"+this.vParams+"&headerPolicyId="+e+"&headerId="+t+"&type="+this.type,{callback:function(){teaweb.successRefresh("保存成功")}})},deleteDeletingHeader:function(e,t){teaweb.confirm("确定要删除'"+t+"'吗?",function(){Tea.action("/servers/server/settings/headers/deleteDeletingHeader").params({headerPolicyId:e,headerName:t}).post().refresh()})},deleteHeader:function(e,t,i){teaweb.confirm("确定要删除此Header吗",function(){this.$post("/servers/server/settings/headers/delete").params({headerPolicyId:e,type:t,headerId:i}).refresh()})},updateCORS:function(e){teaweb.popup("/servers/server/settings/headers/updateCORSPopup?"+this.vParams+"&headerPolicyId="+e+"&type="+this.type,{callback:function(){teaweb.successRefresh("保存成功")}})}},template:`<div>
<div class="ui menu tabular small">
<a class="item" :class="{active:type == 'response'}" @click.prevent="selectType('response')">响应Header<span v-if="responseSettingHeaders.length > 0">({{responseSettingHeaders.length}})</span></a>
@@ -3565,12 +3572,12 @@ example2.com
</tr>
</tbody>
</table>
</div>`}),Vue.component("http-access-log-box",{props:["v-access-log","v-keyword","v-show-server-link"],data:function(){let e=this.vAccessLog;if(null!=e.header&&null!=e.header.Upgrade&&null!=e.header.Upgrade.values&&e.header.Upgrade.values.$contains("websocket")&&("http"==e.scheme?e.scheme="ws":"https"==e.scheme&&(e.scheme="wss")),null!=e.tags&&0<e.tags.length){let n={};e.tags=e.tags.$filter(function(e,t){var i=void 0===n[t];return n[t]=!0,i})}var t;return e.unicodeHost="",null!=e.host&&e.host.startsWith("xn--")&&(t=e.host.indexOf(":"),e.unicodeHost=0<t?punycode.ToUnicode(e.host.substring(0,t)):punycode.ToUnicode(e.host)),{accessLog:e}},methods:{formatCost:function(e){if(null==e)return"0";let t=(1e3*e).toString(),i=t.split(".");return i.length<2?t:i[0]+"."+i[1].substring(0,3)},showLog:function(){let e=this;var t=this.accessLog.requestId;this.$parent.$children.forEach(function(e){null!=e.deselect&&e.deselect()}),this.select(),teaweb.popup("/servers/server/log/viewPopup?requestId="+t,{width:"50em",height:"28em",onClose:function(){e.deselect()}})},select:function(){this.$refs.box.parentNode.style.cssText="background: rgba(0, 0, 0, 0.1)"},deselect:function(){this.$refs.box.parentNode.style.cssText=""},mismatch:function(){teaweb.warn("当前访问没有匹配到任何网站服务")}},template:`<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
</div>`}),Vue.component("http-access-log-box",{props:["v-access-log","v-keyword","v-show-server-link"],data:function(){let e=this.vAccessLog;if(null!=e.header&&null!=e.header.Upgrade&&null!=e.header.Upgrade.values&&e.header.Upgrade.values.$contains("websocket")&&("http"==e.scheme?e.scheme="ws":"https"==e.scheme&&(e.scheme="wss")),null!=e.tags&&0<e.tags.length){let n={};e.tags=e.tags.$filter(function(e,t){var i=void 0===n[t];return n[t]=!0,i})}var t;return e.unicodeHost="",null!=e.host&&e.host.startsWith("xn--")&&(t=e.host.indexOf(":"),e.unicodeHost=0<t?punycode.ToUnicode(e.host.substring(0,t)):punycode.ToUnicode(e.host)),{accessLog:e}},methods:{formatCost:function(e){if(null==e)return"0";let t=(1e3*e).toString(),i=t.split(".");return i.length<2?t:i[0]+"."+i[1].substring(0,3)},showLog:function(){let e=this;var t=this.accessLog.requestId;this.$parent.$children.forEach(function(e){null!=e.deselect&&e.deselect()}),this.select(),teaweb.popup("/servers/server/log/viewPopup?requestId="+t,{width:"50em",height:"28em",onClose:function(){e.deselect()}})},select:function(){this.$refs.box.parentNode.style.cssText="background: rgba(0, 0, 0, 0.1)"},deselect:function(){this.$refs.box.parentNode.style.cssText=""},mismatch:function(){teaweb.warn("当前访问没有匹配到任何网站")}},template:`<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
<div>
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a>
<!-- 服务 -->
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[服务]</span></span>
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
@@ -4834,7 +4841,7 @@ example2.com
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站服务的访问。</p>
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站的访问。</p>
</td>
</tr>
<tr>
@@ -6019,7 +6026,7 @@ example2.com
<td class="title">已选中防护对象</td>
<td>
<div v-for="(object, index) in objects" class="ui label basic small" style="margin-bottom: 0.5em">
<span v-if="object.type == 'server'">网站服务{{object.name}}</span>
<span v-if="object.type == 'server'">网站:{{object.name}}</span>
&nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</div>
</td>
@@ -6034,18 +6041,18 @@ example2.com
<table class="ui table celled">
<tr>
<td class="title">对象类型</td>
<td>网站服务</td>
<td>网站</td>
</tr>
<!-- 服务列表 -->
<tr>
<td>服务列表</td>
<td>网站列表</td>
<td>
<span v-if="serversIsLoading">加载中...</span>
<div v-if="!serversIsLoading && servers.length == 0">暂时还没有可选的网站服务。</div>
<div v-if="!serversIsLoading && servers.length == 0">暂时还没有可选的网站。</div>
<table class="ui table" v-show="!serversIsLoading && servers.length > 0">
<thead class="full-width">
<tr>
<th>网站服务名称</th>
<th>网站名称</th>
<th class="one op">操作</th>
</tr>
</thead>

View File

@@ -5390,7 +5390,7 @@ Vue.component("http-firewall-config-box", {
<td class="title">启用WAF</td>
<td>
<checkbox v-model="firewall.isOn"></checkbox>
<p class="comment">选中后,表示启用当前网站服务的WAF功能。</p>
<p class="comment">选中后表示启用当前网站的WAF功能。</p>
</td>
</tr>
</tbody>
@@ -7270,6 +7270,7 @@ Vue.component("uam-config-box", {
config = {
isPrior: false,
isOn: false,
addToWhiteList: true,
onlyURLPatterns: [],
exceptURLPatterns: []
}
@@ -7309,6 +7310,13 @@ Vue.component("uam-config-box", {
</tr>
</tbody>
<tbody v-show="moreOptionsVisible">
<tr>
<td>加入IP白名单</td>
<td>
<checkbox v-model="config.addToWhiteList"></checkbox>
<p class="comment">选中后表示验证通过后将访问者IP加入到临时白名单中此IP下次访问时不再校验5秒盾此白名单只对5秒盾有效不影响其他规则。此选项主要用于可能无法正常使用Cookie的网站。</p>
</td>
</tr>
<tr>
<td>例外URL</td>
<td>
@@ -8573,8 +8581,8 @@ Vue.component("http-firewall-actions-box", {
<option value="service">当前服务</option>
<option value="global">所有服务</option>
</select>
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站服务的访问,其他服务不受影响。</p>
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站服务的访问。</p>
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站的访问,其他服务不受影响。</p>
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站的访问。</p>
</td>
</tr>
<tr v-if="actionCode == 'block'">
@@ -8949,10 +8957,13 @@ Vue.component("user-selector", {
} else {
this.$emit("change", 0)
}
},
clear: function () {
this.$refs.comboBox.clear()
}
},
template: `<div>
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change"></combo-box>
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change" ref="comboBox"></combo-box>
</div>`
})
@@ -10138,7 +10149,7 @@ Vue.component("http-access-log-box", {
this.$refs.box.parentNode.style.cssText = ""
},
mismatch: function () {
teaweb.warn("当前访问没有匹配到任何网站服务")
teaweb.warn("当前访问没有匹配到任何网站")
}
},
template: `<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
@@ -10146,7 +10157,7 @@ Vue.component("http-access-log-box", {
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a>
<!-- 服务 -->
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[服务]</span></span>
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
@@ -13991,7 +14002,7 @@ Vue.component("http-firewall-captcha-options", {
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站服务的访问。</p>
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站的访问。</p>
</td>
</tr>
<tr>
@@ -19128,7 +19139,7 @@ Vue.component("ad-instance-objects-box", {
<td class="title">已选中防护对象</td>
<td>
<div v-for="(object, index) in objects" class="ui label basic small" style="margin-bottom: 0.5em">
<span v-if="object.type == 'server'">网站服务{{object.name}}</span>
<span v-if="object.type == 'server'">网站:{{object.name}}</span>
&nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</div>
</td>
@@ -19143,18 +19154,18 @@ Vue.component("ad-instance-objects-box", {
<table class="ui table celled">
<tr>
<td class="title">对象类型</td>
<td>网站服务</td>
<td>网站</td>
</tr>
<!-- 服务列表 -->
<tr>
<td>服务列表</td>
<td>网站列表</td>
<td>
<span v-if="serversIsLoading">加载中...</span>
<div v-if="!serversIsLoading && servers.length == 0">暂时还没有可选的网站服务。</div>
<div v-if="!serversIsLoading && servers.length == 0">暂时还没有可选的网站。</div>
<table class="ui table" v-show="!serversIsLoading && servers.length > 0">
<thead class="full-width">
<tr>
<th>网站服务名称</th>
<th>网站名称</th>
<th class="one op">操作</th>
</tr>
</thead>

View File

@@ -73,7 +73,7 @@ Vue.component("http-access-log-box", {
this.$refs.box.parentNode.style.cssText = ""
},
mismatch: function () {
teaweb.warn("当前访问没有匹配到任何网站服务")
teaweb.warn("当前访问没有匹配到任何网站")
}
},
template: `<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
@@ -81,7 +81,7 @@ Vue.component("http-access-log-box", {
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a>
<!-- 服务 -->
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[服务]</span></span>
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>

View File

@@ -663,8 +663,8 @@ Vue.component("http-firewall-actions-box", {
<option value="service">当前服务</option>
<option value="global">所有服务</option>
</select>
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站服务的访问,其他服务不受影响。</p>
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站服务的访问。</p>
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站的访问,其他服务不受影响。</p>
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站的访问。</p>
</td>
</tr>
<tr v-if="actionCode == 'block'">

View File

@@ -147,7 +147,7 @@ Vue.component("http-firewall-captcha-options", {
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站服务的访问。</p>
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站的访问。</p>
</td>
</tr>
<tr>

View File

@@ -49,7 +49,7 @@ Vue.component("http-firewall-config-box", {
<td class="title">启用WAF</td>
<td>
<checkbox v-model="firewall.isOn"></checkbox>
<p class="comment">选中后,表示启用当前网站服务的WAF功能。</p>
<p class="comment">选中后表示启用当前网站的WAF功能。</p>
</td>
</tr>
</tbody>

View File

@@ -24,9 +24,12 @@ Vue.component("user-selector", {
} else {
this.$emit("change", 0)
}
},
clear: function () {
this.$refs.comboBox.clear()
}
},
template: `<div>
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change"></combo-box>
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change" ref="comboBox"></combo-box>
</div>`
})

View File

@@ -2312,7 +2312,9 @@ window.Tea.Action = function (action, params) {
.then(function () {
// console.log("done");
if (typeof (_doneFn) == "function") {
_doneFn.call(Tea.Vue, {});
setTimeout(function () {
_doneFn.call(Tea.Vue, {});
})
}
});
};

View File

@@ -146,6 +146,10 @@ body.expanded .right-box {
display: inline;
}
/** 通用 **/
* {
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
scrollbar-width: thin;
}
.clear {
clear: both;
}
@@ -582,7 +586,7 @@ body.expanded .main {
background: black !important;
}
.main-menu::-webkit-scrollbar {
width: 2px;
width: 4px;
}
.main .tab-menu {
margin-top: 1em !important;

File diff suppressed because one or more lines are too long

View File

@@ -117,6 +117,7 @@ Tea.context(function () {
if (!Tea.Vue.teaCheckNodeTasks) {
return
}
let isStream = false
this.$post("/clusters/tasks/check")
.params({
isDoing: this.doingNodeTasks.isDoing ? 1 : 0,
@@ -128,11 +129,12 @@ Tea.context(function () {
this.doingNodeTasks.isDoing = resp.data.isDoing
this.doingNodeTasks.hasError = resp.data.hasError
this.doingNodeTasks.isUpdated = true
isStream = resp.data.shouldWait
})
.done(function () {
this.$delay(function () {
this.loadNodeTasks()
}, 5000)
}, isStream ? 5000 : 30000)
})
}
@@ -156,6 +158,7 @@ Tea.context(function () {
if (!Tea.Vue.teaCheckDNSTasks) {
return
}
let isStream = false
this.$post("/dns/tasks/check")
.params({
isDoing: this.doingDNSTasks.isDoing ? 1 : 0,
@@ -167,11 +170,12 @@ Tea.context(function () {
this.doingDNSTasks.isDoing = resp.data.isDoing
this.doingDNSTasks.hasError = resp.data.hasError
this.doingDNSTasks.isUpdated = true
isStream = resp.data.isStream
})
.done(function () {
this.$delay(function () {
this.loadDNSTasks()
}, 3000)
}, isStream ? 5000 : 30000)
})
}

View File

@@ -2,6 +2,11 @@
@import "./@grids";
/** 通用 **/
* {
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
scrollbar-width: thin;
}
.clear {
clear: both;
}
@@ -568,7 +573,7 @@ body.expanded .main {
}
.main-menu::-webkit-scrollbar {
width: 2px;
width: 4px;
}
.main {

View File

@@ -1,4 +1,8 @@
/** 通用 **/
* {
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
scrollbar-width: thin;
}
.clear {
clear: both;
}

View File

@@ -1 +1 @@
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":"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","file":"@layout_popup.css"}
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":"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","file":"@layout_popup.css"}

View File

@@ -1,4 +1,9 @@
/** 通用 **/
* {
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
scrollbar-width: thin;
}
.clear {
clear: both;
}

View File

@@ -0,0 +1,107 @@
.left-box {
width: 8em;
position: fixed;
top: 7.5em;
bottom: 2.4em;
overflow-y: auto;
overflow-x: hidden;
border-right: 1px #ddd solid;
}
.left-box .menu {
width: 90% !important;
}
.left-box .menu .item {
line-height: 1.2;
position: relative;
padding-left: 1em !important;
}
.left-box .menu .item .icon {
position: absolute;
top: 50%;
left: 0;
margin-top: -0.4em !important;
}
.left-box .menu .item.separator {
border-bottom: 1px #eee solid !important;
padding-top: 0;
padding-bottom: 0;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.left-box .menu .item.on span {
border-bottom: 1px #666 dashed;
}
.left-box .menu .item.off span var {
font-style: normal;
background: #db2828;
color: white;
font-size: 8px;
padding: 2px;
border-radius: 2px;
margin-left: 1em;
}
.left-box .menu .header {
border-bottom: 1px #ddd solid;
padding-left: 0 !important;
padding-bottom: 1em !important;
}
.left-box::-webkit-scrollbar {
width: 4px;
}
.left-box.disabled {
opacity: 0.1;
}
.left-box.tiny {
top: 10.5em;
}
.left-box.without-tabbar {
top: 3em;
}
.left-box.with-menu {
top: 10em;
}
.left-box.without-menu {
top: 6em;
}
.right-box {
position: fixed;
top: 7.5em;
bottom: 1.3em;
right: 0;
left: 18em;
padding-right: 2em;
padding-bottom: 2em;
overflow-y: auto;
}
@media screen and (max-width: 512px) {
.right-box {
left: 13em;
padding-right: 1em;
}
}
body.expanded .right-box {
left: 10em;
}
.right-box.tiny {
top: 10.4em;
left: 26.5em;
}
.right-box::-webkit-scrollbar {
width: 4px;
}
.right-box.without-tabbar {
top: 3em;
}
.right-box.with-menu {
top: 10em;
}
.right-box.without-menu {
top: 6em;
}
.main.without-footer .left-box {
bottom: 0.2em;
}
.narrow-scrollbar::-webkit-scrollbar {
width: 4px;
}
/*# sourceMappingURL=@left_menu.css.map */

View File

@@ -1 +1 @@
undefined
{"version":3,"sources":["@left_menu.less"],"names":[],"mappings":"AAAA;EACC,UAAA;EACA,eAAA;EACA,UAAA;EACA,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,4BAAA;;AAPD,SASC;EACC,qBAAA;;AAVF,SASC,MAGC;EACC,gBAAA;EACA,kBAAA;EACA,4BAAA;;AAfH,SASC,MAGC,MAKC;EACC,kBAAA;EACA,QAAA;EACA,OAAA;EACA,kBAAA;;AArBJ,SASC,MAgBC,MAAK;EACJ,6BAAA;EACA,cAAA;EACA,iBAAA;EACA,wBAAA;EACA,2BAAA;;AA9BH,SASC,MAwBC,MAAK,GACJ;EACC,8BAAA;;AAnCJ,SASC,MA8BC,MAAK,IACJ,KACC;EACC,kBAAA;EACA,mBAAA;EACA,YAAA;EACA,cAAA;EACA,YAAA;EACA,kBAAA;EACA,gBAAA;;AAhDL,SASC,MA6CC;EACC,6BAAA;EACA,0BAAA;EACA,8BAAA;;AAQH,SAAS;EACR,UAAA;;AAGD,SAAS;EACR,YAAA;;AAGD,SAAS;EACR,WAAA;;AAGD,SAAS;EACR,QAAA;;AAGD,SAAS;EACR,SAAA;;AAGD,SAAS;EACR,QAAA;;AAGD;EACC,eAAA;EACA,UAAA;EACA,aAAA;EACA,QAAA;EACA,UAAA;EACA,kBAAA;EACA,mBAAA;EACA,gBAAA;;AAGD,mBAAqC;EACpC;IACC,UAAA;IACA,kBAAA;;;AAIF,IAAI,SAAU;EACb,UAAA;;AAGD,UAAU;EACT,WAAA;EACA,YAAA;;AAGD,UAAU;EACT,UAAA;;AAGD,UAAU;EACT,QAAA;;AAGD,UAAU;EACT,SAAA;;AAGD,UAAU;EACT,QAAA;;AAID,KAAK,eAAgB;EACpB,aAAA;;AAID,iBAAiB;EAChB,UAAA","file":"@left_menu.css"}

View File

@@ -139,7 +139,7 @@
<span v-else class="disabled">-</span>
</td>
<td class="center" v-if="windowWidth < miniWidth || windowWidth > columnWidth5">
<span v-if="node.status.isActive && node.status.load1m > 0">{{node.status.load1m}}</span>
<span v-if="node.status.isActive">{{node.status.load1m}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">

View File

@@ -14,7 +14,7 @@
<td>
<div v-if="hasDomains">
<dns-domain-selector :v-domain-id="domainId" :v-domain-name="domainName" :v-provider-name="domainProvider.name" @change="changeDomain"></dns-domain-selector>
<p class="comment">用于生成集群节点和网站服务的DNS解析记录<span v-if="domainId > 0">,修改后将自动删除旧域名中的相关记录</span></p>
<p class="comment">用于生成集群节点和网站的DNS解析记录<span v-if="domainId > 0">,修改后将自动删除旧域名中的相关记录</span></p>
</div>
<div v-else>
没有可用的域名,请在 <a href="/dns/providers" target="_blank">[域名解析]</a> 中添加。
@@ -43,7 +43,7 @@
<td>自动设置CNAME记录<optional-label></optional-label></td>
<td>
<values-box :values="cnameRecords" name="cnameRecords" placeholder="记录名" ref="cnameRecords"></values-box>
<p class="comment">除集群已创建的网站服务之外自动解析到集群的CNAME记录比如<code-label @click.prevent="addCnameRecord('www')">www</code-label></p>
<p class="comment">除集群已创建的网站之外自动解析到集群的CNAME记录比如<code-label @click.prevent="addCnameRecord('www')">www</code-label></p>
</td>
</tr>
<tr>
@@ -51,10 +51,10 @@
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>允许通过CNAME访问网站服务</td>
<td>允许通过CNAME访问网站</td>
<td>
<checkbox name="cnameAsDomain" v-model="cnameAsDomain"></checkbox>
<p class="comment">选中后表示允许使用CNAME直接访问网站服务如果取消选中则表示CNAME只作为DNS解析记录使用。</p>
<p class="comment">选中后表示允许使用CNAME直接访问网站如果取消选中则表示CNAME只作为DNS解析记录使用。</p>
</td>
</tr>
<tr v-show="teaIsPlus">
@@ -84,7 +84,7 @@
</td>
</tr>
<tr>
<td>是否同步网站服务DNS状态</td>
<td>是否同步网站DNS状态</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="serversAutoSync" value="1" v-model="serversAutoSync"/>

View File

@@ -92,7 +92,7 @@
<td class="title">记录服务错误</td>
<td>
<checkbox name="logRecordServerError" v-model="config.log.recordServerError"></checkbox>
<p class="comment">在节点运行日志中记录网站服务相关错误,比如无法连接源站等。</p>
<p class="comment">在节点运行日志中记录网站相关错误,比如无法连接源站等。</p>
</td>
</tr>
</table>

View File

@@ -13,14 +13,14 @@
<td>默认缓存设置</td>
<td>
<http-cache-policy-selector></http-cache-policy-selector>
<p class="comment">为可选项。集群中的网站服务可以自行设置是否启用此设置。</p>
<p class="comment">为可选项。集群中的网站可以自行设置是否启用此设置。</p>
</td>
</tr>
<tr>
<td>默认WAF设置</td>
<td>
<http-firewall-policy-selector></http-firewall-policy-selector>
<p class="comment">为可选项。集群中的网站服务可以自行设置是否启用此设置。</p>
<p class="comment">为可选项。集群中的网站可以自行设置是否启用此设置。</p>
</td>
</tr>
<tr>
@@ -71,7 +71,7 @@
<td>选择主域名</td>
<td>
<dns-domain-selector @change="changeDomain"></dns-domain-selector>
<p class="comment">用于生成集群节点和网站服务的DNS解析记录。</p>
<p class="comment">用于生成集群节点和网站的DNS解析记录。</p>
</td>
</tr>
<tr>

View File

@@ -134,7 +134,7 @@
<span v-else class="disabled">-</span>
</td>
<td class="center" v-if="windowWidth < miniWidth || windowWidth > columnWidth5">
<span v-if="node.status.isActive && node.status.load1m > 0">{{node.status.load1m}}</span>
<span v-if="node.status.isActive">{{node.status.load1m}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">

View File

@@ -11,7 +11,7 @@
<!-- 没有节点提醒 -->
<div class="ui icon message warning" v-if="!isLoading && dashboard.defaultClusterId > 0 && dashboard.countNodes == 0">
<i class="icon warning circle"></i>
<a :href="'/clusters/cluster/createNode?clusterId=' + dashboard.defaultClusterId">还没有在集群中添加节点,现在去添加?添加节点后才可部署网站服务</a>
<a :href="'/clusters/cluster/createNode?clusterId=' + dashboard.defaultClusterId">还没有在集群中添加节点,现在去添加?添加节点后才可部署网站。</a>
</div>
<!-- 新版本更新提醒 -->

View File

@@ -176,8 +176,8 @@
</tr>
</table>
<!-- 网站服务解析记录 -->
<h3>网站服务解析记录 <span>&nbsp; ({{servers.length}}个)</span></h3>
<!-- 网站解析记录 -->
<h3>网站解析记录 <span>&nbsp; ({{servers.length}}个)</span></h3>
<p class="comment" v-if="servers.length == 0">暂时没有需要设置的DNS记录。</p>
<table class="ui table selectable celled" v-if="servers.length > 0">
<thead>

View File

@@ -48,7 +48,7 @@
<td>自动设置CNAME记录</td>
<td>
<values-box :values="cnameRecords" name="cnameRecords" placeholder="记录名" ref="cnameRecords"></values-box>
<p class="comment">除集群已创建的网站服务之外自动解析到集群的CNAME记录比如<code-label @click.prevent="addCnameRecord('www')">www</code-label></p>
<p class="comment">除集群已创建的网站之外自动解析到集群的CNAME记录比如<code-label @click.prevent="addCnameRecord('www')">www</code-label></p>
</td>
</tr>
<tr>
@@ -56,10 +56,10 @@
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>允许通过CNAME访问网站服务</td>
<td>允许通过CNAME访问网站</td>
<td>
<checkbox name="cnameAsDomain" v-model="cnameAsDomain"></checkbox>
<p class="comment">选中后表示允许使用CNAME直接访问网站服务如果取消选中则表示CNAME只作为DNS解析记录使用。</p>
<p class="comment">选中后表示允许使用CNAME直接访问网站如果取消选中则表示CNAME只作为DNS解析记录使用。</p>
</td>
</tr>
<tr v-show="teaIsPlus">
@@ -89,7 +89,7 @@
</td>
</tr>
<tr>
<td>是否同步网站服务DNS状态</td>
<td>是否同步网站DNS状态</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="serversAutoSync" value="1" v-model="serversAutoSync"/>

View File

@@ -1,6 +1,6 @@
<first-menu>
<menu-item href="/servers" code="index">服务列表</menu-item>
<menu-item href="/servers" code="index">网站列表</menu-item>
<menu-item href="/servers?auditingFlag=1" code="auditing">审核中<span :class="{red: countAuditing > 0}">({{countAuditing}})</span></menu-item>
<span class="item disabled">|</span>
<menu-item href="/servers/create" code="create">[创建网站服务]</menu-item>
<menu-item href="/servers/create" code="create">[创建网站]</menu-item>
</first-menu>

View File

@@ -1,6 +1,8 @@
<first-menu>
<menu-item href="/servers/certs/acme" code="task">所有任务</menu-item>
<menu-item href="/servers/certs/acme/create" code="create">新申请</menu-item>
<span class="disabled item">|</span>
<menu-item href="/servers/certs/acme/create" code="create">[新申请]</menu-item>
<span class="disabled item">|</span>
<menu-item href="/servers/certs/acme/users" code="user">ACME用户</menu-item>
<menu-item href="/servers/certs/acme/accounts" code="account">服务商账号</menu-item>
</first-menu>

View File

@@ -12,7 +12,7 @@
选择申请方式
</div>
<div class="ui step" :class="{active:step == 'user'}">
选择用户
选择ACME用户
</div>
<div class="ui step" :class="{active:step == 'dns'}">
填写域名信息
@@ -26,7 +26,7 @@
<div v-show="step == 'prepare'">
<table class="ui table definition selectable">
<tr>
<td class="title">认证方式</td>
<td class="title">认证方式 *</td>
<td>
<div style="margin-bottom: 1em">
<radio name="authType" :v-value="'http'" v-model="authType">使用HTTP认证</radio> &nbsp;
@@ -39,6 +39,13 @@
<p class="comment">我们在申请免费证书的过程中需要自动增加或修改相关域名的TXT记录请先确保你已经在"域名解析" -- <a href="/dns/providers" target="_blank">"DNS服务商"</a> 中已经添加了对应的DNS服务商账号。</p>
</div>
</td>
</tr>
<tr>
<td>选择平台用户</td>
<td>
<user-selector @change="changePlatformUser"></user-selector>
<p class="comment">可选项,选择证书所属用户,如果没有选择,则视为管理员所有。</p>
</td>
</tr>
<tr>
<td colspan="2">
@@ -74,7 +81,7 @@
</td>
</tr>
<tr v-if="providerCode.length > 0">
<td class="title">选择用户 *</td>
<td class="title">选择ACME用户 *</td>
<td>
<div v-if="users.length > 0">
<div class="ui fields inline">
@@ -89,7 +96,7 @@
</div>
</div>
</div>
<div v-else><a href="" @click.prevent="createUser">暂时还没有用户,点此创建</a></div>
<div v-else><a href="" @click.prevent="createUser"><span v-if="platformUserId > 0">当前平台用户下</span>暂时还没有ACME用户,点此创建</a></div>
<p class="comment">选择一个作为申请证书的用户。</p>
</td>
</tr>

View File

@@ -1,13 +1,31 @@
Tea.context(function () {
this.step = "prepare"
/**
* 选择平台用户
*/
this.platformUserId = 0
this.changePlatformUser = function (platformUserId) {
this.platformUserId = platformUserId
}
/**
* 准备工作
*/
this.authType = "http"
this.users = []
this.doPrepare = function () {
this.step = "user"
this.$post(".userOptions")
.params({
platformUserId: this.platformUserId
})
.success(function (resp) {
this.users = resp.data.users
})
}
this.prepareMoreOptionsVisible = false
@@ -17,7 +35,7 @@ Tea.context(function () {
}
/**
* 选择用户
* 选择ACME用户
*/
this.userId = 0
@@ -27,7 +45,7 @@ Tea.context(function () {
this.createUser = function () {
let that = this
teaweb.popup("/servers/certs/acme/users/createPopup?providerCode=" + this.providerCode, {
teaweb.popup("/servers/certs/acme/users/createPopup?providerCode=" + this.providerCode + "&platformUserId=" + this.platformUserId, {
height: "30em",
width: "44em",
callback: function (resp) {
@@ -90,6 +108,7 @@ Tea.context(function () {
this.$post("$")
.params({
platformUserId: this.platformUserId,
authType: this.authType,
acmeUserId: this.userId,
dnsProviderId: this.dnsProviderId,

View File

@@ -4,27 +4,32 @@
<div class="right-box without-tabbar">
{$template "menu"}
<second-menu v-if="countAll > 0">
<menu-item :href="'/servers/certs/acme?keyword=' + keyword" :active="type == ''">所有任务({{countAll}})</menu-item>
<menu-item :href="'/servers/certs/acme?type=available&keyword=' + keyword" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs/acme?type=expired&keyword=' + keyword" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?type=7days&keyword=' + keyword" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?type=30days&keyword=' + keyword" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
<div class="item right">
<form class="ui form">
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" placeholder="关键词" style="width:10em" v-model="keyword"/>
</div>
<div class="ui field">
<button type="submit" class="ui button">搜索</button>
</div>
</div>
</form>
</div>
<second-menu>
<menu-item :href="'/servers/certs/acme?keyword=' + keyword + '&userId=' + searchingUserId" :active="type == ''">所有任务({{countAll}})</menu-item>
<menu-item :href="'/servers/certs/acme?type=available&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs/acme?type=expired&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?type=7days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?type=30days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
</second-menu>
<p class="comment" v-if="tasks.length == 0">暂时还没有证书申请任务。</p>
<form class="ui form">
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" placeholder="域名等关键词" style="width:12em" v-model="keyword"/>
</div>
<div class="ui field">
<user-selector :v-user-id="searchingUserId"></user-selector>
</div>
<div class="ui field"></div>
<div class="ui field">
<button type="submit" class="ui button">搜索</button>
<a :href="Tea.url('.')" v-if="keyword.length > 0 || searchingUserId > 0">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="tasks.length == 0"><span v-if="searchingUserId > 0">当前用户下</span>暂时还没有证书申请任务。</p>
<div class="ui message blue" v-if="isRunning">有任务在执行中,可能需要的时间较长,请耐心等待。</div>
@@ -37,6 +42,7 @@
<th>更新时间</th>
<th class="center" style="width:6em">自动续期</th>
<th class="center" style="width:6em">关联证书</th>
<th>所属用户</th>
<th class="three op">操作</th>
</tr>
</thead>
@@ -77,6 +83,10 @@
</div>
<span class="disabled" v-else="">-</span>
</td>
<td>
<span v-if="user != null && user.id > 0"><user-link :v-user="user"></user-link></span>
<span v-else class="disabled">管理员</span>
</td>
<td>
<a href="" @click.prevent="updateTask(task.id)" :class="{disabled: isRunning}">修改</a> &nbsp;
<a href="" @click.prevent="runTask(index, task)" :class="{disabled: isRunning}">执行</a> &nbsp;

View File

@@ -1,9 +1,14 @@
{$layout "layout_popup"}
<h3>创建用户</h3>
<h3>创建ACME用户</h3>
<form method="post" class="ui form" data-tea-success="success" data-tea-action="$">
<csrf-token></csrf-token>
<input type="hidden" name="platformUserId" :value="platformUserId"/>
<table class="ui table definition selectable">
<tr v-if="platformUser != null">
<td>所属平台用户</td>
<td><user-link :v-user="platformUser"></user-link></td>
</tr>
<tr>
<td class="title">用户邮箱 *</td>
<td>

View File

@@ -1,6 +1,6 @@
{$layout "layout_popup"}
<h3>修改用户</h3>
<h3>修改ACME用户</h3>
<form method="post" class="ui form" data-tea-success="success" data-tea-action="$">
<csrf-token></csrf-token>
<input type="hidden" name="userId" :value="user.id"/>

View File

@@ -3,12 +3,12 @@
<div class="right-box without-tabbar">
<second-menu>
<menu-item :href="'/servers/certs?keyword=' + keyword" :active="type == ''">所有证书({{countAll}})</menu-item>
<menu-item :href="'/servers/certs?type=ca&keyword=' + keyword" :active="type == 'ca'">CA证书({{countCA}})</menu-item>
<menu-item :href="'/servers/certs?type=available&keyword=' + keyword" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs?type=expired&keyword=' + keyword" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs?type=7days&keyword=' + keyword" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs?type=30days&keyword=' + keyword" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
<menu-item :href="'/servers/certs?keyword=' + keyword + '&userId=' + searchingUserId" :active="type == ''">所有证书({{countAll}})</menu-item>
<menu-item :href="'/servers/certs?type=ca&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'ca'">CA证书({{countCA}})</menu-item>
<menu-item :href="'/servers/certs?type=available&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs?type=expired&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs?type=7days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs?type=30days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
<span class="item disabled">|</span>
<a href="" class="item" @click.prevent="uploadCert">[上传证书]</a>
<a href="" class="item" @click.prevent="uploadBatch">[批量上传]</a>
@@ -18,17 +18,20 @@
<input type="hidden" name="type" :value="type"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" placeholder="关键词" style="width:10em" v-model="keyword"/>
<input type="text" name="keyword" placeholder="域名、说明等关键词" style="width:12em" v-model="keyword"/>
</div>
<div class="ui field">
<user-selector :v-user-id="searchingUserId"></user-selector>
</div>
<div class="ui field">
<button type="submit" class="ui button">搜索</button>
&nbsp;
<a :href="Tea.url('.', { 'type':type })" v-if="keyword.length > 0">[清除条件]</a>
<a :href="Tea.url('.', { 'type':type })" v-if="keyword.length > 0 || searchingUserId > 0">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="certs.length == 0">暂时还没有相关的证书。</p>
<p class="comment" v-if="certs.length == 0"><span v-if="searchingUserId > 0">当前用户下</span>暂时还没有相关的证书。</p>
<table class="ui table selectable celled" v-if="certs.length > 0">
<thead>
<tr>
@@ -38,6 +41,7 @@
<th>生效日期</th>
<th>过期日期</th>
<th class="center">引用服务</th>
<th>所属用户</th>
<th class="center">状态</th>
<th class="three op">操作</th>
</tr>
@@ -62,6 +66,10 @@
<td>{{certInfos[index].beginDay}}</td>
<td>{{certInfos[index].endDay}}</td>
<td class="center">{{certInfos[index].countServers}}</td>
<td>
<span v-if="user != null && user.id > 0"><user-link :v-user="user"></user-link></span>
<span v-else class="disabled">管理员</span>
</td>
<td nowrap="" class="center">
<span class="ui label red tiny basic" v-if="!certInfos[index].isOn">未启用</span>
<span class="ui label red tiny basic" v-else-if="certInfos[index].isExpired">已过期</span>

View File

@@ -1,7 +1,7 @@
Tea.context(function () {
// 上传证书
this.uploadCert = function () {
teaweb.popup("/servers/certs/uploadPopup", {
teaweb.popup("/servers/certs/uploadPopup?userId=" + this.searchingUserId, {
height: "30em",
callback: function () {
teaweb.success("上传成功", function () {
@@ -13,7 +13,7 @@ Tea.context(function () {
// 批量上传证书
this.uploadBatch = function () {
teaweb.popup("/servers/certs/uploadBatchPopup", {
teaweb.popup("/servers/certs/uploadBatchPopup?userId=" + this.searchingUserId, {
callback: function () {
window.location.reload()
}

View File

@@ -3,13 +3,13 @@
<h3>选择证书 <span v-if="searchingDomains.length > 0">(当前服务域名:{{searchingDomains[0]}}<var style="font-style: normal" v-if="searchingDomains.length > 1">等{{searchingDomains.length}}个域名</var></span></h3>
<!-- 搜索表单 -->
<form class="ui form" action="/servers/certs/selectPopup">
<form class="ui form" action="/servers/certs/selectPopup" ref="searchForm">
<input type="hidden" name="selectedCertIds" :value="selectedCertIds"/>
<input type="hidden" name="searchingType" :value="searchingType"/>
<input type="hidden" name="searchingDomains" :value="searchingDomains.join(',')"/>
<div class="ui fields inline">
<div class="ui field">
<user-selector :v-user-id="userId"></user-selector>
<user-selector :v-user-id="userId" ref="userSelector"></user-selector>
</div>
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="域名、说明文字等" size="30"/>
@@ -25,6 +25,8 @@
<div v-if="searchingDomains.length > 0">
<div class="ui divider" style="margin-bottom: 0"></div>
<second-menu>
<span class="item"><span v-if="searchingUserId > 0">当前用户</span><span v-else>未指定用户</span></span>
<raquo-item></raquo-item>
<menu-item :active="searchingType == 'all'" :href="baseURL + '&searchingType=all'">所有证书 <span class="small">({{totalAll}})</span></menu-item>
<span class="disabled item">|</span>
<menu-item :active="searchingType == 'match'" :href="baseURL + '&searchingType=match'">域名匹配证书 <span class="small"> ({{totalMatch}})</span></menu-item>
@@ -38,7 +40,9 @@
</div>
<!-- 证书列表 -->
<p class="comment" v-if="certs.length == 0">暂时还没有<span v-if="searchingType == 'match'">跟所添加域名匹配的</span>相关证书。</p>
<p class="comment" v-if="certs.length == 0">
<span v-if="searchingUserId > 0">当前用户下</span>暂时还没有<span v-if="searchingType == 'match'">跟所添加域名匹配的</span>相关证书<span v-if="searchingUserId > 0"><a href="" @click.prevent="searchNoneUserCerts">[尝试搜索管理员上传和申请的证书]</a></span>
</p>
<table class="ui table selectable celled" v-if="certs.length > 0">
<thead>
<tr>

View File

@@ -74,4 +74,11 @@ Tea.context(function () {
}
})
}
this.searchNoneUserCerts = function () {
this.$refs.userSelector.clear()
this.$delay(function () {
this.$refs.searchForm.submit()
}, 10)
}
})

View File

@@ -4,7 +4,7 @@
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success" data-tea-fail="fail">
<div class="tables-box">
<div class="table-box left">
<h3>新网站服务主要信息:</h3>
<h3>新网站主要信息:</h3>
<table class="ui table selectable definition">
<tr v-show="hasUsers">
<td class="title">所属用户<optional-label></optional-label></td>
@@ -112,7 +112,7 @@
<checkbox checked="checked" name="websocketIsOn">Websocket</checkbox>
<checkbox checked="checked" name="cacheIsOn">缓存</checkbox>
<checkbox name="wafIsOn">WAF</checkbox>
<checkbox name="remoteAddrIsOn">从上级代理中读取IP <tip-icon content="用来支持读取上级代理传递的X-Real-IP、X-Forwarded-For等信息。如果用户需要通过别的代理服务才能访问到这个网站服务,才需要选中。"></tip-icon> </checkbox>
<checkbox name="remoteAddrIsOn">从上级代理中读取IP <tip-icon content="用来支持读取上级代理传递的X-Real-IP、X-Forwarded-For等信息。如果用户需要通过别的代理服务才能访问到这个网站才需要选中。"></tip-icon> </checkbox>
<checkbox checked="checked" name="statIsOn">统计 <tip-icon content="开启统计后,会统计访客区域、操作系统、浏览器等信息。"></tip-icon> </checkbox>
</div>
</td>

View File

@@ -7,7 +7,7 @@
<div class="ui icon message small error">
<i class="icon warning circle"></i>
<div class="content"><a href="/clusters/logs?type=needFix">有{{countNeedFixLogs}}个网站服务相关的错误需要修复。</a></div>
<div class="content"><a href="/clusters/logs?type=needFix">有{{countNeedFixLogs}}个网站相关的错误需要修复。</a></div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
{$layout}
<first-menu>
<menu-item href="/servers">服务列表</menu-item>
<menu-item href="/servers">网站列表</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/servers/server/delete?serverId=' + server.id" active="true">"{{server.name}}"删除</menu-item>
<span class="disabled item">|</span>

View File

@@ -3,7 +3,7 @@
{$layout}
<first-menu>
<menu-item href="/servers">服务列表</menu-item>
<menu-item href="/servers">网站列表</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/servers/server/log?serverId=' + server.id" active="true">"{{server.name}}"日志</menu-item>
<span class="disabled item">|</span>

View File

@@ -1,7 +1,7 @@
{$layout}
<first-menu>
<menu-item href="/servers">服务列表</menu-item>
<menu-item href="/servers">网站列表</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/servers/server/log?serverId=' + server.id" active="true">"{{server.name}}"日志</menu-item>
<span class="disabled item">|</span>

View File

@@ -1,7 +1,7 @@
{$layout}
<first-menu>
<menu-item href="/servers">服务列表</menu-item>
<menu-item href="/servers">网站列表</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/servers/server/log?serverId=' + server.id" active="true">"{{server.name}}"日志</menu-item>
<span class="disabled item">|</span>

View File

@@ -1,5 +1,5 @@
<first-menu>
<menu-item href="/servers">服务列表</menu-item>
<menu-item href="/servers">网站列表</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/servers/server/settings?serverId=' + server.id" :active="leftMenuActiveItem == null">"{{server.name}}"设置</menu-item>
<span class="disabled item">&raquo;</span>

View File

@@ -56,7 +56,7 @@
<td class="title">已绑定的域名</td>
<td>
<server-name-box :v-server-names="serverNames"></server-name-box>
<p class="comment">用户可以通过这些域名访问当前网站服务</p>
<p class="comment">用户可以通过这些域名访问当前网站。</p>
</td>
</tr>
</table>

View File

@@ -1,5 +1,5 @@
<first-menu>
<menu-item href="/servers">服务列表</menu-item>
<menu-item href="/servers">网站列表</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/servers/server/stat?serverId=' + server.id" active="true">"{{server.name}}"统计</menu-item>
<span class="disabled item">|</span>

View File

@@ -77,6 +77,20 @@
<p class="comment">只允许通过这些域名或者IP作为主机地址访问当前管理系统不填表示没有限制。</p>
</td>
</tr>
<tr>
<td>检查客户端指纹</td>
<td>
<checkbox name="checkClientFingerprint" v-model="config.checkClientFingerprint"></checkbox>
<p class="comment">选中后,表示每次访问时都检查客户端相关信息是否跟登录时一致。</p>
</td>
</tr>
<tr>
<td>检查客户端区域</td>
<td>
<checkbox name="checkClientRegion" v-model="config.checkClientRegion"></checkbox>
<p class="comment">选中后,表示每次访问时都检查客户端所在地理区域是否和登录时一致。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>

View File

@@ -5,7 +5,7 @@
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<table class="ui table definition selectable">
<tr>
<td>是否启用</td>
<td>启用HTTP</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="serverConfig.http.on"/>

View File

@@ -5,7 +5,7 @@
<form method="post" data-tea-action="$" data-tea-success="success" class="ui form">
<table class="ui table definition selectable">
<tr>
<td>是否启用</td>
<td>启用HTTPS</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="serverConfig.https.on"/>

View File

@@ -2,7 +2,7 @@
<menu-item href="/users">用户列表</menu-item>
<span class="item">|</span>
<menu-item :href="'/users/user?userId=' + user.id" code="index">{{user.fullname}}&nbsp; <span class="small">({{user.username}})</span></menu-item>
<menu-item :href="'/users/user/servers?userId=' + user.id" code="server" v-if="teaIsPlus">网站服务</menu-item>
<menu-item :href="'/users/user/servers?userId=' + user.id" code="server" v-if="teaIsPlus">网站列表</menu-item>
<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>

View File

@@ -35,7 +35,7 @@
<td>关联集群 *</td>
<td>
<cluster-selector></cluster-selector>
<p class="comment">用户发布的网站服务会自动部署到此集群。</p>
<p class="comment">用户发布的网站会自动部署到此集群。</p>
</td>
</tr>
<tr v-show="teaIsPlus">

View File

@@ -42,7 +42,7 @@
<td>关联集群 *</td>
<td>
<cluster-selector :v-cluster-id="clusterId"></cluster-selector>
<p class="comment">用户发布的网站服务会自动部署到此集群,修改此选项会同步修改当前用户下的所有服务,但不影响和套餐绑定的服务。</p>
<p class="comment">用户发布的网站会自动部署到此集群,修改此选项会同步修改当前用户下的所有服务,但不影响和套餐绑定的服务。</p>
</td>
</tr>
<tr>