mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 03:07:20 +08:00
feat: 网站管理优化
This commit is contained in:
3
go.mod
3
go.mod
@@ -39,7 +39,7 @@ require (
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible
|
||||
github.com/spf13/cast v1.7.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20240907135031-d38eb71142ac
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241013191809-e73b7dd454e8
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.1
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.28.0
|
||||
@@ -103,6 +103,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/samber/lo v1.47.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.597 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -271,6 +271,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/sethvargo/go-limiter v1.0.0 h1:JqW13eWEMn0VFv86OKn8wiYJY/m250WoXdrjRV0kLe4=
|
||||
github.com/sethvargo/go-limiter v1.0.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible h1:lOGOyCG67a5dv2hq5Z1BLDUqqKp3HkbjPcz5j6XMS0U=
|
||||
@@ -299,6 +301,8 @@ github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYg
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20240907135031-d38eb71142ac h1:IXccMEFcB+UqGWae8OF9EoA0/8GCLlDj6s84LCU7y58=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20240907135031-d38eb71142ac/go.mod h1:itu4KWRgrfEwGcfNka+rV4houuirUau53i0diN4lG5g=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241013191809-e73b7dd454e8 h1:7yw1yHkylDcu/CwY4hEEe8MycDLo7B9LjcqwhoL8NrM=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241013191809-e73b7dd454e8/go.mod h1:itu4KWRgrfEwGcfNka+rV4houuirUau53i0diN4lG5g=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
|
||||
@@ -12,7 +12,7 @@ type Website struct {
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Status bool `gorm:"not null;default:true" json:"status"`
|
||||
Path string `gorm:"not null" json:"path"`
|
||||
HTTPS bool `gorm:"not null" json:"https"`
|
||||
Https bool `gorm:"not null" json:"https"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/embed"
|
||||
@@ -70,7 +71,7 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
setting.ID = website.ID
|
||||
setting.Name = website.Name
|
||||
setting.Path = website.Path
|
||||
setting.HTTPS = website.HTTPS
|
||||
setting.HTTPS = website.Https
|
||||
setting.PHP = p.GetPHP()
|
||||
setting.Raw = config
|
||||
// 监听地址
|
||||
@@ -78,16 +79,28 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for listen := range slices.Values(listens) {
|
||||
if len(listen) == 0 {
|
||||
continue
|
||||
}
|
||||
setting.Listens = append(setting.Listens, types.WebsiteListen{
|
||||
Address: listen[0],
|
||||
HTTPS: slices.Contains(listen, "ssl"),
|
||||
QUIC: slices.Contains(listen, "quic"),
|
||||
})
|
||||
}
|
||||
setting.Listens = lo.Map(
|
||||
lo.UniqBy(listens, func(listen []string) string {
|
||||
if len(listen) == 0 {
|
||||
return ""
|
||||
}
|
||||
return listen[0]
|
||||
}),
|
||||
func(listen []string, _ int) types.WebsiteListen {
|
||||
addr := listen[0]
|
||||
grouped := lo.GroupBy(listens, func(listen []string) string {
|
||||
if len(listen) == 0 {
|
||||
return ""
|
||||
}
|
||||
return listen[0]
|
||||
})[addr]
|
||||
return types.WebsiteListen{
|
||||
Address: addr,
|
||||
HTTPS: lo.SomeBy(grouped, func(listen []string) bool { return lo.Contains(listen, "ssl") }),
|
||||
QUIC: lo.SomeBy(grouped, func(listen []string) bool { return lo.Contains(listen, "quic") }),
|
||||
}
|
||||
},
|
||||
)
|
||||
// 域名
|
||||
domains, err := p.GetServerName()
|
||||
if err != nil {
|
||||
@@ -174,8 +187,7 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
// 监听地址
|
||||
var listens [][]string
|
||||
for _, listen := range req.Listens {
|
||||
listens = append(listens, []string{listen}) // ipv4
|
||||
listens = append(listens, []string{"[::]:" + listen}) // ipv6
|
||||
listens = append(listens, []string{listen})
|
||||
}
|
||||
if err = p.SetListen(listens); err != nil {
|
||||
return nil, err
|
||||
@@ -260,7 +272,7 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
Name: req.Name,
|
||||
Status: true,
|
||||
Path: req.Path,
|
||||
HTTPS: false,
|
||||
Https: false,
|
||||
}
|
||||
if err = app.Orm.Create(w).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -335,7 +347,9 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
// 监听地址
|
||||
var listens [][]string
|
||||
for _, listen := range req.Listens {
|
||||
listens = append(listens, []string{listen.Address})
|
||||
if !listen.HTTPS && !listen.QUIC {
|
||||
listens = append(listens, []string{listen.Address})
|
||||
}
|
||||
if listen.HTTPS {
|
||||
listens = append(listens, []string{listen.Address, "ssl"})
|
||||
}
|
||||
@@ -383,7 +397,7 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
if err = io.Write(keyPath, req.SSLCertificateKey, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
website.HTTPS = req.HTTPS
|
||||
website.Https = req.HTTPS
|
||||
if req.HTTPS {
|
||||
if err = p.SetHTTPS(certPath, keyPath); err != nil {
|
||||
return err
|
||||
@@ -418,6 +432,7 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
}
|
||||
userIni := filepath.Join(req.Root, ".user.ini")
|
||||
if req.OpenBasedir {
|
||||
_, _ = shell.Execf(`chattr -i '%s'`, userIni)
|
||||
if err = io.Write(userIni, fmt.Sprintf("open_basedir=%s:/tmp/", req.Root), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -444,6 +459,7 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -488,6 +504,7 @@ func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
|
||||
|
||||
if err := systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -554,7 +571,7 @@ func (r *websiteRepo) ResetConfig(id uint) error {
|
||||
}
|
||||
|
||||
website.Status = true
|
||||
website.HTTPS = false
|
||||
website.Https = false
|
||||
if err := app.Orm.Save(website).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -598,6 +615,10 @@ func (r *websiteRepo) UpdateStatus(id uint, status bool) error {
|
||||
if len(rootComment) == 0 {
|
||||
return fmt.Errorf("未找到运行目录注释")
|
||||
}
|
||||
if len(rootComment) != 1 {
|
||||
return fmt.Errorf("运行目录注释数量不正确,预期1个,实际%d个", len(rootComment))
|
||||
}
|
||||
rootComment[0] = strings.TrimPrefix(rootComment[0], "# ")
|
||||
if !io.Exists(rootComment[0]) {
|
||||
return fmt.Errorf("运行目录不存在")
|
||||
}
|
||||
@@ -607,14 +628,18 @@ func (r *websiteRepo) UpdateStatus(id uint, status bool) error {
|
||||
if len(indexComment) == 0 {
|
||||
return fmt.Errorf("未找到默认文档注释")
|
||||
}
|
||||
if err = p.SetIndex(strings.Fields(indexStr)); err != nil {
|
||||
if len(indexComment) != 1 {
|
||||
return fmt.Errorf("默认文档注释数量不正确,预期1个,实际%d个", len(indexComment))
|
||||
}
|
||||
indexComment[0] = strings.TrimPrefix(indexComment[0], "# ")
|
||||
if err = p.SetIndex(strings.Fields(indexComment[0])); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = p.SetRootWithComment(filepath.Join(app.Root, "server/nginx/html"), []string{root}); err != nil {
|
||||
if err = p.SetRootWithComment(filepath.Join(app.Root, "server/nginx/html"), []string{"# " + root}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.SetIndexWithComment([]string{"stop.html"}, []string{indexStr}); err != nil {
|
||||
if err = p.SetIndexWithComment([]string{"stop.html"}, []string{"# " + indexStr}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package request
|
||||
|
||||
type CertCreate struct {
|
||||
Type string `form:"type" json:"type" validate:"required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
|
||||
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
|
||||
AccountID uint `form:"account_id" json:"account_id"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id"`
|
||||
@@ -12,7 +12,7 @@ type CertCreate struct {
|
||||
type CertUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required"`
|
||||
Type string `form:"type" json:"type" validate:"required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
|
||||
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
|
||||
AccountID uint `form:"account_id" json:"account_id"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id"`
|
||||
|
||||
@@ -34,7 +34,7 @@ type FilePermission struct {
|
||||
}
|
||||
|
||||
type FileCompress struct {
|
||||
Paths []string `form:"paths" json:"paths" validate:"required"`
|
||||
Paths []string `form:"paths" json:"paths" validate:"min=1,dive,required"`
|
||||
File string `form:"file" json:"file" validate:"required"`
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ type WebsiteDefaultConfig struct {
|
||||
|
||||
type WebsiteCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Listens []string `form:"listens" json:"listens" validate:"required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required"`
|
||||
Listens []string `form:"listens" json:"listens" validate:"min=1,dive,required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
|
||||
Path string `form:"path" json:"path"`
|
||||
PHP int `form:"php" json:"php" validate:"required,number,gte=0"`
|
||||
PHP int `form:"php" json:"php" validate:"number,gte=0"`
|
||||
DB bool `form:"db" json:"db"`
|
||||
DBType string `form:"db_type" json:"db_type"`
|
||||
DBName string `form:"db_name" json:"db_name"`
|
||||
@@ -28,21 +28,21 @@ type WebsiteDelete struct {
|
||||
|
||||
type WebsiteUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required"`
|
||||
Listens []types.WebsiteListen `form:"listens" json:"listens" validate:"required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required"`
|
||||
Listens []types.WebsiteListen `form:"listens" json:"listens" validate:"min=1"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
|
||||
HTTPS bool `form:"https" json:"https"`
|
||||
OCSP bool `form:"ocsp" json:"ocsp"`
|
||||
HSTS bool `form:"hsts" json:"hsts"`
|
||||
HTTPRedirect bool `form:"http_redirect" json:"http_redirect"`
|
||||
OpenBasedir bool `form:"open_basedir" json:"open_basedir"`
|
||||
Index []string `form:"index" json:"index" validate:"required"`
|
||||
Index []string `form:"index" json:"index" validate:"min=1,dive,required"`
|
||||
Path string `form:"path" json:"path" validate:"required"` // 网站目录
|
||||
Root string `form:"root" json:"root" validate:"required"` // 运行目录
|
||||
Raw string `form:"raw" json:"raw"`
|
||||
Rewrite string `form:"rewrite" json:"rewrite"`
|
||||
PHP int `form:"php" json:"php"`
|
||||
SSLCertificate string `form:"ssl_certificate" json:"ssl_certificate"`
|
||||
SSLCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key"`
|
||||
SSLCertificate string `form:"ssl_certificate" json:"ssl_certificate" validate:"required_if=HTTPS true"`
|
||||
SSLCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key" validate:"required_if=HTTPS true"`
|
||||
}
|
||||
|
||||
type WebsiteUpdateRemark struct {
|
||||
|
||||
@@ -108,13 +108,13 @@ func Cli() []*cli.Command {
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "domain",
|
||||
Name: "domains",
|
||||
Usage: "与网站关联的域名列表",
|
||||
Aliases: []string{"d"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "listen",
|
||||
Name: "listens",
|
||||
Usage: "与网站关联的监听地址列表",
|
||||
Aliases: []string{"l"},
|
||||
Required: true,
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/utils/hash"
|
||||
@@ -350,18 +349,10 @@ func (s *CliService) Port(ctx context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func (s *CliService) WebsiteCreate(ctx context.Context, cmd *cli.Command) error {
|
||||
var ports []uint
|
||||
for port := range slices.Values(cmd.IntSlice("ports")) {
|
||||
if port < 1 || port > 65535 {
|
||||
return fmt.Errorf("端口范围错误")
|
||||
}
|
||||
ports = append(ports, uint(port))
|
||||
}
|
||||
|
||||
req := &request.WebsiteCreate{
|
||||
Name: cmd.String("name"),
|
||||
Domains: cmd.StringSlice("domains"),
|
||||
Listens: cmd.StringSlice("listen"),
|
||||
Listens: cmd.StringSlice("listens"),
|
||||
Path: cmd.String("path"),
|
||||
PHP: int(cmd.Int("php")),
|
||||
DB: false,
|
||||
|
||||
@@ -2,13 +2,14 @@ package io
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
)
|
||||
|
||||
// Remove 删除文件/目录
|
||||
|
||||
@@ -99,7 +99,7 @@ func (p *Parser) GetPHP() int {
|
||||
if slices.ContainsFunc(dir.GetParameters(), func(s string) bool {
|
||||
return strings.HasPrefix(s, "enable-php-") && strings.HasSuffix(s, ".conf")
|
||||
}) {
|
||||
_, err = fmt.Sscanf(dir.GetParameters()[0], "enable-php-%d.conf", &result)
|
||||
_, _ = fmt.Sscanf(dir.GetParameters()[0], "enable-php-%d.conf", &result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ package nginx
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
)
|
||||
|
||||
type NginxTestSuite struct {
|
||||
@@ -51,6 +52,20 @@ func (s *NginxTestSuite) TestIndex() {
|
||||
s.Equal([]string{"index.html", "index.htm"}, index)
|
||||
}
|
||||
|
||||
func (s *NginxTestSuite) TestIndexWithComment() {
|
||||
parser, err := NewParser()
|
||||
s.NoError(err)
|
||||
index, comment, err := parser.GetIndexWithComment()
|
||||
s.NoError(err)
|
||||
s.Equal([]string{"index.php", "index.html", "index.htm"}, index)
|
||||
s.Equal([]string(nil), comment)
|
||||
s.NoError(parser.SetIndexWithComment([]string{"index.html", "index.htm"}, []string{"# 测试"}))
|
||||
index, comment, err = parser.GetIndexWithComment()
|
||||
s.NoError(err)
|
||||
s.Equal([]string{"index.html", "index.htm"}, index)
|
||||
s.Equal([]string{"# 测试"}, comment)
|
||||
}
|
||||
|
||||
func (s *NginxTestSuite) TestRoot() {
|
||||
parser, err := NewParser()
|
||||
s.NoError(err)
|
||||
@@ -63,6 +78,20 @@ func (s *NginxTestSuite) TestRoot() {
|
||||
s.Equal("/www/wwwroot/test", root)
|
||||
}
|
||||
|
||||
func (s *NginxTestSuite) TestRootWithComment() {
|
||||
parser, err := NewParser()
|
||||
s.NoError(err)
|
||||
root, comment, err := parser.GetRootWithComment()
|
||||
s.NoError(err)
|
||||
s.Equal("/www/wwwroot/default", root)
|
||||
s.Equal([]string(nil), comment)
|
||||
s.NoError(parser.SetRootWithComment("/www/wwwroot/test", []string{"# 测试"}))
|
||||
root, comment, err = parser.GetRootWithComment()
|
||||
s.NoError(err)
|
||||
s.Equal("/www/wwwroot/test", root)
|
||||
s.Equal([]string{"# 测试"}, comment)
|
||||
}
|
||||
|
||||
func (s *NginxTestSuite) TestIncludes() {
|
||||
parser, err := NewParser()
|
||||
s.NoError(err)
|
||||
@@ -106,7 +135,6 @@ func (s *NginxTestSuite) TestHTTPS() {
|
||||
s.False(parser.GetHTTPS())
|
||||
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
|
||||
s.True(parser.GetHTTPS())
|
||||
io.Write("testdata/https.conf", parser.Dump(), 0755)
|
||||
expect, err := io.Read("testdata/https.conf")
|
||||
s.NoError(err)
|
||||
s.Equal(expect, parser.Dump())
|
||||
|
||||
@@ -328,6 +328,11 @@ func (p *Parser) SetHTTPRedirect(httpRedirect bool) error {
|
||||
var directives []*config.Directive
|
||||
var foundFlag bool
|
||||
for _, dir := range ifs { // 所有 if
|
||||
if !httpRedirect {
|
||||
if len(dir.GetParameters()) == 3 && dir.GetParameters()[0] == "($scheme" && dir.GetParameters()[1] == "=" && dir.GetParameters()[2] == "http)" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
var ifDirectives []config.IDirective
|
||||
for _, dir2 := range dir.GetBlock().GetDirectives() { // 每个 if 中所有指令
|
||||
if !httpRedirect {
|
||||
|
||||
@@ -9,7 +9,8 @@ export default {
|
||||
// 创建
|
||||
create: (data: any): Promise<AxiosResponse<any>> => request.post('/website', data),
|
||||
// 删除
|
||||
delete: (data: any): Promise<AxiosResponse<any>> => request.post('/website/' + data.id, data),
|
||||
delete: (id: number, path: boolean, db: boolean): Promise<AxiosResponse<any>> =>
|
||||
request.delete(`/website/${id}`, { params: { path, db } }),
|
||||
// 获取默认配置
|
||||
defaultConfig: (): Promise<AxiosResponse<any>> => request.get('/website/defaultConfig'),
|
||||
// 保存默认配置
|
||||
|
||||
@@ -4,7 +4,7 @@ import { NButton } from 'naive-ui'
|
||||
|
||||
import info from '@/api/panel/info'
|
||||
import website from '@/api/panel/website'
|
||||
import type { WebsiteSetting } from '@/views/website/types'
|
||||
import type { WebsiteListen, WebsiteSetting } from '@/views/website/types'
|
||||
|
||||
const route = useRoute()
|
||||
const { id } = route.params
|
||||
@@ -12,16 +12,14 @@ const { id } = route.params
|
||||
const setting = ref<WebsiteSetting>({
|
||||
id: 0,
|
||||
name: '',
|
||||
ports: [],
|
||||
ssl_ports: [],
|
||||
quic_ports: [],
|
||||
listens: [] as WebsiteListen[],
|
||||
domains: [],
|
||||
root: '',
|
||||
path: '',
|
||||
index: '',
|
||||
index: [],
|
||||
php: 0,
|
||||
open_basedir: false,
|
||||
ssl: false,
|
||||
https: false,
|
||||
ssl_certificate: '',
|
||||
ssl_certificate_key: '',
|
||||
ssl_not_before: '',
|
||||
@@ -63,16 +61,23 @@ const getWebsiteSetting = async () => {
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (setting.value.ssl) {
|
||||
if (setting.value.ssl_certificate == '') {
|
||||
window.$message.error('请填写证书')
|
||||
return
|
||||
}
|
||||
if (setting.value.ssl_certificate_key == '') {
|
||||
window.$message.error('请填写私钥')
|
||||
return
|
||||
}
|
||||
// 如果没有任何监听地址设置了https,则自动添加443
|
||||
if (setting.value.https && !setting.value.listens.some((item) => item.https)) {
|
||||
setting.value.listens.push({
|
||||
address: '443',
|
||||
https: true,
|
||||
quic: true
|
||||
})
|
||||
}
|
||||
// 如果关闭了https,自动禁用所有https和quic
|
||||
if (!setting.value.https) {
|
||||
setting.value.listens = setting.value.listens.filter((item) => item.address !== '443') // 443直接删掉
|
||||
setting.value.listens.forEach((item) => {
|
||||
item.https = false
|
||||
item.quic = false
|
||||
})
|
||||
}
|
||||
|
||||
await website.saveConfig(Number(id), setting.value).then(() => {
|
||||
getWebsiteSetting()
|
||||
window.$message.success('保存成功')
|
||||
@@ -100,6 +105,14 @@ const title = computed(() => {
|
||||
return '编辑网站 - 加载中...'
|
||||
})
|
||||
|
||||
const onCreateListen = () => {
|
||||
return {
|
||||
address: '',
|
||||
https: false,
|
||||
quic: false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getWebsiteSetting()
|
||||
getPhpAndDb()
|
||||
@@ -119,7 +132,7 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane name="domain" tab="域名端口">
|
||||
<n-tab-pane name="domain" tab="域名监听">
|
||||
<n-form v-if="setting">
|
||||
<n-form-item label="域名">
|
||||
<n-dynamic-input
|
||||
@@ -129,16 +142,18 @@ onMounted(() => {
|
||||
show-sort-button
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="端口">
|
||||
<n-dynamic-input v-model:value="setting.ports" show-sort-button>
|
||||
<template #default="{ index }">
|
||||
<n-input-number
|
||||
v-model:value="setting.ports[index]"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
clearable
|
||||
w-full
|
||||
/>
|
||||
<n-form-item label="监听地址">
|
||||
<n-dynamic-input
|
||||
v-model:value="setting.listens"
|
||||
show-sort-button
|
||||
:on-create="onCreateListen"
|
||||
>
|
||||
<template #default="{ value }">
|
||||
<div flex items-center w-full>
|
||||
<n-input v-model:value="value.address" clearable />
|
||||
<n-checkbox v-model:checked="value.https" ml-20 mr-20 w-120> HTTPS </n-checkbox>
|
||||
<n-checkbox v-model:checked="value.quic" w-200> QUIC(HTTP3) </n-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</n-dynamic-input>
|
||||
</n-form-item>
|
||||
@@ -157,7 +172,7 @@ onMounted(() => {
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="默认文档">
|
||||
<n-input v-model:value="setting.index" placeholder="输入默认文档(多个用空格分隔)" />
|
||||
<n-dynamic-tags v-model:value="setting.index" />
|
||||
</n-form-item>
|
||||
<n-form-item label="PHP版本">
|
||||
<n-select
|
||||
@@ -177,8 +192,7 @@ onMounted(() => {
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="https" tab="HTTPS">
|
||||
<n-flex vertical v-if="setting">
|
||||
<n-alert type="info">开启 HTTPS 前,请先在域名端口处添加 443 端口!</n-alert>
|
||||
<n-card v-if="setting.ssl">
|
||||
<n-card v-if="setting.https && setting.ssl_issuer != ''">
|
||||
<n-descriptions title="证书信息" :column="2">
|
||||
<n-descriptions-item>
|
||||
<template #label>证书有效期</template>
|
||||
@@ -210,27 +224,7 @@ onMounted(() => {
|
||||
</n-card>
|
||||
<n-form>
|
||||
<n-form-item label="总开关(只有打开了总开关,下面的设置才会生效!)">
|
||||
<n-switch v-model:value="setting.ssl" />
|
||||
</n-form-item>
|
||||
<n-form-item label="HTTPS(SSL)端口">
|
||||
<n-checkbox-group v-model:value="setting.ssl_ports">
|
||||
<n-checkbox
|
||||
v-for="item in setting.ports"
|
||||
:key="item"
|
||||
:value="item"
|
||||
:label="String(item)"
|
||||
/>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="QUIC(HTTP3)端口">
|
||||
<n-checkbox-group v-model:value="setting.quic_ports">
|
||||
<n-checkbox
|
||||
v-for="item in setting.ports"
|
||||
:key="item"
|
||||
:value="item"
|
||||
:label="String(item)"
|
||||
/>
|
||||
</n-checkbox-group>
|
||||
<n-switch v-model:value="setting.https" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-form inline>
|
||||
|
||||
@@ -182,15 +182,15 @@ const pagination = reactive({
|
||||
pageSizes: [15, 30, 50, 100]
|
||||
})
|
||||
|
||||
const addModal = ref(false)
|
||||
const createModal = ref(false)
|
||||
const editDefaultPageModal = ref(false)
|
||||
|
||||
const buttonLoading = ref(false)
|
||||
const buttonDisabled = ref(false)
|
||||
const addModel = ref({
|
||||
const createModel = ref({
|
||||
name: '',
|
||||
domains: [] as Array<string>,
|
||||
ports: [] as Array<number>,
|
||||
listens: [] as Array<string>,
|
||||
php: 0,
|
||||
db: false,
|
||||
db_type: '0',
|
||||
@@ -201,7 +201,6 @@ const addModel = ref({
|
||||
remark: ''
|
||||
})
|
||||
const deleteModel = ref({
|
||||
id: 0,
|
||||
path: true,
|
||||
db: false
|
||||
})
|
||||
@@ -284,12 +283,10 @@ const handleEdit = (row: any) => {
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
deleteModel.value.id = id
|
||||
await website.delete(deleteModel.value).then(() => {
|
||||
await website.delete(id, deleteModel.value.path, deleteModel.value.db).then(() => {
|
||||
window.$message.success('删除成功')
|
||||
onPageChange(pagination.page)
|
||||
})
|
||||
deleteModel.value.id = 0
|
||||
deleteModel.value.path = true
|
||||
}
|
||||
|
||||
@@ -302,18 +299,18 @@ const handleSaveDefaultPage = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleAdd = async () => {
|
||||
const handleCreate = async () => {
|
||||
buttonLoading.value = true
|
||||
buttonDisabled.value = true
|
||||
// 去除空的域名和端口
|
||||
addModel.value.domains = addModel.value.domains.filter((item) => item !== '')
|
||||
addModel.value.ports = addModel.value.ports.filter((item) => item !== 0)
|
||||
createModel.value.domains = createModel.value.domains.filter((item) => item !== '')
|
||||
createModel.value.listens = createModel.value.listens.filter((item) => item !== '')
|
||||
// 端口为空自动添加 80 端口
|
||||
if (addModel.value.ports.length === 0) {
|
||||
addModel.value.ports.push(80)
|
||||
if (createModel.value.listens.length === 0) {
|
||||
createModel.value.listens.push('80')
|
||||
}
|
||||
await website
|
||||
.create(addModel.value)
|
||||
.create(createModel.value)
|
||||
.then(() => {
|
||||
window.$message.success('创建成功')
|
||||
getWebsiteList(pagination.page, pagination.pageSize).then((res) => {
|
||||
@@ -321,11 +318,11 @@ const handleAdd = async () => {
|
||||
pagination.itemCount = res.total
|
||||
pagination.pageCount = res.total / pagination.pageSize + 1
|
||||
})
|
||||
addModal.value = false
|
||||
addModel.value = {
|
||||
createModal.value = false
|
||||
createModel.value = {
|
||||
name: '',
|
||||
domains: [] as Array<string>,
|
||||
ports: [] as Array<number>,
|
||||
listens: [] as Array<string>,
|
||||
php: 0,
|
||||
db: false,
|
||||
db_type: '0',
|
||||
@@ -351,14 +348,10 @@ const batchDelete = async () => {
|
||||
}
|
||||
|
||||
for (const id of selectedRowKeys.value) {
|
||||
deleteModel.value.id = id
|
||||
deleteModel.value.path = true
|
||||
deleteModel.value.db = false
|
||||
await website.delete(deleteModel.value).then(() => {
|
||||
await website.delete(id, true, false).then(() => {
|
||||
let site = data.value.find((item) => item.id === id)
|
||||
window.$message.success('网站 ' + site?.name + ' 删除成功')
|
||||
})
|
||||
deleteModel.value.id = 0
|
||||
}
|
||||
|
||||
onPageChange(pagination.page)
|
||||
@@ -386,7 +379,7 @@ onMounted(() => {
|
||||
<n-space vertical size="large">
|
||||
<n-card rounded-10>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="addModal = true">
|
||||
<n-button type="primary" @click="createModal = true">
|
||||
{{ $t('websiteIndex.create.trigger') }}
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="batchDelete">
|
||||
@@ -416,19 +409,19 @@ onMounted(() => {
|
||||
</n-space>
|
||||
</common-page>
|
||||
<n-modal
|
||||
v-model:show="addModal"
|
||||
v-model:show="createModal"
|
||||
:title="$t('websiteIndex.create.title')"
|
||||
preset="card"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
@close="addModal = false"
|
||||
@close="createModal = false"
|
||||
>
|
||||
<n-form :model="addModel">
|
||||
<n-form :model="createModel">
|
||||
<n-form-item path="name" :label="$t('websiteIndex.create.fields.name.label')">
|
||||
<n-input
|
||||
v-model:value="addModel.name"
|
||||
v-model:value="createModel.name"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$t('websiteIndex.create.fields.name.placeholder')"
|
||||
@@ -438,7 +431,7 @@ onMounted(() => {
|
||||
<n-col :span="11">
|
||||
<n-form-item :label="$t('websiteIndex.create.fields.domains.label')">
|
||||
<n-dynamic-input
|
||||
v-model:value="addModel.domains"
|
||||
v-model:value="createModel.domains"
|
||||
placeholder="example.com"
|
||||
:min="1"
|
||||
show-sort-button
|
||||
@@ -448,19 +441,12 @@ onMounted(() => {
|
||||
<n-col :span="2"></n-col>
|
||||
<n-col :span="11">
|
||||
<n-form-item :label="$t('websiteIndex.create.fields.port.label')">
|
||||
<n-dynamic-input v-model:value="addModel.ports" show-sort-button>
|
||||
<template #default="{ index }">
|
||||
<div style="display: flex; align-items: center; width: 100%">
|
||||
<n-input-number
|
||||
v-model:value="addModel.ports[index]"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
clearable
|
||||
w-full
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</n-dynamic-input>
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.listens"
|
||||
placeholder="80"
|
||||
:min="1"
|
||||
show-sort-button
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
@@ -468,7 +454,7 @@ onMounted(() => {
|
||||
<n-col :span="11">
|
||||
<n-form-item path="php" :label="$t('websiteIndex.create.fields.phpVersion.label')">
|
||||
<n-select
|
||||
v-model:value="addModel.php"
|
||||
v-model:value="createModel.php"
|
||||
:options="installedDbAndPhp.php"
|
||||
:placeholder="$t('websiteIndex.create.fields.phpVersion.placeholder')"
|
||||
@keydown.enter.prevent
|
||||
@@ -480,16 +466,16 @@ onMounted(() => {
|
||||
<n-col :span="11">
|
||||
<n-form-item path="db" :label="$t('websiteIndex.create.fields.db.label')">
|
||||
<n-select
|
||||
v-model:value="addModel.db_type"
|
||||
v-model:value="createModel.db_type"
|
||||
:options="installedDbAndPhp.db"
|
||||
:placeholder="$t('websiteIndex.create.fields.db.placeholder')"
|
||||
@keydown.enter.prevent
|
||||
@update:value="
|
||||
() => {
|
||||
addModel.db = addModel.db_type != '0'
|
||||
addModel.db_name = formatDbValue(addModel.name)
|
||||
addModel.db_user = formatDbValue(addModel.name)
|
||||
addModel.db_password = generateRandomString(16)
|
||||
createModel.db = createModel.db_type != '0'
|
||||
createModel.db_name = formatDbValue(createModel.name)
|
||||
createModel.db_user = formatDbValue(createModel.name)
|
||||
createModel.db_password = generateRandomString(16)
|
||||
}
|
||||
"
|
||||
>
|
||||
@@ -500,12 +486,12 @@ onMounted(() => {
|
||||
<n-row :gutter="[0, 24]">
|
||||
<n-col :span="7">
|
||||
<n-form-item
|
||||
v-if="addModel.db"
|
||||
v-if="createModel.db"
|
||||
path="db_name"
|
||||
:label="$t('websiteIndex.create.fields.dbName.label')"
|
||||
>
|
||||
<n-input
|
||||
v-model:value="addModel.db_name"
|
||||
v-model:value="createModel.db_name"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$t('websiteIndex.create.fields.dbName.placeholder')"
|
||||
@@ -515,12 +501,12 @@ onMounted(() => {
|
||||
<n-col :span="1"></n-col>
|
||||
<n-col :span="7">
|
||||
<n-form-item
|
||||
v-if="addModel.db"
|
||||
v-if="createModel.db"
|
||||
path="db_user"
|
||||
:label="$t('websiteIndex.create.fields.dbUser.label')"
|
||||
>
|
||||
<n-input
|
||||
v-model:value="addModel.db_user"
|
||||
v-model:value="createModel.db_user"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$t('websiteIndex.create.fields.dbUser.placeholder')"
|
||||
@@ -530,12 +516,12 @@ onMounted(() => {
|
||||
<n-col :span="1"></n-col>
|
||||
<n-col :span="8">
|
||||
<n-form-item
|
||||
v-if="addModel.db"
|
||||
v-if="createModel.db"
|
||||
path="db_password"
|
||||
:label="$t('websiteIndex.create.fields.dbPassword.label')"
|
||||
>
|
||||
<n-input
|
||||
v-model:value="addModel.db_password"
|
||||
v-model:value="createModel.db_password"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$t('websiteIndex.create.fields.dbPassword.placeholder')"
|
||||
@@ -545,7 +531,7 @@ onMounted(() => {
|
||||
</n-row>
|
||||
<n-form-item path="path" :label="$t('websiteIndex.create.fields.path.label')">
|
||||
<n-input
|
||||
v-model:value="addModel.path"
|
||||
v-model:value="createModel.path"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$t('websiteIndex.create.fields.path.placeholder')"
|
||||
@@ -553,7 +539,7 @@ onMounted(() => {
|
||||
</n-form-item>
|
||||
<n-form-item path="remark" :label="$t('websiteIndex.create.fields.remark.label')">
|
||||
<n-input
|
||||
v-model:value="addModel.remark"
|
||||
v-model:value="createModel.remark"
|
||||
type="textarea"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$t('websiteIndex.create.fields.remark.placeholder')"
|
||||
@@ -567,7 +553,7 @@ onMounted(() => {
|
||||
block
|
||||
:loading="buttonLoading"
|
||||
:disabled="buttonDisabled"
|
||||
@click="handleAdd"
|
||||
@click="handleCreate"
|
||||
>
|
||||
{{ $t('websiteIndex.create.actions.submit') }}
|
||||
</n-button>
|
||||
|
||||
@@ -10,19 +10,23 @@ export interface Website {
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface WebsiteListen {
|
||||
address: string
|
||||
https: boolean
|
||||
quic: boolean
|
||||
}
|
||||
|
||||
export interface WebsiteSetting {
|
||||
id: number
|
||||
name: string
|
||||
ports: number[]
|
||||
ssl_ports: number[]
|
||||
quic_ports: number[]
|
||||
listens: WebsiteListen[]
|
||||
domains: string[]
|
||||
root: string
|
||||
path: string
|
||||
index: string
|
||||
index: string[]
|
||||
php: number
|
||||
open_basedir: boolean
|
||||
ssl: boolean
|
||||
https: boolean
|
||||
ssl_certificate: string
|
||||
ssl_certificate_key: string
|
||||
ssl_not_before: string
|
||||
|
||||
Reference in New Issue
Block a user