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

feat: PHP环境支持管理

This commit is contained in:
2026-01-08 01:43:21 +08:00
parent 45616b4ab3
commit 7940b50a72
23 changed files with 93 additions and 272 deletions

View File

@@ -491,7 +491,7 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
Cert: certPath,
Key: keyPath,
Protocols: lo.If(len(req.SSLProtocols) > 0, req.SSLProtocols).Else([]string{"TLSv1.2", "TLSv1.3"}),
Ciphers: lo.If(req.SSLCiphers != "", req.SSLCiphers).Else("HIGH:!aNULL:!MD5"),
Ciphers: lo.If(req.SSLCiphers != "", req.SSLCiphers).Else("ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305"),
HSTS: req.HSTS,
OCSP: req.OCSP,
HTTPRedirect: req.HTTPRedirect,

View File

@@ -147,7 +147,7 @@ func (v *baseVhost) Listen() []types.Listen {
// Apache 的监听配置通常在 VirtualHost 的参数中
// 例如: <VirtualHost *:80> 或 <VirtualHost 192.168.1.1:443>
for _, arg := range v.vhost.Args {
listen := types.Listen{Address: arg}
listen := types.Listen{Address: arg, Args: []string{}}
result = append(result, listen)
}

View File

@@ -121,7 +121,6 @@ func parseProxyFile(filePath string) (*types.Proxy, error) {
if rm := resolverPattern.FindStringSubmatch(blockContent); rm != nil {
parts := strings.Fields(rm[1])
proxy.Resolver = parts
proxy.AutoRefresh = true // 有 resolver 通常意味着需要自动刷新
}
// 解析 resolver_timeout
@@ -218,19 +217,16 @@ func generateProxyConfig(proxy types.Proxy) string {
sb.WriteString(fmt.Sprintf("location %s {\n", location))
// resolver 配置(如果启用自动刷新)
if proxy.AutoRefresh && len(proxy.Resolver) > 0 {
// resolver 配置
if len(proxy.Resolver) > 0 {
sb.WriteString(fmt.Sprintf(" resolver %s;\n", strings.Join(proxy.Resolver, " ")))
if proxy.ResolverTimeout > 0 {
sb.WriteString(fmt.Sprintf(" resolver_timeout %ds;\n", int(proxy.ResolverTimeout.Seconds())))
}
// 使用变量实现动态解析
sb.WriteString(fmt.Sprintf(" set $backend \"%s\";\n", proxy.Pass))
sb.WriteString(" proxy_pass $backend;\n")
} else {
sb.WriteString(fmt.Sprintf(" proxy_pass %s;\n", proxy.Pass))
}
sb.WriteString(fmt.Sprintf(" proxy_pass %s;\n", proxy.Pass))
// Host 头
if proxy.Host != "" {
sb.WriteString(fmt.Sprintf(" proxy_set_header Host \"%s\";\n", proxy.Host))

View File

@@ -158,7 +158,7 @@ func (v *baseVhost) Listen() []types.Listen {
var result []types.Listen
for _, dir := range directives {
l := v.parser.parameters2Slices(dir.GetParameters())
listen := types.Listen{Address: l[0]}
listen := types.Listen{Address: l[0], Args: []string{}}
for i := 1; i < len(l); i++ {
listen.Args = append(listen.Args, l[i])
}

View File

@@ -5,7 +5,6 @@ import "time"
// Proxy 反向代理配置
type Proxy struct {
Location string `form:"location" json:"location" validate:"required"` // 匹配路径,如: "/", "/api", "~ ^/api/v[0-9]+/"
AutoRefresh bool `form:"auto_refresh" json:"auto_refresh"` // 是否自动刷新解析
Pass string `form:"pass" json:"pass" validate:"required"` // 代理地址,如: "http://example.com", "http://backend"
Host string `form:"host" json:"host"` // 代理 Host如: "example.com"
SNI string `form:"sni" json:"sni"` // 代理 SNI如: "example.com"
@@ -18,7 +17,7 @@ type Proxy struct {
// Upstream 上游服务器配置
type Upstream struct {
Servers map[string]string `form:"servers" json:"servers" validate:"required"` // 上游服务器及权重,如: map["server1"] = "weight=5"
Servers map[string]string `form:"servers" json:"servers" validate:"required"` // 上游服务器及配置,如: map["server1"] = "weight=5 resolve"
Algo string `form:"algo" json:"algo"` // 负载均衡算法,如: "least_conn", "ip_hash"
Keepalive int `form:"keepalive" json:"keepalive"` // 保持连接数,如: 32
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/acepanel/panel/pkg/webserver/types"
)
// NewStaticVhost 创建纯静态虚拟主机实例
func NewStaticVhost(serverType Type, configDir string) (types.StaticVhost, error) {
switch serverType {
case TypeNginx:
@@ -20,7 +19,6 @@ func NewStaticVhost(serverType Type, configDir string) (types.StaticVhost, error
}
}
// NewPHPVhost 创建 PHP 虚拟主机实例
func NewPHPVhost(serverType Type, configDir string) (types.PHPVhost, error) {
switch serverType {
case TypeNginx:
@@ -32,7 +30,6 @@ func NewPHPVhost(serverType Type, configDir string) (types.PHPVhost, error) {
}
}
// NewProxyVhost 创建反向代理虚拟主机实例
func NewProxyVhost(serverType Type, configDir string) (types.ProxyVhost, error) {
switch serverType {
case TypeNginx:

View File

@@ -1,34 +0,0 @@
import { http } from '@/utils'
export default {
// 设为 CLI 版本
setCli: (version: number): any => http.Post(`/apps/php${version}/set_cli`),
// 获取配置
config: (version: number): any => http.Get(`/apps/php${version}/config`),
// 保存配置
saveConfig: (version: number, config: string): any =>
http.Post(`/apps/php${version}/config`, { config }),
// 获取FPM配置
fpmConfig: (version: number): any => http.Get(`/apps/php${version}/fpm_config`),
// 保存FPM配置
saveFPMConfig: (version: number, config: string): any =>
http.Post(`/apps/php${version}/fpm_config`, { config }),
// 负载状态
load: (version: number): any => http.Get(`/apps/php${version}/load`),
// 获取日志
log: (version: number): any => http.Get(`/apps/php${version}/log`),
// 清空日志
clearLog: (version: number): any => http.Post(`/apps/php${version}/clear_log`),
// 获取慢日志
slowLog: (version: number): any => http.Get(`/apps/php${version}/slow_log`),
// 清空慢日志
clearSlowLog: (version: number): any => http.Post(`/apps/php${version}/clear_slow_log`),
// 拓展列表
extensions: (version: number): any => http.Get(`/apps/php${version}/extensions`),
// 安装拓展
installExtension: (version: number, slug: string): any =>
http.Post(`/apps/php${version}/extensions`, { slug }),
// 卸载拓展
uninstallExtension: (version: number, slug: string): any =>
http.Delete(`/apps/php${version}/extensions`, { slug })
}

View File

@@ -0,0 +1,34 @@
import { http } from '@/utils'
export default {
// 设为 CLI 版本
setCli: (slug: number): any => http.Post(`/environment/php/${slug}/set_cli`),
// 获取配置
config: (slug: number): any => http.Get(`/environment/php/${slug}/config`),
// 保存配置
saveConfig: (slug: number, config: string): any =>
http.Post(`/environment/php/${slug}/config`, { config }),
// 获取FPM配置
fpmConfig: (slug: number): any => http.Get(`/environment/php/${slug}/fpm_config`),
// 保存FPM配置
saveFPMConfig: (slug: number, config: string): any =>
http.Post(`/environment/php/${slug}/fpm_config`, { config }),
// 负载状态
load: (slug: number): any => http.Get(`/environment/php/${slug}/load`),
// 获取日志
log: (slug: number): any => http.Get(`/environment/php/${slug}/log`),
// 清空日志
clearLog: (slug: number): any => http.Post(`/environment/php/${slug}/clear_log`),
// 获取慢日志
slowLog: (slug: number): any => http.Get(`/environment/php/${slug}/slow_log`),
// 清空慢日志
clearSlowLog: (slug: number): any => http.Post(`/environment/php/${slug}/clear_slow_log`),
// 拓展列表
modules: (slug: number): any => http.Get(`/environment/php/${slug}/modules`),
// 安装拓展
installModule: (slug: number, module: string): any =>
http.Post(`/environment/php/${slug}/modules`, { slug: module }),
// 卸载拓展
uninstallModule: (slug: number, module: string): any =>
http.Delete(`/environment/php/${slug}/modules`, { slug: module })
}

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import environment from '@/api/panel/environment'
import { router } from '@/router'
import { renderLocalIcon } from '@/utils'
import { NButton, NDataTable, NFlex, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
@@ -86,8 +87,8 @@ const columns: any = [
NButton,
{
size: 'small',
type: 'info'
//onClick: () => handleManage(row.slug)
type: 'info',
onClick: () => handleManage(row.type, row.slug)
},
{
default: () => $gettext('Manage')
@@ -195,6 +196,10 @@ const handleUninstall = (type: string, slug: string) => {
})
}
const handleManage = (type: string, slug: string) => {
router.push({ name: 'environment-' + type, params: { slug } })
}
onMounted(() => {
refresh()
})

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
defineOptions({
name: 'apps-php74-index'
})
import PhpView from '@/views/apps/php/PhpView.vue'
</script>
<template>
<php-view :version="74" />
</template>

View File

@@ -1,22 +0,0 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'php74',
path: '/apps/php74',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-php74-index',
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 7.4',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
defineOptions({
name: 'apps-php80-index'
})
import PhpView from '@/views/apps/php/PhpView.vue'
</script>
<template>
<php-view :version="80" />
</template>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
defineOptions({
name: 'apps-php81-index'
})
import PhpView from '@/views/apps/php/PhpView.vue'
</script>
<template>
<php-view :version="81" />
</template>

View File

@@ -1,22 +0,0 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'php81',
path: '/apps/php81',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-php81-index',
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 8.1',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
defineOptions({
name: 'apps-php82-index'
})
import PhpView from '@/views/apps/php/PhpView.vue'
</script>
<template>
<php-view :version="82" />
</template>

View File

@@ -1,22 +0,0 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'php82',
path: '/apps/php82',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-php82-index',
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 8.2',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
defineOptions({
name: 'apps-php83-index'
})
import PhpView from '@/views/apps/php/PhpView.vue'
</script>
<template>
<php-view :version="83" />
</template>

View File

@@ -1,22 +0,0 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'php83',
path: '/apps/php83',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-php83-index',
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 8.3',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
defineOptions({
name: 'apps-php84-index'
})
import PhpView from '@/views/apps/php/PhpView.vue'
</script>
<template>
<php-view :version="84" />
</template>

View File

@@ -1,22 +0,0 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'php84',
path: '/apps/php84',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-php84-index',
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 8.4',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType

View File

@@ -1,44 +1,39 @@
<script setup lang="ts">
import ServiceStatus from '@/components/common/ServiceStatus.vue'
import { NButton, NDataTable, NPopconfirm } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import php from '@/api/apps/php'
import ServiceStatus from '@/components/common/ServiceStatus.vue'
import php from '@/api/panel/environment/php'
const route = useRoute()
const slug = Number(route.params.slug)
const { $gettext } = useGettext()
const props = defineProps({
version: {
type: Number,
required: true
}
})
const { version } = toRefs(props)
const currentTab = ref('status')
const { data: config } = useRequest(php.config(version.value), {
const { data: config } = useRequest(php.config(slug), {
initialData: ''
})
const { data: fpmConfig } = useRequest(php.fpmConfig(version.value), {
const { data: fpmConfig } = useRequest(php.fpmConfig(slug), {
initialData: ''
})
const { data: log } = useRequest(php.log(version.value), {
const { data: log } = useRequest(php.log(slug), {
initialData: ''
})
const { data: slowLog } = useRequest(php.slowLog(version.value), {
const { data: slowLog } = useRequest(php.slowLog(slug), {
initialData: ''
})
const { data: load } = useRequest(php.load(version.value), {
const { data: load } = useRequest(php.load(slug), {
initialData: []
})
const { data: extensions } = useRequest(php.extensions(version.value), {
const { data: modules } = useRequest(php.modules(slug), {
initialData: []
})
const extensionColumns: any = [
const moduleColumns: any = [
{
title: $gettext('Extension Name'),
title: $gettext('Module Name'),
key: 'name',
minWidth: 250,
resizable: true,
@@ -62,7 +57,7 @@ const extensionColumns: any = [
? h(
NPopconfirm,
{
onPositiveClick: () => handleInstallExtension(row.slug)
onPositiveClick: () => handleInstallModule(row.slug)
},
{
default: () => {
@@ -87,7 +82,7 @@ const extensionColumns: any = [
? h(
NPopconfirm,
{
onPositiveClick: () => handleUninstallExtension(row.slug)
onPositiveClick: () => handleUninstallModule(row.slug)
},
{
default: () => {
@@ -132,43 +127,43 @@ const loadColumns: any = [
]
const handleSetCli = async () => {
useRequest(php.setCli(version.value)).onSuccess(() => {
useRequest(php.setCli(slug)).onSuccess(() => {
window.$message.success($gettext('Set successfully'))
})
}
const handleSaveConfig = async () => {
useRequest(php.saveConfig(version.value, config.value)).onSuccess(() => {
useRequest(php.saveConfig(slug, config.value)).onSuccess(() => {
window.$message.success($gettext('Saved successfully'))
})
}
const handleSaveFPMConfig = async () => {
useRequest(php.saveFPMConfig(version.value, fpmConfig.value)).onSuccess(() => {
useRequest(php.saveFPMConfig(slug, fpmConfig.value)).onSuccess(() => {
window.$message.success($gettext('Saved successfully'))
})
}
const handleClearLog = async () => {
useRequest(php.clearLog(version.value)).onSuccess(() => {
useRequest(php.clearLog(slug)).onSuccess(() => {
window.$message.success($gettext('Cleared successfully'))
})
}
const handleClearSlowLog = async () => {
useRequest(php.clearSlowLog(version.value)).onSuccess(() => {
useRequest(php.clearSlowLog(slug)).onSuccess(() => {
window.$message.success($gettext('Cleared successfully'))
})
}
const handleInstallExtension = async (slug: string) => {
useRequest(php.installExtension(version.value, slug)).onSuccess(() => {
const handleInstallModule = async (module: string) => {
useRequest(php.installModule(slug, module)).onSuccess(() => {
window.$message.success($gettext('Task submitted, please check progress in background tasks'))
})
}
const handleUninstallExtension = async (name: string) => {
useRequest(php.uninstallExtension(version.value, name)).onSuccess(() => {
const handleUninstallModule = async (module: string) => {
useRequest(php.uninstallModule(slug, module)).onSuccess(() => {
window.$message.success($gettext('Task submitted, please check progress in background tasks'))
})
}
@@ -179,21 +174,22 @@ const handleUninstallExtension = async (name: string) => {
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="status" :tab="$gettext('Running Status')">
<n-flex vertical>
<service-status :service="`php-fpm-${version}`" show-reload />
<n-card> PHP {{ slug }} </n-card>
<service-status :service="`php-fpm-${slug}`" show-reload />
<n-button type="info" @click="handleSetCli">
{{ $gettext('Set as CLI Default Version') }}
</n-button>
</n-flex>
</n-tab-pane>
<n-tab-pane name="extensions" :tab="$gettext('Extension Management')">
<n-tab-pane name="modules" :tab="$gettext('Module Management')">
<n-flex vertical>
<n-data-table
striped
remote
:scroll-x="1000"
:loading="false"
:columns="extensionColumns"
:data="extensions"
:columns="moduleColumns"
:data="modules"
:row-key="(row: any) => row.slug"
/>
</n-flex>
@@ -204,7 +200,7 @@ const handleUninstallExtension = async (name: string) => {
{{
$gettext(
'This modifies the PHP %{ version } main configuration file. If you do not understand the meaning of each parameter, please do not modify it randomly!',
{ version: version }
{ version: slug }
)
}}
</n-alert>
@@ -222,7 +218,7 @@ const handleUninstallExtension = async (name: string) => {
{{
$gettext(
'This modifies the PHP %{ version } FPM configuration file. If you do not understand the meaning of each parameter, please do not modify it randomly!',
{ version: version }
{ version: slug }
)
}}
</n-alert>
@@ -245,7 +241,7 @@ const handleUninstallExtension = async (name: string) => {
/>
</n-tab-pane>
<n-tab-pane name="run-log" :tab="$gettext('Runtime Logs')">
<realtime-log :service="'php-fpm-' + version" />
<realtime-log :service="'php-fpm-' + slug" />
</n-tab-pane>
<n-tab-pane name="log" :tab="$gettext('Error Logs')">
<n-flex vertical>
@@ -270,3 +266,5 @@ const handleUninstallExtension = async (name: string) => {
</n-tabs>
</common-page>
</template>
<style scoped lang="scss"></style>

View File

@@ -3,17 +3,19 @@ import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'php80',
path: '/apps/php80',
component: Layout,
name: 'environment',
path: '/environment',
isHidden: true,
component: Layout,
children: [
{
name: 'apps-php80-index',
path: '',
component: () => import('./IndexView.vue'),
name: 'environment-php',
path: 'php/:slug',
isHidden: true,
component: () => import('./PHPView.vue'),
meta: {
title: 'PHP 8.0',
title: 'PHP',
icon: 'mdi:language-php',
role: ['admin'],
requireAuth: true
}

View File

@@ -24,8 +24,8 @@ export default {
{
name: 'home-update',
path: 'update',
component: () => import('./UpdateView.vue'),
isHidden: true,
component: () => import('./UpdateView.vue'),
meta: {
title: 'Update',
icon: 'mdi:archive-arrow-up-outline',