mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-09-11 00:54:55 +08:00
feat: Change the local connection mode of the terminal (#8409)
This commit is contained in:
parent
0ee7e1d675
commit
9ffc601cf0
16 changed files with 138 additions and 171 deletions
|
@ -18,6 +18,55 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (b *BaseApi) WsSSH(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if global.CONF.Base.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
name, err := loadExecutor()
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
slave, err := terminal.NewCommand(name)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
|
@ -42,7 +91,7 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
|||
}
|
||||
source := c.Query("source")
|
||||
var containerID string
|
||||
var initCmd string
|
||||
var initCmd []string
|
||||
switch source {
|
||||
case "redis":
|
||||
containerID, initCmd, err = loadRedisInitCmd(c)
|
||||
|
@ -59,11 +108,11 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
pidMap := loadMapFromDockerTop(containerID)
|
||||
slave, err := terminal.NewCommand("clear && " + initCmd)
|
||||
slave, err := terminal.NewCommand("docker", initCmd...)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(containerID, strings.ReplaceAll(initCmd, fmt.Sprintf("docker exec -it %s ", containerID), ""), pidMap)
|
||||
defer killBash(containerID, strings.ReplaceAll(strings.Join(initCmd, " "), fmt.Sprintf("exec -it %s ", containerID), ""), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
|
@ -83,62 +132,63 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func loadRedisInitCmd(c *gin.Context) (string, string, error) {
|
||||
func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
|
||||
name := c.Query("name")
|
||||
from := c.Query("from")
|
||||
commands := "redis-cli"
|
||||
commands := []string{"exec", "-it"}
|
||||
database, err := databaseService.Get(name)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("no such database in db, err: %v", err)
|
||||
return "", nil, fmt.Errorf("no such database in db, err: %v", err)
|
||||
}
|
||||
if from == "local" {
|
||||
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("no such app in db, err: %v", err)
|
||||
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
name = redisInfo.ContainerName
|
||||
commands = append(commands, []string{name, "redis-cli"}...)
|
||||
if len(database.Password) != 0 {
|
||||
commands = "redis-cli -a " + database.Password + " --no-auth-warning"
|
||||
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
|
||||
}
|
||||
} else {
|
||||
commands = fmt.Sprintf("redis-cli -h %s -p %v", database.Address, database.Port)
|
||||
if len(database.Password) != 0 {
|
||||
commands = fmt.Sprintf("redis-cli -h %s -p %v -a %s --no-auth-warning", database.Address, database.Port, database.Password)
|
||||
}
|
||||
name = "1Panel-redis-cli-tools"
|
||||
commands = append(commands, []string{name, "redis-cli", "-h", database.Address, "-p", fmt.Sprintf("%v", database.Port)}...)
|
||||
if len(database.Password) != 0 {
|
||||
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
|
||||
}
|
||||
}
|
||||
return name, fmt.Sprintf("docker exec -it %s %s", name, commands), nil
|
||||
return name, commands, nil
|
||||
}
|
||||
|
||||
func loadOllamaInitCmd(c *gin.Context) (string, string, error) {
|
||||
func loadOllamaInitCmd(c *gin.Context) (string, []string, error) {
|
||||
name := c.Query("name")
|
||||
if cmd.CheckIllegal(name) {
|
||||
return "", "", fmt.Errorf("ollama model %s contains illegal characters", name)
|
||||
return "", nil, fmt.Errorf("ollama model %s contains illegal characters", name)
|
||||
}
|
||||
ollamaInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: "", Type: "ollama"})
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("no such app in db, err: %v", err)
|
||||
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
containerName := ollamaInfo.ContainerName
|
||||
return containerName, fmt.Sprintf("docker exec -it %s ollama run %s", containerName, name), nil
|
||||
return containerName, []string{"exec", "-it", containerName, "ollama", "run", name}, nil
|
||||
}
|
||||
|
||||
func loadContainerInitCmd(c *gin.Context) (string, string, error) {
|
||||
func loadContainerInitCmd(c *gin.Context) (string, []string, error) {
|
||||
containerID := c.Query("containerid")
|
||||
command := c.Query("command")
|
||||
user := c.Query("user")
|
||||
if cmd.CheckIllegal(user, containerID, command) {
|
||||
return "", "", fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID)
|
||||
return "", nil, fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID)
|
||||
}
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
return "", "", fmt.Errorf("error param of command: %s or containerID: %s", command, containerID)
|
||||
return "", nil, fmt.Errorf("error param of command: %s or containerID: %s", command, containerID)
|
||||
}
|
||||
command = fmt.Sprintf("docker exec -it %s %s", containerID, command)
|
||||
commands := []string{"exec", "-it", containerID, command}
|
||||
if len(user) != 0 {
|
||||
command = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command)
|
||||
commands = []string{"exec", "-it", "-u", user, containerID, command}
|
||||
}
|
||||
|
||||
return containerID, command, nil
|
||||
return containerID, commands, nil
|
||||
}
|
||||
|
||||
func wshandleError(ws *websocket.Conn, err error) bool {
|
||||
|
@ -204,3 +254,12 @@ var upGrader = websocket.Upgrader{
|
|||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func loadExecutor() (string, error) {
|
||||
std, err := cmd.RunDefaultWithStdoutBashC("echo $SHELL")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("load default executor failed, err: %s", std)
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(std, "\n", ""), nil
|
||||
}
|
||||
|
|
|
@ -46,5 +46,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
|||
hostRouter.POST("/tool/supervisor/process", baseApi.OperateProcess)
|
||||
hostRouter.GET("/tool/supervisor/process", baseApi.GetProcess)
|
||||
hostRouter.POST("/tool/supervisor/process/file", baseApi.GetProcessFile)
|
||||
|
||||
hostRouter.GET("/exec", baseApi.WsSSH)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,22 +25,20 @@ type LocalCommand struct {
|
|||
pty *os.File
|
||||
}
|
||||
|
||||
func NewCommand(initCmd string) (*LocalCommand, error) {
|
||||
cmd := exec.Command("bash")
|
||||
func NewCommand(name string, arg ...string) (*LocalCommand, error) {
|
||||
cmd := exec.Command(name, arg...)
|
||||
if term := os.Getenv("TERM"); term != "" {
|
||||
cmd.Env = append(os.Environ(), "TERM="+term)
|
||||
} else {
|
||||
cmd.Env = append(os.Environ(), "TERM=xterm")
|
||||
}
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
cmd.Dir = homeDir
|
||||
|
||||
pty, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to start command")
|
||||
}
|
||||
if len(initCmd) != 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, _ = pty.Write([]byte(initCmd + "\n"))
|
||||
}
|
||||
|
||||
lcmd := &LocalCommand{
|
||||
closeSignal: DefaultCloseSignal,
|
||||
|
@ -99,4 +97,5 @@ func (lcmd *LocalCommand) Wait(quitChan chan bool) {
|
|||
global.LOG.Errorf("ssh session wait failed, err: %v", err)
|
||||
setQuit(quitChan)
|
||||
}
|
||||
setQuit(quitChan)
|
||||
}
|
||||
|
|
|
@ -79,16 +79,9 @@ func (u *HostService) TestLocalConn(id uint) bool {
|
|||
host model.Host
|
||||
err error
|
||||
)
|
||||
if id == 0 {
|
||||
host, err = hostRepo.Get(hostRepo.WithByAddr("127.0.0.1"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
host, err = hostRepo.Get(repo.WithByID(id))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
host, err = hostRepo.Get(repo.WithByID(id))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var connInfo ssh.ConnInfo
|
||||
if err := copier.Copy(&connInfo, &host); err != nil {
|
||||
|
@ -206,11 +199,7 @@ func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, e
|
|||
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))
|
||||
}
|
||||
host, _ = hostRepo.Get(repo.WithByID(id))
|
||||
if host.ID == 0 {
|
||||
return nil, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
|
@ -246,9 +235,6 @@ func (u *HostService) GetHostByID(id uint) (*dto.HostInfo, error) {
|
|||
}
|
||||
|
||||
func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
|
||||
if req.Name == "local" {
|
||||
return nil, buserr.New("ErrRecordExist")
|
||||
}
|
||||
hostItem, _ := hostRepo.Get(hostRepo.WithByAddr(req.Addr), hostRepo.WithByUser(req.User), hostRepo.WithByPort(req.Port))
|
||||
if hostItem.ID != 0 {
|
||||
return nil, buserr.New("ErrRecordExist")
|
||||
|
@ -305,9 +291,6 @@ func (u *HostService) Delete(ids []uint) error {
|
|||
if host.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if host.Name == "local" {
|
||||
return errors.New("the local connection information cannot be deleted!")
|
||||
}
|
||||
if err := hostRepo.Delete(repo.WithByID(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1092,7 +1092,6 @@ const message = {
|
|||
terminal: {
|
||||
local: 'Local',
|
||||
localHelper: 'The `local` name is used only for system local identification',
|
||||
connLocalErr: 'Unable to automatically authenticate, please fill in the local server login information!',
|
||||
testConn: 'Test connection',
|
||||
saveAndConn: 'Save and Connect',
|
||||
connTestOk: 'Connection information available',
|
||||
|
|
|
@ -1050,7 +1050,6 @@ const message = {
|
|||
terminal: {
|
||||
local: 'ローカル',
|
||||
localHelper: 'ローカル名はシステムのローカル識別にのみ使用されます。',
|
||||
connLocalErr: '自動的に認証できない場合は、ローカルサーバーのログイン情報を入力してください。',
|
||||
testConn: 'テスト接続',
|
||||
saveAndConn: '保存して接続します',
|
||||
connTestOk: '利用可能な接続情報',
|
||||
|
|
|
@ -1043,7 +1043,6 @@ const message = {
|
|||
terminal: {
|
||||
local: '로컬',
|
||||
localHelper: '로컬 이름은 시스템 로컬 식별에만 사용됩니다.',
|
||||
connLocalErr: '자동 인증에 실패했습니다. 로컬 서버 로그인 정보를 입력해주세요.',
|
||||
testConn: '연결 테스트',
|
||||
saveAndConn: '저장 후 연결',
|
||||
connTestOk: '연결 정보가 유효합니다.',
|
||||
|
|
|
@ -1079,7 +1079,6 @@ const message = {
|
|||
terminal: {
|
||||
local: 'Tempatan',
|
||||
localHelper: 'Nama tempatan hanya digunakan untuk pengenalan sistem tempatan.',
|
||||
connLocalErr: 'Tidak dapat mengesahkan secara automatik, sila isi maklumat log masuk pelayan tempatan.',
|
||||
testConn: 'Uji sambungan',
|
||||
saveAndConn: 'Simpan dan sambung',
|
||||
connTestOk: 'Maklumat sambungan tersedia',
|
||||
|
|
|
@ -1069,8 +1069,6 @@ const message = {
|
|||
terminal: {
|
||||
local: 'Local',
|
||||
localHelper: 'O nome local é usado apenas para identificação local do sistema.',
|
||||
connLocalErr:
|
||||
'Não foi possível autenticar automaticamente, por favor, preencha as informações de login do servidor local.',
|
||||
testConn: 'Testar conexão',
|
||||
saveAndConn: 'Salvar e conectar',
|
||||
connTestOk: 'Informações de conexão disponíveis',
|
||||
|
|
|
@ -1074,8 +1074,6 @@ const message = {
|
|||
terminal: {
|
||||
local: 'Локальный',
|
||||
localHelper: 'Локальное имя используется только для локальной идентификации системы.',
|
||||
connLocalErr:
|
||||
'Невозможно автоматически аутентифицироваться, пожалуйста, заполните информацию для входа на локальный сервер.',
|
||||
testConn: 'Проверить подключение',
|
||||
saveAndConn: 'Сохранить и подключиться',
|
||||
connTestOk: 'Информация о подключении доступна',
|
||||
|
|
|
@ -1040,7 +1040,6 @@ const message = {
|
|||
terminal: {
|
||||
local: '本機',
|
||||
localHelper: 'local 名稱僅用於系統本機標識',
|
||||
connLocalErr: '無法自動認證,請填寫本地服務器的登錄信息!',
|
||||
testConn: '連接測試',
|
||||
saveAndConn: '保存並連接',
|
||||
connTestOk: '連接信息可用',
|
||||
|
|
|
@ -1038,7 +1038,6 @@ const message = {
|
|||
terminal: {
|
||||
local: '本机',
|
||||
localHelper: 'local 名称仅用于系统本机标识',
|
||||
connLocalErr: '无法自动认证,请填写本地服务器的登录信息!',
|
||||
testConn: '连接测试',
|
||||
saveAndConn: '保存并连接',
|
||||
connTestOk: '连接信息可用',
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
:data="data"
|
||||
@search="search"
|
||||
>
|
||||
<el-table-column type="selection" :selectable="selectable" fix />
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column :label="$t('terminal.ip')" prop="addr" fix />
|
||||
<el-table-column :label="$t('commons.login.username')" show-overflow-tooltip prop="user" />
|
||||
<el-table-column :label="$t('commons.table.port')" prop="port" />
|
||||
|
@ -106,9 +106,6 @@ const acceptParams = () => {
|
|||
search();
|
||||
};
|
||||
|
||||
function selectable(row) {
|
||||
return row.name !== 'local';
|
||||
}
|
||||
const dialogRef = ref();
|
||||
const onOpenDialog = async (
|
||||
title: string,
|
||||
|
@ -174,9 +171,6 @@ const buttons = [
|
|||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
disabled: (row: any) => {
|
||||
return row.name === 'local';
|
||||
},
|
||||
click: (row: Host.Host) => {
|
||||
onBatchDelete(row);
|
||||
},
|
||||
|
|
|
@ -61,8 +61,7 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.title')" prop="name">
|
||||
<el-tag v-if="itemName === 'local'">local</el-tag>
|
||||
<el-input v-else clearable v-model="dialogData.rowData!.name" />
|
||||
<el-input clearable v-model="dialogData.rowData!.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.description')" prop="description">
|
||||
<el-input clearable type="textarea" v-model="dialogData.rowData!.description" />
|
||||
|
@ -103,12 +102,10 @@ const drawerVisible = ref(false);
|
|||
const dialogData = ref<DialogProps>({
|
||||
title: '',
|
||||
});
|
||||
const itemName = ref();
|
||||
|
||||
const groupList = ref();
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
itemName.value = params.rowData.name;
|
||||
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
||||
drawerVisible.value = true;
|
||||
loadGroups();
|
||||
|
@ -126,14 +123,7 @@ const rules = reactive({
|
|||
port: [Rules.requiredInput, Rules.port],
|
||||
user: [Rules.requiredInput],
|
||||
authMode: [Rules.requiredSelect],
|
||||
name: [{ validator: checkName, trigger: 'blur' }],
|
||||
});
|
||||
function checkName(rule: any, value: any, callback: any) {
|
||||
if (value === 'local' && dialogData.value.title !== 'edit') {
|
||||
return callback(new Error(i18n.global.t('terminal.localHelper')));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
const loadGroups = async () => {
|
||||
const res = await getGroupList('host');
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<template>
|
||||
<DrawerPro v-model="dialogVisible" :header="$t('terminal.addHost')" @close="handleClose" size="large">
|
||||
<el-form ref="hostRef" label-width="100px" label-position="top" :model="hostInfo" :rules="rules">
|
||||
<el-alert
|
||||
v-if="isLocal"
|
||||
class="common-prompt"
|
||||
center
|
||||
:title="$t('terminal.connLocalErr')"
|
||||
:closable="false"
|
||||
type="warning"
|
||||
/>
|
||||
<el-form-item :label="$t('terminal.ip')" prop="addr">
|
||||
<el-input @change="isOK = false" clearable v-model.trim="hostInfo.addr" />
|
||||
</el-form-item>
|
||||
|
@ -55,8 +47,7 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.title')" prop="name">
|
||||
<el-tag v-if="isLocal">local</el-tag>
|
||||
<el-input v-else clearable v-model="hostInfo.name" />
|
||||
<el-input clearable v-model="hostInfo.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.description')" prop="description">
|
||||
<el-input clearable v-model="hostInfo.description" />
|
||||
|
@ -80,7 +71,7 @@
|
|||
import { ElForm } from 'element-plus';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { addHost, editHost, getHostByID, testByInfo } from '@/api/modules/terminal';
|
||||
import { addHost, testByInfo } from '@/api/modules/terminal';
|
||||
import i18n from '@/lang';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
|
@ -116,21 +107,9 @@ const rules = reactive({
|
|||
authMode: [Rules.requiredSelect],
|
||||
password: [Rules.requiredInput],
|
||||
privateKey: [Rules.requiredInput],
|
||||
name: [{ validator: checkName, trigger: 'blur' }],
|
||||
});
|
||||
function checkName(rule: any, value: any, callback: any) {
|
||||
if (value === 'local' && !isLocal.value) {
|
||||
return callback(new Error(i18n.global.t('terminal.localHelper')));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
const isLocal = ref(false);
|
||||
interface DialogProps {
|
||||
isLocal: boolean;
|
||||
}
|
||||
const acceptParams = (props: DialogProps) => {
|
||||
isLocal.value = props.isLocal;
|
||||
const acceptParams = () => {
|
||||
loadGroups();
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
@ -150,29 +129,7 @@ const loadGroups = async () => {
|
|||
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();
|
||||
});
|
||||
setDefault();
|
||||
};
|
||||
|
||||
const setDefault = () => {
|
||||
|
@ -204,19 +161,13 @@ const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
|
|||
});
|
||||
break;
|
||||
case 'saveAndConn':
|
||||
let res;
|
||||
if (hostInfo.id == 0) {
|
||||
res = await addHost(hostInfo);
|
||||
} else {
|
||||
res = await editHost(hostInfo);
|
||||
}
|
||||
const res = await addHost(hostInfo);
|
||||
dialogVisible.value = false;
|
||||
let title = res.data.user + '@' + res.data.addr + ':' + res.data.port;
|
||||
if (res.data.name.length !== 0) {
|
||||
title = res.data.name + '-' + title;
|
||||
}
|
||||
let isLocal = hostInfo.name === 'local';
|
||||
emit('on-conn-terminal', title, res.data.id, isLocal);
|
||||
emit('on-conn-terminal', title, res.data.id);
|
||||
emit('load-host-tree');
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
>
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<span v-if="node.label === 'default'">{{ $t('commons.table.default') }}</span>
|
||||
<span v-if="node.label === 'Default'">{{ $t('commons.table.default') }}</span>
|
||||
<div v-else>
|
||||
<span v-if="node.label.length <= 25">
|
||||
<a @click="onClickConn(node, data)">{{ node.label }}</a>
|
||||
|
@ -173,8 +173,6 @@ const loadTooltip = () => {
|
|||
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
|
||||
};
|
||||
|
||||
const localHostID = ref();
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
const terminalValue = ref();
|
||||
const terminalTabs = ref([]) as any;
|
||||
|
@ -209,26 +207,7 @@ const acceptParams = async () => {
|
|||
timer = setInterval(() => {
|
||||
syncTerminal();
|
||||
}, 1000 * 5);
|
||||
for (let gIndex = 0; gIndex < hostTree.value.length; gIndex++) {
|
||||
if (!hostTree.value[gIndex].children) {
|
||||
continue;
|
||||
}
|
||||
for (let i = 0; i < hostTree.value[gIndex].children.length; i++) {
|
||||
if (hostTree.value[gIndex].children[i].label.startsWith('local - ')) {
|
||||
localHostID.value = hostTree.value[gIndex].children[i].id;
|
||||
hostTree.value[gIndex].children.splice(i, 1);
|
||||
if (hostTree.value[gIndex].children.length === 0) {
|
||||
hostTree.value.splice(gIndex, 1);
|
||||
}
|
||||
if (terminalTabs.value.length !== 0) {
|
||||
return;
|
||||
}
|
||||
onNewLocal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onNewLocal();
|
||||
if (!mobile.value) {
|
||||
screenfull.on('change', () => {
|
||||
globalStore.isFullScreen = screenfull.isFullscreen;
|
||||
|
@ -329,17 +308,34 @@ function beforeLeave(activeName: string) {
|
|||
}
|
||||
|
||||
const onNewSsh = () => {
|
||||
dialogRef.value!.acceptParams({ isLocal: false });
|
||||
dialogRef.value!.acceptParams();
|
||||
};
|
||||
const onNewLocal = () => {
|
||||
onConnTerminal(i18n.global.t('terminal.localhost'), localHostID.value, false);
|
||||
terminalTabs.value.push({
|
||||
index: tabIndex,
|
||||
title: i18n.global.t('terminal.localhost'),
|
||||
wsID: 0,
|
||||
status: 'online',
|
||||
latency: 0,
|
||||
});
|
||||
terminalValue.value = tabIndex;
|
||||
nextTick(() => {
|
||||
ctx.refs[`t-${terminalValue.value}`] &&
|
||||
ctx.refs[`t-${terminalValue.value}`][0].acceptParams({
|
||||
endpoint: '/api/v2/hosts/exec',
|
||||
initCmd: initCmd.value,
|
||||
error: '',
|
||||
});
|
||||
initCmd.value = '';
|
||||
});
|
||||
tabIndex++;
|
||||
};
|
||||
|
||||
const onClickConn = (node: Node, data: Tree) => {
|
||||
if (node.level === 1) {
|
||||
return;
|
||||
}
|
||||
onConnTerminal(node.label, data.id, false);
|
||||
onConnTerminal(node.label, data.id);
|
||||
};
|
||||
|
||||
const onReconnect = async (item: any) => {
|
||||
|
@ -347,6 +343,20 @@ const onReconnect = async (item: any) => {
|
|||
ctx.refs[`t-${item.index}`] && ctx.refs[`t-${item.index}`][0].onClose();
|
||||
}
|
||||
item.Refresh = !item.Refresh;
|
||||
if (item.wsID === 0) {
|
||||
nextTick(() => {
|
||||
ctx.refs[`t-${item.index}`] &&
|
||||
ctx.refs[`t-${item.index}`][0].acceptParams({
|
||||
endpoint: '/api/v2/hosts/exec',
|
||||
initCmd: initCmd.value,
|
||||
error: '',
|
||||
});
|
||||
initCmd.value = '';
|
||||
});
|
||||
syncTerminal();
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await testByID(item.wsID);
|
||||
nextTick(() => {
|
||||
ctx.refs[`t-${item.index}`] &&
|
||||
|
@ -359,16 +369,8 @@ const onReconnect = async (item: any) => {
|
|||
syncTerminal();
|
||||
};
|
||||
|
||||
const onConnTerminal = async (title: string, wsID: number, isLocal?: boolean) => {
|
||||
const onConnTerminal = async (title: string, wsID: number) => {
|
||||
const res = await testByID(wsID);
|
||||
if (isLocal) {
|
||||
for (const tab of terminalTabs.value) {
|
||||
if (tab.title === i18n.global.t('terminal.localhost')) {
|
||||
onReconnect(tab);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
terminalTabs.value.push({
|
||||
index: tabIndex,
|
||||
title: title,
|
||||
|
@ -377,9 +379,6 @@ const onConnTerminal = async (title: string, wsID: number, isLocal?: boolean) =>
|
|||
latency: 0,
|
||||
});
|
||||
terminalValue.value = tabIndex;
|
||||
if (!res.data && title === i18n.global.t('terminal.localhost')) {
|
||||
dialogRef.value!.acceptParams({ isLocal: true });
|
||||
}
|
||||
nextTick(() => {
|
||||
ctx.refs[`t-${terminalValue.value}`] &&
|
||||
ctx.refs[`t-${terminalValue.value}`][0].acceptParams({
|
||||
|
|
Loading…
Add table
Reference in a new issue