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

feat: 网站重构2

This commit is contained in:
2025-12-01 23:31:59 +08:00
parent 2b8890305c
commit 0ebd1c0699
8 changed files with 288 additions and 230 deletions

View File

@@ -295,9 +295,26 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
return nil, err
}
// 创建配置文件目录
if err = os.MkdirAll(filepath.Join(app.Root, "sites", req.Name, "config", "vhost"), 0644); err != nil {
return nil, err
}
if err = os.MkdirAll(filepath.Join(app.Root, "sites", req.Name, "config", "global"), 0644); err != nil {
return nil, err
}
// 创建日志目录
if err = os.MkdirAll(filepath.Join(app.Root, "sites", req.Name, "log"), 0644); err != nil {
return nil, err
}
// 反向代理支持
if proxyVhost, ok := vhost.(webservertypes.ProxyVhost); ok {
if err = proxyVhost.SetProxies([]webservertypes.Proxy{req.Proxy}); err != nil {
if err = proxyVhost.SetProxies([]webservertypes.Proxy{
{
Location: "^~ /",
Pass: req.Proxy,
},
}); err != nil {
return nil, err
}
}

View File

@@ -2,7 +2,6 @@ package request
import (
"github.com/acepanel/panel/pkg/types"
webservertypes "github.com/acepanel/panel/pkg/webserver/types"
)
type WebsiteDefaultConfig struct {
@@ -28,8 +27,8 @@ type WebsiteCreate struct {
DBPassword string `form:"db_password" json:"db_password" validate:"requiredIf:DB,true"`
Remark string `form:"remark" json:"remark"`
PHP uint `form:"php" json:"php" validate:"requiredIf:Type,php"` // 仅 PHP 网站需要
Proxy webservertypes.Proxy `form:"proxy" json:"proxy" validate:"requiredIf:Type,proxy"` // 仅反向代理网站需要
PHP uint `form:"php" json:"php" validate:"requiredIf:Type,php"` // 仅 PHP 网站需要
Proxy string `form:"proxy" json:"proxy" validate:"requiredIf:Type,proxy"` // 仅反向代理网站需要
}
type WebsiteDelete struct {

View File

@@ -112,7 +112,7 @@ func (s *WebsiteService) Create(w http.ResponseWriter, r *http.Request) {
if len(req.Path) == 0 {
req.Path, _ = s.settingRepo.Get(biz.SettingKeyWebsitePath)
req.Path = filepath.Join(req.Path, req.Name)
req.Path = filepath.Join(req.Path, req.Name, "public")
}
if _, err = s.websiteRepo.Create(req); err != nil {

View File

@@ -254,7 +254,7 @@ func (v *baseVhost) Save() error {
}
func (v *baseVhost) Reload() error {
parts := strings.Fields("systemctl reload openresty")
parts := strings.Fields("systemctl reload nginx")
if err := exec.Command(parts[0], parts[1:]...).Run(); err != nil {
if testErr := exec.Command("nginx", "-t").Run(); testErr != nil {
return fmt.Errorf("nginx config test failed: %w", testErr)

View File

@@ -1,11 +1,13 @@
<script setup lang="ts">
import website from '@/api/panel/website'
import { NButton } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
import website from '@/api/panel/website'
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const type = defineModel<string>('type', { type: String, required: true })
const { $gettext } = useGettext()
const bulkCreate = ref('')

View File

@@ -0,0 +1,245 @@
<script setup lang="ts">
import { NButton, NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import home from '@/api/panel/home'
import website from '@/api/panel/website'
import { generateRandomString } from '@/utils'
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const type = defineModel<string>('type', { type: String, required: true })
const { $gettext } = useGettext()
const createModel = ref({
type: '',
name: '',
listens: [] as Array<string>,
domains: [] as Array<string>,
path: '',
db: false,
db_type: '0',
db_name: '',
db_user: '',
db_password: '',
remark: '',
php: 0,
proxy: ''
})
const { data: installedDbAndPhp } = useRequest(home.installedDbAndPhp, {
initialData: {
php: [
{
label: $gettext('Not used'),
value: 0
}
],
db: [
{
label: '',
value: ''
}
]
}
})
const handleCreate = async () => {
createModel.value.type = type.value
// 去除空的域名和端口
createModel.value.domains = createModel.value.domains.filter((item) => item !== '')
createModel.value.listens = createModel.value.listens.filter((item) => item !== '')
// 端口为空自动添加 80 端口
if (createModel.value.listens.length === 0) {
createModel.value.listens.push('80')
}
// 端口中去掉 443 端口,不允许在未配置证书下监听 443 端口
createModel.value.listens = createModel.value.listens.filter((item) => item !== '443')
useRequest(website.create(createModel.value)).onSuccess(() => {
window.$bus.emit('website:refresh')
window.$message.success(
$gettext('Website %{ name } created successfully', { name: createModel.value.name })
)
show.value = false
createModel.value = {
type: '',
name: '',
domains: [] as Array<string>,
listens: [] as Array<string>,
db: false,
db_type: '0',
db_name: '',
db_user: '',
db_password: '',
path: '',
remark: '',
php: 0,
proxy: ''
}
})
}
const formatDbValue = (value: string) => {
value = value.replace(/\./g, '_')
value = value.replace(/-/g, '_')
if (value.length > 16) {
value = value.substring(0, 16)
}
return value
}
</script>
<template>
<n-modal
v-model:show="show"
:title="$gettext('Create Website')"
preset="card"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="show = false"
>
<n-form :model="createModel">
<n-form-item path="name" :label="$gettext('Name')">
<n-input
v-model:value="createModel.name"
type="text"
@keydown.enter.prevent
:placeholder="
$gettext('Must use English for the website name, it cannot be modified after setting')
"
/>
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="11">
<n-form-item :label="$gettext('Domain')">
<n-dynamic-input
v-model:value="createModel.domains"
placeholder="example.com"
:min="1"
show-sort-button
/>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="11">
<n-form-item :label="$gettext('Port')">
<n-dynamic-input
v-model:value="createModel.listens"
placeholder="80"
:min="1"
show-sort-button
/>
</n-form-item>
</n-col>
</n-row>
<n-row v-if="type == 'php'" :gutter="[0, 24]">
<n-col :span="11">
<n-form-item path="php" :label="$gettext('PHP Version')">
<n-select
v-model:value="createModel.php"
:options="installedDbAndPhp.php"
:placeholder="$gettext('Select PHP Version')"
@keydown.enter.prevent
>
</n-select>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="11">
<n-form-item path="db" :label="$gettext('Database')">
<n-select
v-model:value="createModel.db_type"
:options="installedDbAndPhp.db"
:placeholder="$gettext('Select Database')"
@keydown.enter.prevent
@update:value="
() => {
createModel.db = createModel.db_type != '0'
createModel.db_name = formatDbValue(createModel.name)
createModel.db_user = formatDbValue(createModel.name)
createModel.db_password = generateRandomString(16)
}
"
>
</n-select>
</n-form-item>
</n-col>
</n-row>
<n-row v-if="type == 'php'" :gutter="[0, 24]">
<n-col :span="7">
<n-form-item v-if="createModel.db" path="db_name" :label="$gettext('Database Name')">
<n-input
v-model:value="createModel.db_name"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database Name')"
/>
</n-form-item>
</n-col>
<n-col :span="1"></n-col>
<n-col :span="7">
<n-form-item v-if="createModel.db" path="db_user" :label="$gettext('Database User')">
<n-input
v-model:value="createModel.db_user"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database User')"
/>
</n-form-item>
</n-col>
<n-col :span="1"></n-col>
<n-col :span="8">
<n-form-item
v-if="createModel.db"
path="db_password"
:label="$gettext('Database Password')"
>
<n-input
v-model:value="createModel.db_password"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database Password')"
/>
</n-form-item>
</n-col>
</n-row>
<n-form-item v-if="type != 'proxy'" path="path" :label="$gettext('Directory')">
<n-input
v-model:value="createModel.path"
type="text"
@keydown.enter.prevent
:placeholder="
$gettext(
'Website root directory (if left empty, defaults to website directory/website name/public)'
)
"
/>
</n-form-item>
<n-form-item v-if="type == 'proxy'" path="path" :label="$gettext('Proxy Target')">
<n-input
v-model:value="createModel.proxy"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Proxy target address (e.g., http://127.0.0.1:3000)')"
/>
</n-form-item>
<n-form-item path="remark" :label="$gettext('Remark')">
<n-input
v-model:value="createModel.remark"
type="textarea"
@keydown.enter.prevent
:placeholder="$gettext('Remark')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">
{{ $gettext('Create') }}
</n-button>
</n-modal>
</template>
<style scoped lang="scss"></style>

View File

@@ -3,10 +3,15 @@ defineOptions({
name: 'website-index'
})
import BulkCreateModal from '@/views/website/BulkCreateModal.vue'
import CreateModal from '@/views/website/CreateModal.vue'
import ListView from '@/views/website/ListView.vue'
import SettingView from '@/views/website/SettingView.vue'
const currentTab = ref('proxy')
const createModal = ref(false)
const bulkCreateModal = ref(false)
</script>
<template>
@@ -19,7 +24,15 @@ const currentTab = ref('proxy')
<n-tab name="setting" :tab="$gettext('Settings')" />
</n-tabs>
</template>
<list-view v-if="currentTab != 'setting'" v-model:type="currentTab" />
<list-view
v-if="currentTab != 'setting'"
v-model:type="currentTab"
v-model:create-modal="createModal"
v-model:bulk-create-modal="bulkCreateModal"
/>
<setting-view v-if="currentTab === 'setting'" />
<create-modal v-model:show="createModal" v-model:type="currentTab" />
<bulk-create-modal v-model:show="bulkCreateModal" v-model:type="currentTab" />
</common-page>
</template>

View File

@@ -2,13 +2,13 @@
import { NButton, NCheckbox, NDataTable, NFlex, NInput, NPopconfirm, NSwitch, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import home from '@/api/panel/home'
import website from '@/api/panel/website'
import { useFileStore } from '@/store'
import { generateRandomString, isNullOrUndef } from '@/utils'
import BulkCreate from '@/views/website/BulkCreate.vue'
import { isNullOrUndef } from '@/utils'
const type = defineModel<string>('type', { type: String, required: true }) // 网站类型
const createModal = defineModel<boolean>('createModal', { type: Boolean, required: true }) // 创建网站
const bulkCreateModal = defineModel<boolean>('bulkCreateModal', { type: Boolean, required: true }) // 批量创建网站
const fileStore = useFileStore()
const { $gettext } = useGettext()
@@ -203,44 +203,11 @@ const columns: any = [
}
]
const createModal = ref(false)
const bulkCreateModal = ref(false)
const createModel = ref({
name: '',
listens: [] as Array<string>,
domains: [] as Array<string>,
path: '',
php: 0,
db: false,
db_type: '0',
db_name: '',
db_user: '',
db_password: '',
remark: ''
})
const deleteModel = ref({
path: true,
db: false
})
const { data: installedDbAndPhp } = useRequest(home.installedDbAndPhp, {
initialData: {
php: [
{
label: $gettext('Not used'),
value: 0
}
],
db: [
{
label: '',
value: ''
}
]
}
})
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
(page, pageSize) => website.list(type.value, page, pageSize),
{
@@ -288,38 +255,6 @@ const handleDelete = (id: number) => {
})
}
const handleCreate = async () => {
// 去除空的域名和端口
createModel.value.domains = createModel.value.domains.filter((item) => item !== '')
createModel.value.listens = createModel.value.listens.filter((item) => item !== '')
// 端口为空自动添加 80 端口
if (createModel.value.listens.length === 0) {
createModel.value.listens.push('80')
}
// 端口中去掉 443 端口nginx 不允许在未配置证书下监听 443 端口
createModel.value.listens = createModel.value.listens.filter((item) => item !== '443')
useRequest(website.create(createModel.value)).onSuccess(() => {
refresh()
window.$message.success(
$gettext('Website %{ name } created successfully', { name: createModel.value.name })
)
createModal.value = false
createModel.value = {
name: '',
domains: [] as Array<string>,
listens: [] as Array<string>,
php: 0,
db: false,
db_type: '0',
db_name: '',
db_user: '',
db_password: '',
path: '',
remark: ''
}
})
}
const bulkDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info($gettext('Please select the websites to delete'))
@@ -334,16 +269,6 @@ const bulkDelete = async () => {
window.$message.success($gettext('Deleted successfully'))
}
const formatDbValue = (value: string) => {
value = value.replace(/\./g, '_')
value = value.replace(/-/g, '_')
if (value.length > 16) {
value = value.substring(0, 16)
}
return value
}
onMounted(() => {
refresh()
window.$bus.on('website:refresh', refresh)
@@ -394,147 +319,4 @@ onMounted(() => {
}"
/>
</n-flex>
<n-modal
v-model:show="createModal"
:title="$gettext('Create Website')"
preset="card"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="createModal = false"
>
<n-form :model="createModel">
<n-form-item path="name" :label="$gettext('Website Name')">
<n-input
v-model:value="createModel.name"
type="text"
@keydown.enter.prevent
:placeholder="
$gettext(
'Recommended to use English for the website name, it cannot be modified after setting'
)
"
/>
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="11">
<n-form-item :label="$gettext('Domain')">
<n-dynamic-input
v-model:value="createModel.domains"
placeholder="example.com"
:min="1"
show-sort-button
/>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="11">
<n-form-item :label="$gettext('Port')">
<n-dynamic-input
v-model:value="createModel.listens"
placeholder="80"
:min="1"
show-sort-button
/>
</n-form-item>
</n-col>
</n-row>
<n-row :gutter="[0, 24]">
<n-col :span="11">
<n-form-item path="php" :label="$gettext('PHP Version')">
<n-select
v-model:value="createModel.php"
:options="installedDbAndPhp.php"
:placeholder="$gettext('Select PHP Version')"
@keydown.enter.prevent
>
</n-select>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="11">
<n-form-item path="db" :label="$gettext('Database')">
<n-select
v-model:value="createModel.db_type"
:options="installedDbAndPhp.db"
:placeholder="$gettext('Select Database')"
@keydown.enter.prevent
@update:value="
() => {
createModel.db = createModel.db_type != '0'
createModel.db_name = formatDbValue(createModel.name)
createModel.db_user = formatDbValue(createModel.name)
createModel.db_password = generateRandomString(16)
}
"
>
</n-select>
</n-form-item>
</n-col>
</n-row>
<n-row :gutter="[0, 24]">
<n-col :span="7">
<n-form-item v-if="createModel.db" path="db_name" :label="$gettext('Database Name')">
<n-input
v-model:value="createModel.db_name"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database Name')"
/>
</n-form-item>
</n-col>
<n-col :span="1"></n-col>
<n-col :span="7">
<n-form-item v-if="createModel.db" path="db_user" :label="$gettext('Database User')">
<n-input
v-model:value="createModel.db_user"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database User')"
/>
</n-form-item>
</n-col>
<n-col :span="1"></n-col>
<n-col :span="8">
<n-form-item
v-if="createModel.db"
path="db_password"
:label="$gettext('Database Password')"
>
<n-input
v-model:value="createModel.db_password"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database Password')"
/>
</n-form-item>
</n-col>
</n-row>
<n-form-item path="path" :label="$gettext('Directory')">
<n-input
v-model:value="createModel.path"
type="text"
@keydown.enter.prevent
:placeholder="
$gettext(
'Website root directory (if left empty, defaults to website directory/website name)'
)
"
/>
</n-form-item>
<n-form-item path="remark" :label="$gettext('Remark')">
<n-input
v-model:value="createModel.remark"
type="textarea"
@keydown.enter.prevent
:placeholder="$gettext('Remark')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">
{{ $gettext('Create') }}
</n-button>
</n-modal>
<bulk-create v-model:show="bulkCreateModal" />
</template>