From c5ec454b2619bd2ac2e22ba7d24d70a4add54c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sat, 17 May 2025 18:21:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/apps/mysql/app.go | 2 +- pkg/systemctl/service.go | 71 ++++---- web/src/api/apps/docker/index.ts | 2 +- web/src/api/apps/memcached/index.ts | 4 +- web/src/api/apps/phpmyadmin/index.ts | 4 +- web/src/components/common/ServiceStatus.vue | 181 ++++++++++++++++++++ web/src/views/apps/codeserver/IndexView.vue | 93 +--------- web/src/views/apps/docker/IndexView.vue | 92 +--------- web/src/views/apps/fail2ban/IndexView.vue | 101 +---------- web/src/views/apps/frp/IndexView.vue | 134 +-------------- web/src/views/apps/gitea/IndexView.vue | 91 +--------- web/src/views/apps/memcached/IndexView.vue | 98 +---------- web/src/views/apps/minio/IndexView.vue | 91 +--------- web/src/views/apps/mysql/IndexView.vue | 104 +---------- web/src/views/apps/nginx/IndexView.vue | 103 +---------- web/src/views/apps/php/PhpView.vue | 103 +---------- web/src/views/apps/phpmyadmin/IndexView.vue | 4 +- web/src/views/apps/podman/IndexView.vue | 95 +--------- web/src/views/apps/postgresql/IndexView.vue | 104 +---------- web/src/views/apps/pureftpd/IndexView.vue | 91 +--------- web/src/views/apps/redis/IndexView.vue | 94 +--------- web/src/views/apps/rsync/IndexView.vue | 89 +--------- web/src/views/apps/supervisor/IndexView.vue | 97 +---------- 23 files changed, 296 insertions(+), 1552 deletions(-) create mode 100644 web/src/components/common/ServiceStatus.vue diff --git a/internal/apps/mysql/app.go b/internal/apps/mysql/app.go index 43a76877..b45ba79f 100644 --- a/internal/apps/mysql/app.go +++ b/internal/apps/mysql/app.go @@ -158,7 +158,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { // ClearErrorLog 清空错误日志 func (s *App) ClearErrorLog(w http.ResponseWriter, r *http.Request) { - if err := systemctl.LogsClear("mysqld"); err != nil { + if err := systemctl.LogClear("mysqld"); err != nil { service.Error(w, http.StatusInternalServerError, "%v", err) return } diff --git a/pkg/systemctl/service.go b/pkg/systemctl/service.go index 19992258..08e1b2b7 100644 --- a/pkg/systemctl/service.go +++ b/pkg/systemctl/service.go @@ -1,8 +1,6 @@ package systemctl import ( - "errors" - "fmt" "time" "github.com/tnb-labs/panel/pkg/shell" @@ -10,84 +8,79 @@ import ( // Status 获取服务状态 func Status(name string) (bool, error) { - output, err := shell.Execf("systemctl status %s | grep Active | grep -v grep | awk '{print $2}'", name) - return output == "active", err + output, _ := shell.Execf("systemctl is-active '%s'", name) // 不判断错误,因为 is-active 在服务未启用时会返回 3 + return output == "active", nil } // IsEnabled 服务是否启用 func IsEnabled(name string) (bool, error) { - out, err := shell.Execf("systemctl is-enabled '%s'", name) - if err != nil { - return false, fmt.Errorf("failed to check service status: %w", err) - } - - switch out { - case "enabled": - return true, nil - case "disabled": - return false, nil - case "masked": - return false, errors.New("service is masked") - case "static": - return false, errors.New("service is statically enabled") - case "indirect": - return false, errors.New("service is indirectly enabled") - default: - return false, errors.New("unknown service status") - } + out, _ := shell.Execf("systemctl is-enabled '%s'", name) // 不判断错误,因为 is-enabled 在服务禁用时会返回 1 + return out == "enabled" || out == "static" || out == "indirect", nil } // Start 启动服务 func Start(name string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl start %s", name) + _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl start '%s'", name) return err } // Stop 停止服务 func Stop(name string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl stop %s", name) + _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl stop '%s'", name) return err } // Restart 重启服务 func Restart(name string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl restart %s", name) + _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl restart '%s'", name) return err } // Reload 重载服务 func Reload(name string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl reload %s", name) + _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl reload '%s'", name) return err } // Enable 启用服务 func Enable(name string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl enable %s", name) + _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl enable '%s'", name) return err } // Disable 禁用服务 func Disable(name string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl disable %s", name) + _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl disable '%s'", name) return err } -// Logs 获取服务日志 -func Logs(name string) (string, error) { - return shell.ExecfWithTimeout(2*time.Minute, "journalctl -u %s", name) +// Mask 屏蔽服务 +func Mask(name string) error { + _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl mask '%s'", name) + return err } -// LogsTail 获取服务日志 -func LogsTail(name string, lines int) (string, error) { - return shell.ExecfWithTimeout(2*time.Minute, "journalctl -u %s --lines %d", name, lines) +// Unmask 解除屏蔽服务 +func Unmask(name string) error { + _, err := shell.ExecfWithTimeout(2*time.Minute, "systemctl unmask '%s'", name) + return err } -// LogsClear 清空服务日志 -func LogsClear(name string) error { - if _, err := shell.Execf("journalctl --rotate -u %s", name); err != nil { +// Log 获取服务日志 +func Log(name string) (string, error) { + return shell.ExecfWithTimeout(2*time.Minute, "journalctl -u '%s'", name) +} + +// LogTail 获取服务日志 +func LogTail(name string, lines int) (string, error) { + return shell.ExecfWithTimeout(2*time.Minute, "journalctl -u '%s' --lines '%d'", name, lines) +} + +// LogClear 清空服务日志 +func LogClear(name string) error { + if _, err := shell.Execf("journalctl --rotate -u '%s'", name); err != nil { return err } - _, err := shell.Execf("journalctl --vacuum-time=1s -u %s", name) + _, err := shell.Execf("journalctl --vacuum-time=1s -u '%s'", name) return err } diff --git a/web/src/api/apps/docker/index.ts b/web/src/api/apps/docker/index.ts index 7ecb5788..75a6e4f6 100644 --- a/web/src/api/apps/docker/index.ts +++ b/web/src/api/apps/docker/index.ts @@ -1,6 +1,6 @@ import { http } from '@/utils' export default { - getConfig: (): any => http.Get('/apps/docker/config'), + config: (): any => http.Get('/apps/docker/config'), updateConfig: (config: string): any => http.Post('/apps/docker/config', { config }) } diff --git a/web/src/api/apps/memcached/index.ts b/web/src/api/apps/memcached/index.ts index 02a9125f..ed5d20ee 100644 --- a/web/src/api/apps/memcached/index.ts +++ b/web/src/api/apps/memcached/index.ts @@ -1,7 +1,7 @@ import { http } from '@/utils' export default { - getLoad: (): any => http.Get('/apps/memcached/load'), - getConfig: (): any => http.Get('/apps/memcached/config'), + load: (): any => http.Get('/apps/memcached/load'), + config: (): any => http.Get('/apps/memcached/config'), updateConfig: (config: string): any => http.Post('/apps/memcached/config', { config }) } diff --git a/web/src/api/apps/phpmyadmin/index.ts b/web/src/api/apps/phpmyadmin/index.ts index 1199911a..0bc17d76 100644 --- a/web/src/api/apps/phpmyadmin/index.ts +++ b/web/src/api/apps/phpmyadmin/index.ts @@ -6,7 +6,7 @@ export default { // 设置端口 port: (port: number): any => http.Post('/apps/phpmyadmin/port', { port }), // 获取配置 - getConfig: (): any => http.Get('/apps/phpmyadmin/config'), + config: (): any => http.Get('/apps/phpmyadmin/config'), // 保存配置 - saveConfig: (config: string): any => http.Post('/apps/phpmyadmin/config', { config }) + updateConfig: (config: string): any => http.Post('/apps/phpmyadmin/config', { config }) } diff --git a/web/src/components/common/ServiceStatus.vue b/web/src/components/common/ServiceStatus.vue new file mode 100644 index 00000000..ee6ec44a --- /dev/null +++ b/web/src/components/common/ServiceStatus.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/web/src/views/apps/codeserver/IndexView.vue b/web/src/views/apps/codeserver/IndexView.vue index 4dd6903f..11862441 100644 --- a/web/src/views/apps/codeserver/IndexView.vue +++ b/web/src/views/apps/codeserver/IndexView.vue @@ -4,74 +4,26 @@ defineOptions({ }) import Editor from '@guolao/vue-monaco-editor' -import { NButton, NPopconfirm } from 'naive-ui' +import { NButton } from 'naive-ui' import { useGettext } from 'vue3-gettext' import codeserver from '@/api/apps/codeserver' -import systemctl from '@/api/panel/systemctl' +import ServiceStatus from '@/components/common/ServiceStatus.vue' const { $gettext } = useGettext() const currentTab = ref('status') -const status = ref(false) -const isEnabled = ref(false) -const config = ref('') -const statusStr = computed(() => { - return status.value ? $gettext('Running') : $gettext('Stopped') +const { data: config } = useRequest(codeserver.config, { + initialData: { + config: '' + } }) -const getStatus = async () => { - status.value = await systemctl.status('code-server') -} - -const getIsEnabled = async () => { - isEnabled.value = await systemctl.isEnabled('code-server') -} - -const getConfig = async () => { - config.value = await codeserver.config() -} - const handleSaveConfig = () => { useRequest(codeserver.saveConfig(config.value)).onSuccess(() => { window.$message.success($gettext('Saved successfully')) }) } - -const handleStart = async () => { - await systemctl.start('code-server') - window.$message.success($gettext('Started successfully')) - await getStatus() -} - -const handleStop = async () => { - await systemctl.stop('code-server') - window.$message.success($gettext('Stopped successfully')) - await getStatus() -} - -const handleRestart = async () => { - await systemctl.restart('code-server') - window.$message.success($gettext('Restarted successfully')) - await getStatus() -} - -const handleIsEnabled = async () => { - if (isEnabled.value) { - await systemctl.enable('code-server') - window.$message.success($gettext('Autostart enabled successfully')) - } else { - await systemctl.disable('code-server') - window.$message.success($gettext('Autostart disabled successfully')) - } - await getIsEnabled() -} - -onMounted(() => { - getStatus() - getIsEnabled() - getConfig() -}) - - - - - {{ statusStr }} - - - - - {{ $gettext('Start') }} - - - - {{ $gettext('Are you sure you want to stop Code Server?') }} - - - - {{ $gettext('Restart') }} - - - - + diff --git a/web/src/views/apps/docker/IndexView.vue b/web/src/views/apps/docker/IndexView.vue index 123d55ee..c895c18c 100644 --- a/web/src/views/apps/docker/IndexView.vue +++ b/web/src/views/apps/docker/IndexView.vue @@ -4,77 +4,26 @@ defineOptions({ }) import Editor from '@guolao/vue-monaco-editor' -import { NButton, NPopconfirm } from 'naive-ui' +import { NButton } from 'naive-ui' import { useGettext } from 'vue3-gettext' import docker from '@/api/apps/docker' -import systemctl from '@/api/panel/systemctl' +import ServiceStatus from '@/components/common/ServiceStatus.vue' const { $gettext } = useGettext() const currentTab = ref('status') -const status = ref(false) -const isEnabled = ref(false) -const { data: config } = useRequest(docker.getConfig, { +const { data: config } = useRequest(docker.config, { initialData: { config: '' } }) -const statusStr = computed(() => { - return status.value ? $gettext('Running') : $gettext('Stopped') -}) - -const getStatus = async () => { - status.value = await systemctl.status('docker') -} - -const getIsEnabled = async () => { - isEnabled.value = await systemctl.isEnabled('docker') -} - const handleSaveConfig = () => { useRequest(docker.updateConfig(config.value)).onSuccess(() => { window.$message.success($gettext('Saved successfully')) }) } - -const handleStart = () => { - useRequest(systemctl.start('docker')).onSuccess(() => { - window.$message.success($gettext('Started successfully')) - getStatus() - }) -} - -const handleStop = () => { - useRequest(systemctl.stop('docker')).onSuccess(() => { - window.$message.success($gettext('Stopped successfully')) - getStatus() - }) -} - -const handleRestart = () => { - useRequest(systemctl.restart('docker')).onSuccess(() => { - window.$message.success($gettext('Restarted successfully')) - getStatus() - }) -} - -const handleIsEnabled = async () => { - if (isEnabled.value) { - await systemctl.enable('docker') - window.$message.success($gettext('Autostart enabled successfully')) - } else { - await systemctl.disable('docker') - window.$message.success($gettext('Autostart disabled successfully')) - } - await getIsEnabled() -} - -onMounted(() => { - getStatus() - getIsEnabled() -}) - - - - - - {{ statusStr }} - - - - - {{ $gettext('Start') }} - - - - {{ $gettext('Are you sure you want to stop Docker?') }} - - - - {{ $gettext('Restart') }} - - - - - + diff --git a/web/src/views/apps/fail2ban/IndexView.vue b/web/src/views/apps/fail2ban/IndexView.vue index c361cac8..d1fd19c4 100644 --- a/web/src/views/apps/fail2ban/IndexView.vue +++ b/web/src/views/apps/fail2ban/IndexView.vue @@ -8,14 +8,12 @@ import { useGettext } from 'vue3-gettext' import fail2ban from '@/api/apps/fail2ban' import app from '@/api/panel/app' -import systemctl from '@/api/panel/systemctl' import website from '@/api/panel/website' +import ServiceStatus from '@/components/common/ServiceStatus.vue' import { renderIcon } from '@/utils' const { $gettext } = useGettext() const currentTab = ref('status') -const status = ref(false) -const isEnabled = ref(false) const white = ref('') const addJailModal = ref(false) @@ -35,13 +33,6 @@ const jailCurrentlyBan = ref(0) const jailTotalBan = ref(0) const jailBanedList = ref([]) -const statusType = computed(() => { - return status.value ? 'success' : 'error' -}) -const statusStr = computed(() => { - return status.value ? $gettext('Running') : $gettext('Stopped') -}) - const jailsColumns: any = [ { title: $gettext('Name'), @@ -195,49 +186,6 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati } ) -const getStatus = async () => { - status.value = await systemctl.status('fail2ban') -} - -const getIsEnabled = async () => { - isEnabled.value = await systemctl.isEnabled('fail2ban') -} - -const handleStart = async () => { - await systemctl.start('fail2ban') - window.$message.success($gettext('Started successfully')) - await getStatus() -} - -const handleIsEnabled = async () => { - if (isEnabled.value) { - await systemctl.enable('fail2ban') - window.$message.success($gettext('Autostart enabled successfully')) - } else { - await systemctl.disable('fail2ban') - window.$message.success($gettext('Autostart disabled successfully')) - } - await getIsEnabled() -} - -const handleStop = async () => { - await systemctl.stop('fail2ban') - window.$message.success($gettext('Stopped successfully')) - await getStatus() -} - -const handleRestart = async () => { - await systemctl.restart('fail2ban') - window.$message.success($gettext('Restarted successfully')) - await getStatus() -} - -const handleReload = async () => { - await systemctl.reload('fail2ban') - window.$message.success($gettext('Reloaded successfully')) - await getStatus() -} - const handleAddJail = () => { useRequest(fail2ban.add(addJailModel.value)).onSuccess(() => { refresh() @@ -269,8 +217,6 @@ const handleUnBan = (name: string, ip: string) => { onMounted(() => { refresh() - getStatus() - getIsEnabled() getWhiteList() useRequest(app.isInstalled('nginx')).onSuccess(({ data }) => { if (data.installed) { @@ -304,47 +250,8 @@ onMounted(() => { - - - - - - {{ statusStr }} - - - - - {{ $gettext('Start') }} - - - - {{ - $gettext( - 'Stopping Fail2ban will disable all rules. Are you sure you want to stop?' - ) - }} - - - - {{ $gettext('Restart') }} - - - - {{ $gettext('Reload') }} - - - - + + { :placeholder="$gettext('IP whitelist, separated by commas')" /> - + diff --git a/web/src/views/apps/frp/IndexView.vue b/web/src/views/apps/frp/IndexView.vue index 3cbc5ad3..88e18904 100644 --- a/web/src/views/apps/frp/IndexView.vue +++ b/web/src/views/apps/frp/IndexView.vue @@ -4,44 +4,19 @@ defineOptions({ }) import Editor from '@guolao/vue-monaco-editor' -import { NButton, NPopconfirm } from 'naive-ui' +import { NButton } from 'naive-ui' import { useGettext } from 'vue3-gettext' import frp from '@/api/apps/frp' -import systemctl from '@/api/panel/systemctl' +import ServiceStatus from '@/components/common/ServiceStatus.vue' const { $gettext } = useGettext() const currentTab = ref('frps') -const status = ref({ - frpc: false, - frps: false -}) -const isEnabled = ref({ - frpc: false, - frps: false -}) const config = ref({ frpc: '', frps: '' }) -const statusStr = computed(() => { - return { - frpc: status.value.frpc ? $gettext('Running') : $gettext('Stopped'), - frps: status.value.frps ? $gettext('Running') : $gettext('Stopped') - } -}) - -const getStatus = async () => { - status.value.frps = await systemctl.status('frps') - status.value.frpc = await systemctl.status('frpc') -} - -const getIsEnabled = async () => { - isEnabled.value.frps = await systemctl.isEnabled('frps') - isEnabled.value.frpc = await systemctl.isEnabled('frpc') -} - const getConfig = async () => { config.value.frps = await frp.config('frps') config.value.frpc = await frp.config('frpc') @@ -55,38 +30,7 @@ const handleSaveConfig = (service: string) => { ) } -const handleStart = async (name: string) => { - await systemctl.start(name) - window.$message.success($gettext('Started successfully')) - await getStatus() -} - -const handleStop = async (name: string) => { - await systemctl.stop(name) - window.$message.success($gettext('Stopped successfully')) - await getStatus() -} - -const handleRestart = async (name: string) => { - await systemctl.restart(name) - window.$message.success($gettext('Restarted successfully')) - await getStatus() -} - -const handleIsEnabled = async (name: string) => { - if (isEnabled.value[name as keyof typeof isEnabled.value]) { - await systemctl.enable(name) - window.$message.success($gettext('Autostart enabled successfully')) - } else { - await systemctl.disable(name) - window.$message.success($gettext('Autostart disabled successfully')) - } - await getIsEnabled() -} - onMounted(() => { - getStatus() - getIsEnabled() getConfig() }) @@ -95,39 +39,8 @@ onMounted(() => { - - - - - - {{ statusStr.frps }} - - - - - {{ $gettext('Start') }} - - - - {{ $gettext('Are you sure you want to stop Frps?') }} - - - - {{ $gettext('Restart') }} - - - - + + - - - - - - {{ statusStr }} - - - - - {{ $gettext('Start') }} - - - - {{ - $gettext( - 'Stopping Supervisor will cause all processes managed by Supervisor to be killed. Are you sure you want to stop?' - ) - }} - - - - {{ $gettext('Restart') }} - - - - - +