1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-14 01:34:47 +08:00

feat: 概览界面增加服务重启以及面板重启功能 (#2579)

Refs #328
This commit is contained in:
ssongliu 2023-10-17 17:54:30 +08:00 committed by GitHub
parent e9eea9c795
commit 3ff09c8b7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 180 additions and 2 deletions

View File

@ -56,6 +56,28 @@ func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error netOption in path")) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error netOption in path"))
return return
} }
data := dashboardService.LoadCurrentInfo(ioOption, netOption) data := dashboardService.LoadCurrentInfo(ioOption, netOption)
helper.SuccessWithData(c, data) helper.SuccessWithData(c, data)
} }
// @Tags Dashboard
// @Summary System restart
// @Description 重启服务器/面板
// @Accept json
// @Param operation path string true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /dashboard/system/restart/:operation [post]
func (b *BaseApi) SystemRestart(c *gin.Context) {
operation, ok := c.Params.Get("operation")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error operation in path"))
return
}
if err := dashboardService.Restart(operation); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -2,10 +2,12 @@ package service
import ( import (
"encoding/json" "encoding/json"
"fmt"
"strings" "strings"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk" "github.com/shirou/gopsutil/v3/disk"
@ -20,11 +22,31 @@ type DashboardService struct{}
type IDashboardService interface { type IDashboardService interface {
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
Restart(operation string) error
} }
func NewIDashboardService() IDashboardService { func NewIDashboardService() IDashboardService {
return &DashboardService{} return &DashboardService{}
} }
func (u *DashboardService) Restart(operation string) error {
if operation != "1panel" && operation != "system" {
return fmt.Errorf("handle restart operation %s failed, err: nonsupport such operation", operation)
}
itemCmd := fmt.Sprintf("%s 1pctl restart", cmd.SudoHandleCmd())
if operation == "system" {
itemCmd = fmt.Sprintf("%s reboot", cmd.SudoHandleCmd())
}
go func() {
stdout, err := cmd.Exec(itemCmd)
if err != nil {
global.LOG.Errorf("handle %s failed, err: %v", itemCmd, stdout)
}
}()
return nil
}
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) { func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
var baseInfo dto.DashboardBase var baseInfo dto.DashboardBase
hostInfo, err := host.Info() hostInfo, err := host.Info()

View File

@ -18,5 +18,6 @@ func (s *CronjobRouter) InitDashboardRouter(Router *gin.RouterGroup) {
{ {
cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo) cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo)
cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo) cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo)
cmdRouter.POST("/system/restart/:operation", baseApi.SystemRestart)
} }
} }

View File

@ -3718,6 +3718,37 @@ const docTemplate = `{
} }
} }
}, },
"/dashboard/system/restart/:operation": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "重启服务器/面板",
"consumes": [
"application/json"
],
"tags": [
"Dashboard"
],
"summary": "System restart",
"parameters": [
{
"type": "string",
"description": "request",
"name": "operation",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/databases": { "/databases": {
"post": { "post": {
"security": [ "security": [

View File

@ -3711,6 +3711,37 @@
} }
} }
}, },
"/dashboard/system/restart/:operation": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "重启服务器/面板",
"consumes": [
"application/json"
],
"tags": [
"Dashboard"
],
"summary": "System restart",
"parameters": [
{
"type": "string",
"description": "request",
"name": "operation",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/databases": { "/databases": {
"post": { "post": {
"security": [ "security": [

View File

@ -6536,6 +6536,25 @@ paths:
summary: Load dashboard current info summary: Load dashboard current info
tags: tags:
- Dashboard - Dashboard
/dashboard/system/restart/:operation:
post:
consumes:
- application/json
description: 重启服务器/面板
parameters:
- description: request
in: path
name: operation
required: true
type: string
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: System restart
tags:
- Dashboard
/databases: /databases:
post: post:
consumes: consumes:

View File

@ -8,3 +8,7 @@ export const loadBaseInfo = (ioOption: string, netOption: string) => {
export const loadCurrentInfo = (ioOption: string, netOption: string) => { export const loadCurrentInfo = (ioOption: string, netOption: string) => {
return http.get<Dashboard.CurrentInfo>(`/dashboard/current/${ioOption}/${netOption}`); return http.get<Dashboard.CurrentInfo>(`/dashboard/current/${ioOption}/${netOption}`);
}; };
export const systemRestart = (operation: string) => {
return http.post(`/dashboard/system/restart/${operation}`);
};

View File

@ -13,6 +13,7 @@
</el-badge> </el-badge>
</el-radio-button> </el-radio-button>
</el-radio-group> </el-radio-group>
<slot name="route-button"></slot>
</el-card> </el-card>
</template> </template>

View File

@ -277,6 +277,11 @@ const message = {
supervisor: 'Supervisor', supervisor: 'Supervisor',
}, },
home: { home: {
restart_1panel: 'Restart Panel',
restart_system: 'Restart Server',
restartHelper:
'Do you want to perform the operation [{0}] on this machine? This action will temporarily disable the service. Do you want to continue?',
operationSuccess: 'Operation successful! Restarting, please wait...',
overview: 'Overview', overview: 'Overview',
entranceHelper: entranceHelper:
'Enabling a secure entrance can help improve system security. If necessary, go to the Control Panel settings, select Security, and enable the secure entrance.', 'Enabling a secure entrance can help improve system security. If necessary, go to the Control Panel settings, select Security, and enable the secure entrance.',

View File

@ -275,6 +275,10 @@ const message = {
supervisor: '進程守護', supervisor: '進程守護',
}, },
home: { home: {
restart_1panel: '重啟面板',
restart_system: '重啟伺服器',
restartHelper: '您是否要對這台機器執行 [{0}] 操作此操作將導致服務暫時無法使用您要繼續嗎',
operationSuccess: '操作成功正在重啟請稍候...',
overview: '概覽', overview: '概覽',
entranceHelper: '設置安全入口有利於提高系統的安全性如有需要前往 面板設置-安全 啟用安全入口', entranceHelper: '設置安全入口有利於提高系統的安全性如有需要前往 面板設置-安全 啟用安全入口',
appInstalled: '已安裝應用', appInstalled: '已安裝應用',

View File

@ -275,6 +275,10 @@ const message = {
supervisor: '进程守护', supervisor: '进程守护',
}, },
home: { home: {
restart_1panel: '重启面板',
restart_system: '重启服务器',
restartHelper: '是否对该机器进行 [{0}] 操作该操作将导致服务暂时不可用是否继续',
operationSuccess: '操作成功正在重启请稍候...',
overview: '概览', overview: '概览',
entranceHelper: '设置安全入口有利于提高系统的安全性如有需要前往 面板设置-安全 启用安全入口', entranceHelper: '设置安全入口有利于提高系统的安全性如有需要前往 面板设置-安全 启用安全入口',
appInstalled: '已安装应用', appInstalled: '已安装应用',

View File

@ -376,4 +376,10 @@ html {
.limit-height-popover { .limit-height-popover {
max-height: 300px; max-height: 300px;
overflow: auto; overflow: auto;
}
.router-button {
margin-top: 12px;
float: right;
margin-right: 50px;
} }

View File

@ -7,7 +7,19 @@
path: '/', path: '/',
}, },
]" ]"
/> >
<template #route-button>
<div class="router-button">
<el-button link type="primary" @click="restart('1panel')">
{{ $t('home.restart_1panel') }}
</el-button>
<el-divider direction="vertical" />
<el-button link type="primary" @click="restart('system')">
{{ $t('home.restart_system') }}
</el-button>
</div>
</template>
</RouterButton>
<el-alert <el-alert
v-if="!isSafety && globalStore.showEntranceWarn" v-if="!isSafety && globalStore.showEntranceWarn"
style="margin-top: 20px" style="margin-top: 20px"
@ -231,10 +243,11 @@ import i18n from '@/lang';
import { Dashboard } from '@/api/interface/dashboard'; import { Dashboard } from '@/api/interface/dashboard';
import { dateFormatForSecond, computeSize } from '@/utils/util'; import { dateFormatForSecond, computeSize } from '@/utils/util';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { loadBaseInfo, loadCurrentInfo } from '@/api/modules/dashboard'; import { loadBaseInfo, loadCurrentInfo, systemRestart } from '@/api/modules/dashboard';
import { getIOOptions, getNetworkOptions } from '@/api/modules/monitor'; import { getIOOptions, getNetworkOptions } from '@/api/modules/monitor';
import { getSettingInfo, loadUpgradeInfo } from '@/api/modules/setting'; import { getSettingInfo, loadUpgradeInfo } from '@/api/modules/setting';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import { MsgSuccess } from '@/utils/message';
const router = useRouter(); const router = useRouter();
const globalStore = GlobalStore(); const globalStore = GlobalStore();
@ -527,6 +540,21 @@ const loadSafeStatus = async () => {
isSafety.value = res.data.securityEntrance; isSafety.value = res.data.securityEntrance;
}; };
const restart = async (type: string) => {
ElMessageBox.confirm(
i18n.global.t('home.restartHelper', [i18n.global.t('home.restart_' + type)]),
i18n.global.t('commons.msg.operate'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
).then(async () => {
MsgSuccess(i18n.global.t('home.operationSuccess'));
await systemRestart(type);
});
};
const onFocus = () => { const onFocus = () => {
isActive.value = true; isActive.value = true;
}; };