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

feat: apache

This commit is contained in:
2026-01-13 00:39:34 +08:00
parent cd85032ecb
commit 02e0aef265
10 changed files with 317 additions and 4 deletions

View File

@@ -8,6 +8,7 @@ package main
import (
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/apps/apache"
"github.com/acepanel/panel/internal/apps/codeserver"
"github.com/acepanel/panel/internal/apps/docker"
"github.com/acepanel/panel/internal/apps/fail2ban"
@@ -127,6 +128,7 @@ func initWeb() (*app.Web, error) {
toolboxLogService := service.NewToolboxLogService(locale, db, containerImageRepo, settingRepo)
webHookRepo := data.NewWebHookRepo(locale, db, logger)
webHookService := service.NewWebHookService(webHookRepo)
apacheApp := apache.NewApp(locale)
codeserverApp := codeserver.NewApp()
dockerApp := docker.NewApp()
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
@@ -147,7 +149,7 @@ func initWeb() (*app.Web, error) {
rsyncApp := rsync.NewApp(locale)
s3fsApp := s3fs.NewApp(locale)
supervisorApp := supervisor.NewApp(locale)
loader := bootstrap.NewLoader(codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, environmentService, environmentPHPService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, webHookService, loader)
wsService := service.NewWsService(locale, config, logger, sshRepo)
ws := route.NewWs(wsService)

View File

@@ -8,6 +8,7 @@ package main
import (
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/apps/apache"
"github.com/acepanel/panel/internal/apps/codeserver"
"github.com/acepanel/panel/internal/apps/docker"
"github.com/acepanel/panel/internal/apps/fail2ban"
@@ -72,6 +73,7 @@ func initCli() (*app.Cli, error) {
cli := route.NewCli(locale, cliService)
command := bootstrap.NewCli(locale, cli)
gormigrate := bootstrap.NewMigrate(db)
apacheApp := apache.NewApp(locale)
codeserverApp := codeserver.NewApp()
dockerApp := docker.NewApp()
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
@@ -92,7 +94,7 @@ func initCli() (*app.Cli, error) {
rsyncApp := rsync.NewApp(locale)
s3fsApp := s3fs.NewApp(locale)
supervisorApp := supervisor.NewApp(locale)
loader := bootstrap.NewLoader(codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
appCli := app.NewCli(command, gormigrate, loader)
return appCli, nil
}

157
internal/apps/apache/app.go Normal file
View File

@@ -0,0 +1,157 @@
package apache
import (
"fmt"
"net/http"
"regexp"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/service"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
"github.com/acepanel/panel/pkg/systemctl"
"github.com/acepanel/panel/pkg/tools"
"github.com/acepanel/panel/pkg/types"
)
type App struct {
t *gotext.Locale
}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.SaveConfig)
r.Get("/error_log", s.ErrorLog)
r.Post("/clear_error_log", s.ClearErrorLog)
}
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/apache/conf/httpd.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, config)
}
func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Write(fmt.Sprintf("%s/server/apache/conf/httpd.conf", app.Root), req.Config, 0600); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Reload("apache"); err != nil {
_, err = shell.Execf("%s/server/apache/bin/apachectl configtest", app.Root)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload apache: %v", err))
return
}
service.Success(w, nil)
}
func (s *App) ErrorLog(w http.ResponseWriter, r *http.Request) {
service.Success(w, fmt.Sprintf("%s/%s", app.Root, "server/apache/logs/error_log"))
}
func (s *App) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
if _, err := shell.Execf("cat /dev/null > %s/%s", app.Root, "server/apache/logs/error_log"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, err := shell.Execf("curl -s http://127.0.0.1/server_status?auto 2>/dev/null || true")
if err != nil {
service.Success(w, []types.NV{})
return
}
var data []types.NV
workers, err := shell.Execf("ps aux | grep httpd | grep -v grep | wc -l")
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get apache workers: %v", err))
return
}
data = append(data, types.NV{
Name: s.t.Get("Workers"),
Value: workers,
})
out, err := shell.Execf("ps aux | grep httpd | grep -v grep | awk '{memsum+=$6};END {print memsum}'")
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get apache workers: %v", err))
return
}
mem := tools.FormatBytes(cast.ToFloat64(out))
data = append(data, types.NV{
Name: s.t.Get("Memory"),
Value: mem,
})
// Parse server-status output
if match := regexp.MustCompile(`Total Accesses:\s*(\d+)`).FindStringSubmatch(status); len(match) == 2 {
data = append(data, types.NV{
Name: s.t.Get("Total Accesses"),
Value: match[1],
})
}
if match := regexp.MustCompile(`Total kBytes:\s*(\d+)`).FindStringSubmatch(status); len(match) == 2 {
data = append(data, types.NV{
Name: s.t.Get("Total Traffic"),
Value: tools.FormatBytes(cast.ToFloat64(match[1]) * 1024),
})
}
if match := regexp.MustCompile(`BusyWorkers:\s*(\d+)`).FindStringSubmatch(status); len(match) == 2 {
data = append(data, types.NV{
Name: s.t.Get("Busy Workers"),
Value: match[1],
})
}
if match := regexp.MustCompile(`IdleWorkers:\s*(\d+)`).FindStringSubmatch(status); len(match) == 2 {
data = append(data, types.NV{
Name: s.t.Get("Idle Workers"),
Value: match[1],
})
}
if match := regexp.MustCompile(`ReqPerSec:\s*([\d.]+)`).FindStringSubmatch(status); len(match) == 2 {
data = append(data, types.NV{
Name: s.t.Get("Requests/sec"),
Value: match[1],
})
}
if match := regexp.MustCompile(`BytesPerSec:\s*([\d.]+)`).FindStringSubmatch(status); len(match) == 2 {
data = append(data, types.NV{
Name: s.t.Get("Bytes/sec"),
Value: tools.FormatBytes(cast.ToFloat64(match[1])),
})
}
service.Success(w, data)
}

View File

@@ -0,0 +1,5 @@
package apache
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}

View File

@@ -3,6 +3,7 @@ package apps
import (
"github.com/google/wire"
"github.com/acepanel/panel/internal/apps/apache"
"github.com/acepanel/panel/internal/apps/codeserver"
"github.com/acepanel/panel/internal/apps/docker"
"github.com/acepanel/panel/internal/apps/fail2ban"
@@ -26,6 +27,7 @@ import (
)
var ProviderSet = wire.NewSet(
apache.NewApp,
codeserver.NewApp,
docker.NewApp,
fail2ban.NewApp,

View File

@@ -1,6 +1,7 @@
package bootstrap
import (
"github.com/acepanel/panel/internal/apps/apache"
"github.com/acepanel/panel/internal/apps/codeserver"
"github.com/acepanel/panel/internal/apps/docker"
"github.com/acepanel/panel/internal/apps/fail2ban"
@@ -25,6 +26,7 @@ import (
)
func NewLoader(
apache *apache.App,
codeserver *codeserver.App,
docker *docker.App,
fail2ban *fail2ban.App,
@@ -47,6 +49,6 @@ func NewLoader(
supervisor *supervisor.App,
) *apploader.Loader {
loader := new(apploader.Loader)
loader.Add(codeserver, docker, fail2ban, frp, gitea, mariadb, memcached, minio, mysql, nginx, openresty, percona, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor)
loader.Add(apache, codeserver, docker, fail2ban, frp, gitea, mariadb, memcached, minio, mysql, nginx, openresty, percona, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor)
return loader
}

View File

@@ -165,7 +165,12 @@ func (v *baseVhost) Listen() []types.Listen {
func (v *baseVhost) SetListen(listens []types.Listen) error {
var args []string
for _, l := range listens {
args = append(args, l.Address)
addr := l.Address
// 如果只是端口号,添加 *: 前缀
if !strings.Contains(addr, ":") {
addr = "*:" + addr
}
args = append(args, addr)
}
v.vhost.Args = args
return nil

View File

@@ -0,0 +1,14 @@
import { http } from '@/utils'
export default {
// 负载状态
load: (): any => http.Get('/apps/apache/load'),
// 获取配置
config: (): any => http.Get('/apps/apache/config'),
// 保存配置
saveConfig: (config: string): any => http.Post('/apps/apache/config', { config }),
// 获取错误日志
errorLog: (): any => http.Get('/apps/apache/error_log'),
// 清空错误日志
clearErrorLog: (): any => http.Post('/apps/apache/clear_error_log')
}

View File

@@ -0,0 +1,102 @@
<script setup lang="ts">
defineOptions({
name: 'apps-apache-index'
})
import { useGettext } from 'vue3-gettext'
import apache from '@/api/apps/apache'
import ServiceStatus from '@/components/common/ServiceStatus.vue'
const { $gettext } = useGettext()
const currentTab = ref('status')
const { data: config } = useRequest(apache.config, {
initialData: ''
})
const { data: errorLog } = useRequest(apache.errorLog, {
initialData: ''
})
const { data: load } = useRequest(apache.load, {
initialData: []
})
const columns: any = [
{
title: $gettext('Property'),
key: 'name',
minWidth: 200,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: $gettext('Current Value'),
key: 'value',
minWidth: 200,
ellipsis: { tooltip: true }
}
]
const handleSaveConfig = () => {
useRequest(apache.saveConfig(config.value)).onSuccess(() => {
window.$message.success($gettext('Saved successfully'))
})
}
const handleClearErrorLog = () => {
useRequest(apache.clearErrorLog()).onSuccess(() => {
window.$message.success($gettext('Cleared successfully'))
})
}
</script>
<template>
<common-page show-footer>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="status" :tab="$gettext('Running Status')">
<service-status service="apache" show-reload />
</n-tab-pane>
<n-tab-pane name="config" :tab="$gettext('Modify Configuration')">
<n-flex vertical>
<n-alert type="warning">
{{
$gettext(
'This modifies the %{name} main configuration file. If you do not understand the meaning of each parameter, please do not modify it randomly!',
{ name: 'Apache' }
)
}}
</n-alert>
<common-editor v-model:value="config" lang="apache" height="60vh" />
<n-flex>
<n-button type="primary" @click="handleSaveConfig">
{{ $gettext('Save') }}
</n-button>
</n-flex>
</n-flex>
</n-tab-pane>
<n-tab-pane name="load" :tab="$gettext('Load Status')">
<n-data-table
striped
remote
:scroll-x="400"
:loading="false"
:columns="columns"
:data="load"
/>
</n-tab-pane>
<n-tab-pane name="run-log" :tab="$gettext('Runtime Logs')">
<realtime-log service="apache" />
</n-tab-pane>
<n-tab-pane name="error-log" :tab="$gettext('Error Logs')">
<n-flex vertical>
<n-flex>
<n-button type="primary" @click="handleClearErrorLog">
{{ $gettext('Clear Log') }}
</n-button>
</n-flex>
<realtime-log :path="errorLog" />
</n-flex>
</n-tab-pane>
</n-tabs>
</common-page>
</template>

View File

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