fix: Fix the problem of terminal saving failure (#8043)

This commit is contained in:
ssongliu 2025-03-03 10:18:55 +08:00 committed by GitHub
parent 5963e28b25
commit c3bd196a23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 205 additions and 134 deletions

View file

@ -137,24 +137,3 @@ func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
data := dashboardService.LoadCurrentInfo(ioOption, netOption)
helper.SuccessWithData(c, data)
}
// @Tags Dashboard
// @Summary System restart
// @Accept json
// @Param operation path string true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/system/restart/:operation [post]
func (b *BaseApi) SystemRestart(c *gin.Context) {
operation, ok := c.Params.Get("operation")
if !ok {
helper.BadRequest(c, errors.New("error operation in path"))
return
}
if err := dashboardService.Restart(operation); err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithOutData(c)
}

View file

@ -42,7 +42,6 @@ type IDashboardService interface {
LoadAppLauncher() ([]dto.AppLauncher, error)
ListLauncherOption(filter string) ([]dto.LauncherOption, error)
Restart(operation string) error
}
func NewIDashboardService() IDashboardService {
@ -78,23 +77,6 @@ func (u *DashboardService) Sync(req dto.SyncFromMaster) error {
}
}
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 systemctl restart 1panel-agent.service", 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) LoadOsInfo() (*dto.OsInfo, error) {
var baseInfo dto.OsInfo
hostInfo, err := host.Info()

View file

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

View file

@ -9,6 +9,7 @@ import (
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/copier"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
@ -159,7 +160,7 @@ func (b *BaseApi) DeleteHost(c *gin.Context) {
// @Summary Update host
// @Accept json
// @Param request body dto.HostOperate true "request"
// @Success 200
// @Success 200 {object} dto.HostInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/hosts/update [post]
@ -214,11 +215,12 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
upMap["pass_phrase"] = req.PassPhrase
}
upMap["description"] = req.Description
if err := hostService.Update(req.ID, upMap); err != nil {
hostItem, err := hostService.Update(req.ID, upMap)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithOutData(c)
helper.SuccessWithData(c, hostItem)
}
// @Tags Host
@ -238,13 +240,34 @@ func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
upMap := make(map[string]interface{})
upMap["group_id"] = req.GroupID
if err := hostService.Update(req.ID, upMap); err != nil {
if _, err := hostService.Update(req.ID, upMap); err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Host
// @Summary Get host info
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200 {object} dto.HostInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/hosts/info [post]
func (b *BaseApi) GetHostByID(c *gin.Context) {
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
info, err := hostService.GetHostByID(req.ID)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, info)
}
func (b *BaseApi) WsSsh(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
@ -265,7 +288,7 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
host, err := hostService.GetHostInfo(uint(id))
host, err := service.GetHostInfo(uint(id))
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
return
}

View file

@ -20,11 +20,11 @@ type HostService struct{}
type IHostService interface {
TestLocalConn(id uint) bool
TestByInfo(req dto.HostConnTest) bool
GetHostInfo(id uint) (*model.Host, error)
GetHostByID(id uint) (*dto.HostInfo, error)
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
SearchWithPage(search dto.SearchHostWithPage) (int64, interface{}, error)
Create(hostDto dto.HostOperate) (*dto.HostInfo, error)
Update(id uint, upMap map[string]interface{}) error
Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error)
Delete(id []uint) error
EncryptHost(itemVal string) (string, error)
@ -124,33 +124,6 @@ func (u *HostService) TestLocalConn(id uint) bool {
return true
}
func (u *HostService) GetHostInfo(id uint) (*model.Host, error) {
host, err := hostRepo.Get(repo.WithByID(id))
if err != nil {
return nil, buserr.New("ErrRecordNotFound")
}
if len(host.Password) != 0 {
host.Password, err = encrypt.StringDecrypt(host.Password)
if err != nil {
return nil, err
}
}
if len(host.PrivateKey) != 0 {
host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
if err != nil {
return nil, err
}
}
if len(host.PassPhrase) != 0 {
host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
if err != nil {
return nil, err
}
}
return &host, err
}
func (u *HostService) SearchWithPage(req dto.SearchHostWithPage) (int64, interface{}, error) {
var options []global.DBOption
if len(req.Info) != 0 {
@ -230,6 +203,48 @@ func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, e
return datas, err
}
func (u *HostService) GetHostByID(id uint) (*dto.HostInfo, error) {
var item dto.HostInfo
var host model.Host
if id == 0 {
host, _ = hostRepo.Get(repo.WithByName("local"))
} else {
host, _ = hostRepo.Get(repo.WithByID(id))
}
if host.ID == 0 {
return nil, buserr.New("ErrRecordNotFound")
}
if err := copier.Copy(&item, &host); err != nil {
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
if !item.RememberPassword {
item.Password = ""
item.PrivateKey = ""
item.PassPhrase = ""
return &item, nil
}
var err error
if len(host.Password) != 0 {
item.Password, err = encrypt.StringDecrypt(host.Password)
if err != nil {
return nil, err
}
}
if len(host.PrivateKey) != 0 {
item.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
if err != nil {
return nil, err
}
}
if len(host.PassPhrase) != 0 {
item.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
if err != nil {
return nil, err
}
}
return &item, err
}
func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
if req.Name == "local" {
return nil, buserr.New("ErrRecordExist")
@ -297,8 +312,16 @@ func (u *HostService) Delete(ids []uint) error {
return hostRepo.Delete(repo.WithByIDs(ids))
}
func (u *HostService) Update(id uint, upMap map[string]interface{}) error {
return hostRepo.Update(id, upMap)
func (u *HostService) Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error) {
if err := hostRepo.Update(id, upMap); err != nil {
return nil, err
}
hostItem, _ := hostRepo.Get(repo.WithByID(id))
var hostinfo dto.HostInfo
if err := copier.Copy(&hostinfo, &hostItem); err != nil {
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
return &hostinfo, nil
}
func (u *HostService) EncryptHost(itemVal string) (string, error) {
@ -309,3 +332,30 @@ func (u *HostService) EncryptHost(itemVal string) (string, error) {
keyItem, err := encrypt.StringEncrypt(string(privateKey))
return keyItem, err
}
func GetHostInfo(id uint) (*model.Host, error) {
host, err := hostRepo.Get(repo.WithByID(id))
if err != nil {
return nil, buserr.New("ErrRecordNotFound")
}
if len(host.Password) != 0 {
host.Password, err = encrypt.StringDecrypt(host.Password)
if err != nil {
return nil, err
}
}
if len(host.PrivateKey) != 0 {
host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey)
if err != nil {
return nil, err
}
}
if len(host.PassPhrase) != 0 {
host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
if err != nil {
return nil, err
}
}
return &host, err
}

View file

@ -51,17 +51,16 @@ var WebUrlMap = map[string]struct{}{
"/ai/model": {},
"/ai/gpu": {},
"/containers": {},
"/containers/container": {},
"containers/container/operate": {},
"/containers/image": {},
"/containers/network": {},
"/containers/volume": {},
"/containers/repo": {},
"/containers/compose": {},
"/containers/template": {},
"/containers/setting": {},
"/containers/dashboard": {},
"/containers": {},
"/containers/container": {},
"/containers/image": {},
"/containers/network": {},
"/containers/volume": {},
"/containers/repo": {},
"/containers/compose": {},
"/containers/template": {},
"/containers/setting": {},
"/containers/dashboard": {},
"/cronjobs": {},
@ -147,6 +146,7 @@ var WebUrlMap = map[string]struct{}{
}
var DynamicRoutes = []string{
`^/containers/container/operate/[^/]+$`,
`^/containers/composeDetail/[^/]+$`,
`^/databases/mysql/setting/[^/]+/[^/]+$`,
`^/databases/postgresql/setting/[^/]+/[^/]+$`,

View file

@ -16,6 +16,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v2.ApiGroupApp.BaseApi
{
hostRouter.POST("", baseApi.CreateHost)
hostRouter.POST("/info", baseApi.GetHostByID)
hostRouter.POST("/del", baseApi.DeleteHost)
hostRouter.POST("/update", baseApi.UpdateHost)
hostRouter.POST("/update/group", baseApi.UpdateHostGroup)

View file

@ -71,6 +71,13 @@ func (c *SSHClient) Run(shell string) (string, error) {
return string(buf), err
}
func (c *SSHClient) SudoHandleCmd() string {
if _, err := c.Run("sudo -n ls"); err == nil {
return "sudo "
}
return ""
}
func (c *SSHClient) Runf(shell string, args ...interface{}) (string, error) {
session, err := c.Client.NewSession()
if err != nil {

View file

@ -7,6 +7,9 @@ import { deepCopy } from '@/utils/util';
export const searchHosts = (params: Host.SearchWithPage) => {
return http.post<ResPage<Host.Host>>(`/core/hosts/search`, params);
};
export const getHostByID = (id: number) => {
return http.post<Host.Host>(`/core/hosts/info`, { id: id });
};
export const getHostTree = (params: Host.ReqSearch) => {
return http.post<Array<Host.HostTree>>(`/core/hosts/tree`, params);
};

View file

@ -52,11 +52,11 @@ const seriesStyle = [
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 94, 235, .5)',
color: 'rgba(0, 94, 235, .3)',
},
{
offset: 1,
color: 'rgba(0, 94, 235, 0)',
color: 'rgba(0, 94, 235, .4)',
},
]),
},
@ -64,11 +64,11 @@ const seriesStyle = [
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(27, 143, 60, .5)',
color: 'rgba(27, 143, 60, .3)',
},
{
offset: 1,
color: 'rgba(27, 143, 60, 0)',
color: 'rgba(27, 143, 60, .4)',
},
]),
},
@ -76,11 +76,11 @@ const seriesStyle = [
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(249, 199, 79, .5)',
color: 'rgba(249, 199, 79, .3)',
},
{
offset: 1,
color: 'rgba(249, 199, 79, 0)',
color: 'rgba(249, 199, 79, .4)',
},
]),
},
@ -88,11 +88,11 @@ const seriesStyle = [
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(255, 173, 177, 0.5)',
color: 'rgba(255, 173, 177, 0.3)',
},
{
offset: 1,
color: 'rgba(255, 173, 177, 0)',
color: 'rgba(255, 173, 177, .4)',
},
]),
},

View file

@ -8,11 +8,11 @@
</div>
<div v-if="createdBy === '1Panel'" style="margin-left: 50px">
<el-button link type="primary" @click="onComposeOperate('up')">
{{ $t('app.start') }}
{{ $t('commons.operate.start') }}
</el-button>
<el-divider direction="vertical" />
<el-button link type="primary" @click="onComposeOperate('stop')">
{{ $t('app.stop') }}
{{ $t('commons.operate.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button link type="primary" @click="onComposeOperate('down')">
@ -40,10 +40,10 @@
<template #main>
<el-button-group>
<el-button :disabled="checkStatus('start')" @click="onOperate('start')">
{{ $t('app.start') }}
{{ $t('commons.operate.start') }}
</el-button>
<el-button :disabled="checkStatus('stop')" @click="onOperate('stop')">
{{ $t('app.stop') }}
{{ $t('commons.operate.stop') }}
</el-button>
<el-button :disabled="checkStatus('restart')" @click="onOperate('restart')">
{{ $t('commons.button.restart') }}

View file

@ -76,7 +76,7 @@
import { ElForm } from 'element-plus';
import { Host } from '@/api/interface/host';
import { Rules } from '@/global/form-rules';
import { addHost, testByInfo } from '@/api/modules/terminal';
import { addHost, editHost, getHostByID, testByInfo } from '@/api/modules/terminal';
import i18n from '@/lang';
import { reactive, ref } from 'vue';
import { MsgError, MsgSuccess } from '@/utils/message';
@ -88,6 +88,7 @@ type FormInstance = InstanceType<typeof ElForm>;
const hostRef = ref<FormInstance>();
const groupList = ref();
const defaultGroup = ref();
let hostInfo = reactive<Host.HostOperate>({
id: 0,
@ -118,21 +119,7 @@ interface DialogProps {
isLocal: boolean;
}
const acceptParams = (props: DialogProps) => {
hostInfo.addr = '';
hostInfo.name = 'local';
hostInfo.groupID = 0;
hostInfo.addr = '';
hostInfo.port = 22;
hostInfo.user = '';
hostInfo.authMode = 'password';
hostInfo.password = '';
hostInfo.privateKey = '';
hostInfo.description = '';
isLocal.value = props.isLocal;
if (props.isLocal) {
hostInfo.addr = '127.0.0.1';
hostInfo.user = 'root';
}
loadGroups();
dialogVisible.value = true;
};
@ -143,11 +130,57 @@ const handleClose = () => {
const emit = defineEmits(['on-conn-terminal', 'load-host-tree']);
const loadGroups = async () => {
const res = await getGroupList('host');
groupList.value = res.data;
for (const item of groupList.value) {
if (item.isDefault) {
defaultGroup.value = item.id;
break;
}
}
if (isLocal.value) {
loadLocal();
} else {
setDefault();
}
};
const loadLocal = async () => {
await getHostByID(0)
.then((res) => {
hostInfo.id = res.data.id || 0;
hostInfo.addr = res.data.addr || '';
hostInfo.name = 'local';
hostInfo.groupID = res.data.groupID || defaultGroup.value;
hostInfo.port = res.data.port || 22;
hostInfo.user = res.data.user || 'root';
hostInfo.authMode = res.data.authMode || 'password';
hostInfo.password = res.data.password || '';
hostInfo.privateKey = res.data.privateKey || '';
hostInfo.description = res.data.description || '';
})
.catch(() => {
setDefault();
});
};
const setDefault = () => {
console.log('123');
hostInfo.addr = '';
hostInfo.name = 'local';
hostInfo.groupID = defaultGroup.value;
hostInfo.port = 22;
hostInfo.user = '';
hostInfo.authMode = 'password';
hostInfo.password = '';
hostInfo.privateKey = '';
hostInfo.description = '';
};
const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
hostInfo.groupID = 0;
switch (ops) {
case 'testConn':
await testByInfo(hostInfo).then((res) => {
@ -161,7 +194,12 @@ const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
});
break;
case 'saveAndConn':
const res = await addHost(hostInfo);
let res;
if (hostInfo.id == 0) {
res = await addHost(hostInfo);
} else {
res = await editHost(hostInfo);
}
dialogVisible.value = false;
let title = res.data.user + '@' + res.data.addr + ':' + res.data.port;
if (res.data.name.length !== 0) {
@ -175,17 +213,6 @@ const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
});
};
const loadGroups = async () => {
const res = await getGroupList('host');
groupList.value = res.data;
for (const item of groupList.value) {
if (item.isDefault) {
hostInfo.groupID = item.id;
break;
}
}
};
defineExpose({
acceptParams,
});

View file

@ -364,7 +364,7 @@ const onConnTerminal = async (title: string, wsID: number, isLocal?: boolean) =>
const res = await testByID(wsID);
if (isLocal) {
for (const tab of terminalTabs.value) {
if (tab.title.indexOf('@127.0.0.1:') !== -1 || tab.title === i18n.global.t('terminal.localhost')) {
if (tab.title === i18n.global.t('terminal.localhost')) {
onReconnect(tab);
}
}

View file

@ -10,10 +10,10 @@
</div>
<div class="mt-0.5">
<el-button type="primary" v-if="!data.isActive" link @click="onOperate('ClamAV', 'start')">
{{ $t('app.start') }}
{{ $t('commons.operate.start') }}
</el-button>
<el-button type="primary" v-if="data.isActive" link @click="onOperate('ClamAV', 'stop')">
{{ $t('app.stop') }}
{{ $t('commons.operate.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link @click="onOperate('ClamAV', 'restart')">
@ -45,7 +45,7 @@
link
@click="onOperate('FreshClam', 'start')"
>
{{ $t('app.start') }}
{{ $t('commons.operate.start') }}
</el-button>
<el-button
type="primary"
@ -53,7 +53,7 @@
link
@click="onOperate('FreshClam', 'stop')"
>
{{ $t('app.stop') }}
{{ $t('commons.operate.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link @click="onOperate('FreshClam', 'restart')">

View file

@ -10,10 +10,10 @@
</div>
<div class="mt-0.5" v-if="!data.init">
<el-button type="primary" v-if="data.status != 'running'" link @click="onOperate('start')">
{{ $t('app.start') }}
{{ $t('commons.operate.start') }}
</el-button>
<el-button type="primary" v-if="data.status == 'running'" link @click="onOperate('stop')">
{{ $t('app.stop') }}
{{ $t('commons.operate.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link @click="onOperate('restart')">