2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 03:07:20 +08:00

feat: 网站管理优化

This commit is contained in:
耗子
2024-10-15 00:29:12 +08:00
parent e8a01e2d04
commit e5ba1375b1
17 changed files with 201 additions and 161 deletions

3
go.mod
View File

@@ -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
View File

@@ -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=

View File

@@ -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"`

View File

@@ -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
}
}

View File

@@ -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"`

View File

@@ -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"`
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 删除文件/目录

View File

@@ -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)
}
}

View File

@@ -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())

View File

@@ -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 {

View File

@@ -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'),
// 保存默认配置

View File

@@ -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="HTTPSSSL端口">
<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="QUICHTTP3端口">
<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>

View File

@@ -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>

View File

@@ -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