mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 快速命令与批量输入实现
This commit is contained in:
parent
5b9372a148
commit
bea9321fc8
@ -29,7 +29,7 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *BaseApi) TestConn(c *gin.Context) {
|
func (b *BaseApi) TestConn(c *gin.Context) {
|
||||||
var req dto.HostOperate
|
var req dto.HostConnTest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
return
|
return
|
||||||
|
@ -17,6 +17,15 @@ type HostOperate struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HostConnTest struct {
|
||||||
|
Addr string `json:"addr" validate:"required,ip"`
|
||||||
|
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
|
||||||
|
User string `json:"user" validate:"required"`
|
||||||
|
AuthMode string `json:"authMode" validate:"oneof=password key"`
|
||||||
|
PrivateKey string `json:"privateKey"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
type SearchForTree struct {
|
type SearchForTree struct {
|
||||||
Info string `json:"info"`
|
Info string `json:"info"`
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,14 @@ import (
|
|||||||
type CommandRouter struct{}
|
type CommandRouter struct{}
|
||||||
|
|
||||||
func (s *CommandRouter) InitCommandRouter(Router *gin.RouterGroup) {
|
func (s *CommandRouter) InitCommandRouter(Router *gin.RouterGroup) {
|
||||||
userRouter := Router.Group("commands")
|
cmdRouter := Router.Group("commands").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
|
||||||
userRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
|
withRecordRouter := Router.Group("commands").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
|
||||||
withRecordRouter := userRouter.Use(middleware.OperationRecord())
|
|
||||||
baseApi := v1.ApiGroupApp.BaseApi
|
baseApi := v1.ApiGroupApp.BaseApi
|
||||||
{
|
{
|
||||||
withRecordRouter.POST("", baseApi.CreateCommand)
|
withRecordRouter.POST("", baseApi.CreateCommand)
|
||||||
withRecordRouter.POST("/del", baseApi.DeleteCommand)
|
withRecordRouter.POST("/del", baseApi.DeleteCommand)
|
||||||
withRecordRouter.PUT(":id", baseApi.UpdateCommand)
|
withRecordRouter.PUT(":id", baseApi.UpdateCommand)
|
||||||
userRouter.POST("/search", baseApi.SearchCommand)
|
cmdRouter.POST("/search", baseApi.SearchCommand)
|
||||||
userRouter.GET("", baseApi.ListCommand)
|
cmdRouter.GET("", baseApi.ListCommand)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,14 @@ import (
|
|||||||
type GroupRouter struct{}
|
type GroupRouter struct{}
|
||||||
|
|
||||||
func (s *GroupRouter) InitGroupRouter(Router *gin.RouterGroup) {
|
func (s *GroupRouter) InitGroupRouter(Router *gin.RouterGroup) {
|
||||||
userRouter := Router.Group("groups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
|
groupRouter := Router.Group("groups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
|
||||||
withRecordRouter := Router.Group("groups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
|
withRecordRouter := Router.Group("groups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
|
||||||
baseApi := v1.ApiGroupApp.BaseApi
|
baseApi := v1.ApiGroupApp.BaseApi
|
||||||
{
|
{
|
||||||
withRecordRouter.POST("", baseApi.CreateGroup)
|
withRecordRouter.POST("", baseApi.CreateGroup)
|
||||||
withRecordRouter.DELETE(":id", baseApi.DeleteGroup)
|
withRecordRouter.DELETE(":id", baseApi.DeleteGroup)
|
||||||
userRouter.POST("/search", baseApi.ListGroup)
|
withRecordRouter.PUT(":id", baseApi.UpdateGroup)
|
||||||
userRouter.GET(":id", baseApi.GetGroupInfo)
|
groupRouter.POST("/search", baseApi.ListGroup)
|
||||||
userRouter.PUT(":id", baseApi.UpdateGroup)
|
groupRouter.GET(":id", baseApi.GetGroupInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,14 @@ export namespace Host {
|
|||||||
|
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
export interface HostConnTest {
|
||||||
|
addr: string;
|
||||||
|
port: number;
|
||||||
|
user: string;
|
||||||
|
authMode: string;
|
||||||
|
privateKey: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
export interface ReqSearch {
|
export interface ReqSearch {
|
||||||
info?: string;
|
info?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import http from '@/api';
|
import http from '@/api';
|
||||||
import { Host } from '../interface/host';
|
import { Host } from '../interface/host';
|
||||||
|
|
||||||
export const getHostList = (params: Host.ReqSearch) => {
|
export const getHostTree = (params: Host.ReqSearch) => {
|
||||||
return http.post<Array<Host.HostTree>>(`/hosts/search`, params);
|
return http.post<Array<Host.HostTree>>(`/hosts/search`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -13,12 +13,11 @@ export const addHost = (params: Host.HostOperate) => {
|
|||||||
return http.post<Host.HostOperate>(`/hosts`, params);
|
return http.post<Host.HostOperate>(`/hosts`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const testConn = (params: Host.HostOperate) => {
|
export const testConn = (params: Host.HostConnTest) => {
|
||||||
return http.post<Host.HostOperate>(`/hosts/testconn`, params);
|
return http.post(`/hosts/testconn`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editHost = (params: Host.HostOperate) => {
|
export const editHost = (params: Host.HostOperate) => {
|
||||||
console.log(params.id);
|
|
||||||
return http.put(`/hosts/` + params.id, params);
|
return http.put(`/hosts/` + params.id, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,12 +92,15 @@ export default {
|
|||||||
terminal: {
|
terminal: {
|
||||||
conn: 'connection',
|
conn: 'connection',
|
||||||
testConn: 'Test connection',
|
testConn: 'Test connection',
|
||||||
|
saveAndConn: 'Save and Connect',
|
||||||
connTestOk: 'Connection information available',
|
connTestOk: 'Connection information available',
|
||||||
hostList: 'Host information',
|
hostList: 'Host information',
|
||||||
createConn: 'Create a connection',
|
createConn: 'Create a connection',
|
||||||
createGroup: 'Create a group',
|
createGroup: 'Create a group',
|
||||||
expand: 'Expand all',
|
expand: 'Expand all',
|
||||||
fold: 'All contract',
|
fold: 'All contract',
|
||||||
|
batchInput: 'Batch input',
|
||||||
|
quickCommand: 'quick command',
|
||||||
groupDeleteHelper:
|
groupDeleteHelper:
|
||||||
'After the group is removed, all connections in the group will be migrated to the default group. Confirm the information',
|
'After the group is removed, all connections in the group will be migrated to the default group. Confirm the information',
|
||||||
addHost: 'Add Host',
|
addHost: 'Add Host',
|
||||||
|
@ -95,12 +95,15 @@ export default {
|
|||||||
terminal: {
|
terminal: {
|
||||||
conn: '连接',
|
conn: '连接',
|
||||||
testConn: '连接测试',
|
testConn: '连接测试',
|
||||||
|
saveAndConn: '保存并连接',
|
||||||
connTestOk: '连接信息可用',
|
connTestOk: '连接信息可用',
|
||||||
hostList: '主机信息',
|
hostList: '主机信息',
|
||||||
createConn: '创建连接',
|
createConn: '创建连接',
|
||||||
createGroup: '创建分组',
|
createGroup: '创建分组',
|
||||||
expand: '全部展开',
|
expand: '全部展开',
|
||||||
fold: '全部收缩',
|
fold: '全部收缩',
|
||||||
|
batchInput: '批量输入',
|
||||||
|
quickCommand: '快速命令',
|
||||||
groupDeleteHelper: '移除组后,组内所有连接将迁移到 default 组内,是否确认',
|
groupDeleteHelper: '移除组后,组内所有连接将迁移到 default 组内,是否确认',
|
||||||
quickCmd: '快捷命令',
|
quickCmd: '快捷命令',
|
||||||
command: '命令',
|
command: '命令',
|
||||||
|
@ -44,7 +44,6 @@
|
|||||||
:default-expand-all="true"
|
:default-expand-all="true"
|
||||||
:data="hostTree"
|
:data="hostTree"
|
||||||
:props="defaultProps"
|
:props="defaultProps"
|
||||||
draggable
|
|
||||||
>
|
>
|
||||||
<template #default="{ node, data }">
|
<template #default="{ node, data }">
|
||||||
<span class="custom-tree-node" @mouseover="hover = data.id" @mouseleave="hover = null">
|
<span class="custom-tree-node" @mouseover="hover = data.id" @mouseleave="hover = null">
|
||||||
@ -135,7 +134,7 @@ import type { ElForm } from 'element-plus';
|
|||||||
import { Rules } from '@/global/form-rues';
|
import { Rules } from '@/global/form-rues';
|
||||||
import { Host } from '@/api/interface/host';
|
import { Host } from '@/api/interface/host';
|
||||||
import { Group } from '@/api/interface/group';
|
import { Group } from '@/api/interface/group';
|
||||||
import { testConn, getHostList, getHostInfo, addHost, editHost, deleteHost } from '@/api/modules/host';
|
import { testConn, getHostTree, getHostInfo, addHost, editHost, deleteHost } from '@/api/modules/host';
|
||||||
import { getGroupList, addGroup, editGroup, deleteGroup } from '@/api/modules/group';
|
import { getGroupList, addGroup, editGroup, deleteGroup } from '@/api/modules/group';
|
||||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
@ -193,7 +192,7 @@ let groupOperation = ref<string>('create');
|
|||||||
let groupInputShow = ref<boolean>(false);
|
let groupInputShow = ref<boolean>(false);
|
||||||
|
|
||||||
const loadHostTree = async () => {
|
const loadHostTree = async () => {
|
||||||
const res = await getHostList(searcConfig);
|
const res = await getHostTree(searcConfig);
|
||||||
hostTree.value = res.data;
|
hostTree.value = res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -218,7 +217,6 @@ const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
|
|||||||
if (!formEl) return;
|
if (!formEl) return;
|
||||||
formEl.validate(async (valid) => {
|
formEl.validate(async (valid) => {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
console.log(ops);
|
|
||||||
switch (ops) {
|
switch (ops) {
|
||||||
case 'create':
|
case 'create':
|
||||||
await addHost(hostInfo);
|
await addHost(hostInfo);
|
||||||
|
@ -1,22 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- <el-button class="drawer-container" icon="arrowLeftBold" @click="hostDrawer = true">
|
|
||||||
{{ $t('terminal.connHistory') }}
|
|
||||||
</el-button> -->
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<el-tabs
|
<el-tabs
|
||||||
type="card"
|
type="card"
|
||||||
editable
|
|
||||||
class="terminal-tabs"
|
class="terminal-tabs"
|
||||||
style="background-color: #efefef"
|
style="background-color: #efefef"
|
||||||
v-model="terminalValue"
|
v-model="terminalValue"
|
||||||
@edit="handleTabsEdit"
|
:before-leave="beforeLeave"
|
||||||
|
@edit="handleTabsRemove"
|
||||||
>
|
>
|
||||||
<el-tab-pane :key="item.key" v-for="item in terminalTabs" :label="item.title" :name="item.key">
|
<el-tab-pane
|
||||||
|
:key="item.key"
|
||||||
|
v-for="item in terminalTabs"
|
||||||
|
:closable="true"
|
||||||
|
:label="item.title"
|
||||||
|
:name="item.key"
|
||||||
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="custom-tabs-label">
|
<span class="custom-tabs-label">
|
||||||
<el-icon color="#67C23A" v-if="item.status === 'online'"><circleCheck /></el-icon>
|
<el-icon style="margin-top: 1px" color="#67C23A" v-if="item.status === 'online'">
|
||||||
<el-icon color="#F56C6C" v-if="item.status === 'closed'"><circleClose /></el-icon>
|
<circleCheck />
|
||||||
|
</el-icon>
|
||||||
|
<el-icon style="margin-top: 1px" color="#F56C6C" v-if="item.status === 'closed'">
|
||||||
|
<circleClose />
|
||||||
|
</el-icon>
|
||||||
<span> {{ item.title }} </span>
|
<span> {{ item.title }} </span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@ -26,17 +32,71 @@
|
|||||||
:wsID="item.wsID"
|
:wsID="item.wsID"
|
||||||
:terminalID="item.key"
|
:terminalID="item.key"
|
||||||
></Terminal>
|
></Terminal>
|
||||||
<div style="background-color: #000">
|
<div>
|
||||||
<el-select v-model="quickCmd" style="width: 25%" class="m-2" placeholder="Select" size="small">
|
<el-select
|
||||||
<el-option v-for="cmd in quickCmds" :key="cmd.value" :label="cmd.label" :value="cmd.value" />
|
v-model="quickCmd"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
@change="quickInput"
|
||||||
|
style="width: 25%"
|
||||||
|
:placeholder="$t('terminal.quickCommand')"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="cmd in commandList"
|
||||||
|
:key="cmd.id"
|
||||||
|
:label="cmd.name + ' [ ' + cmd.command + ' ] '"
|
||||||
|
:value="cmd.command"
|
||||||
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-input v-model="batchInput" class="terminal-input" size="small">
|
<el-input
|
||||||
|
:placeholder="$t('terminal.batchInput')"
|
||||||
|
v-model="batchVal"
|
||||||
|
@keyup.enter="batchInput"
|
||||||
|
style="width: 75%"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-switch size="small" v-model="isBatch" class="ml-2" />
|
<el-switch size="small" v-model="isBatch" class="ml-2" />
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :closable="false" name="newTabs">
|
||||||
|
<template #label>
|
||||||
|
<el-button
|
||||||
|
v-popover="popoverRef"
|
||||||
|
style="background-color: #ededed; border: 0"
|
||||||
|
icon="Plus"
|
||||||
|
></el-button>
|
||||||
|
<el-popover ref="popoverRef" width="250px" trigger="hover" virtual-triggering persistent>
|
||||||
|
<el-button-group style="width: 100%">
|
||||||
|
<el-button @click="onNewSsh">New ssh</el-button>
|
||||||
|
<el-button @click="onNewTab">New tab</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<el-input size="small" clearable style="margin-top: 5px" v-model="hostfilterInfo">
|
||||||
|
<template #append><el-button icon="search" /></template>
|
||||||
|
</el-input>
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
node-key="id"
|
||||||
|
:default-expand-all="true"
|
||||||
|
:data="hostTree"
|
||||||
|
:props="defaultProps"
|
||||||
|
:filter-node-method="filterHost"
|
||||||
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<span class="custom-tree-node">
|
||||||
|
<span>
|
||||||
|
<a @click="onConn(node, data)">{{ node.label }}</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
</el-tab-pane>
|
||||||
<div v-if="terminalTabs.length === 0">
|
<div v-if="terminalTabs.length === 0">
|
||||||
<el-empty
|
<el-empty
|
||||||
style="background-color: #000; height: calc(100vh - 210px)"
|
style="background-color: #000; height: calc(100vh - 210px)"
|
||||||
@ -44,148 +104,88 @@
|
|||||||
></el-empty>
|
></el-empty>
|
||||||
</div>
|
</div>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <el-drawer :size="320" v-model="hostDrawer" :title="$t('terminal.hostHistory')" direction="rtl">
|
<el-dialog v-model="connVisiable" :title="$t('terminal.addHost')" width="30%">
|
||||||
<el-input size="small" clearable style="margin-top: 5px" v-model="searcConfig.info">
|
<el-form ref="hostInfoRef" label-width="100px" label-position="left" :model="hostInfo" :rules="rules">
|
||||||
<template #prepend>
|
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||||
<el-button icon="plus" @click="onAddHost">{{ $t('commons.button.add') }}</el-button>
|
<el-input clearable v-model="hostInfo.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="IP" prop="addr">
|
||||||
|
<el-input clearable v-model="hostInfo.addr" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('terminal.port')" prop="port">
|
||||||
|
<el-input clearable v-model.number="hostInfo.port" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('terminal.user')" prop="user">
|
||||||
|
<el-input clearable v-model="hostInfo.user" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('terminal.authMode')" prop="authMode">
|
||||||
|
<el-radio-group v-model="hostInfo.authMode">
|
||||||
|
<el-radio label="password">{{ $t('terminal.passwordMode') }}</el-radio>
|
||||||
|
<el-radio label="key">{{ $t('terminal.keyMode') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('terminal.password')" v-if="hostInfo.authMode === 'password'" prop="password">
|
||||||
|
<el-input clearable show-password type="password" v-model="hostInfo.password" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('terminal.key')" v-if="hostInfo.authMode === 'key'" prop="privateKey">
|
||||||
|
<el-input clearable type="textarea" v-model="hostInfo.privateKey" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="connVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button @click="submitAddHost(hostInfoRef, 'testConn')">
|
||||||
|
{{ $t('terminal.testConn') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="submitAddHost(hostInfoRef, 'saveAndConn')">
|
||||||
|
{{ $t('terminal.saveAndConn') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #append><el-button icon="search" @click="loadHost" /></template>
|
</el-dialog>
|
||||||
</el-input>
|
</div>
|
||||||
<div v-infinite-scroll="nextPage" style="overflow: auto">
|
|
||||||
<el-card
|
|
||||||
@click="onConnLocal()"
|
|
||||||
style="margin-top: 5px; cursor: pointer"
|
|
||||||
:title="$t('terminal.localhost')"
|
|
||||||
shadow="hover"
|
|
||||||
>
|
|
||||||
<div :inline="true">
|
|
||||||
<div>
|
|
||||||
<span>{{ $t('terminal.localhost') }}</span>
|
|
||||||
</div>
|
|
||||||
<span style="font-size: 14px; line-height: 25px"> [ 127.0.0.1 ]</span>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
<div v-for="(item, index) in data" :key="item.id" @mouseover="hover = index" @mouseleave="hover = null">
|
|
||||||
<el-card @click="onConn(item)" style="margin-top: 5px; cursor: pointer" shadow="hover">
|
|
||||||
<div :inline="true">
|
|
||||||
<div>
|
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
</div>
|
|
||||||
<span style="font-size: 14px; line-height: 25px">
|
|
||||||
[ {{ item.addr + ':' + item.port }} ]
|
|
||||||
<el-button
|
|
||||||
style="float: right; margin-left: 5px"
|
|
||||||
size="small"
|
|
||||||
circle
|
|
||||||
@click="onDeleteHost(item)"
|
|
||||||
v-if="hover === index"
|
|
||||||
icon="delete"
|
|
||||||
></el-button>
|
|
||||||
<el-button
|
|
||||||
style="float: right; margin-left: 5px"
|
|
||||||
size="small"
|
|
||||||
circle
|
|
||||||
@click="onEditHost(item)"
|
|
||||||
v-if="hover === index"
|
|
||||||
icon="edit"
|
|
||||||
></el-button>
|
|
||||||
<div v-if="item.description && hover === index">
|
|
||||||
<span style="font-size: 12px">{{ item.description }}</span>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-drawer> -->
|
|
||||||
|
|
||||||
<el-dialog v-model="connVisiable" :title="$t('terminal.addHost')" width="30%">
|
|
||||||
<el-form ref="hostInfoRef" label-width="100px" label-position="left" :model="hostInfo" :rules="rules">
|
|
||||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
|
||||||
<el-input clearable v-model="hostInfo.name" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="IP" prop="addr">
|
|
||||||
<el-input clearable v-model="hostInfo.addr" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('terminal.port')" prop="port">
|
|
||||||
<el-input clearable v-model.number="hostInfo.port" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('terminal.user')" prop="user">
|
|
||||||
<el-input clearable v-model="hostInfo.user" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('terminal.authMode')" prop="authMode">
|
|
||||||
<el-radio-group v-model="hostInfo.authMode">
|
|
||||||
<el-radio label="password">{{ $t('terminal.passwordMode') }}</el-radio>
|
|
||||||
<el-radio label="key">{{ $t('terminal.keyMode') }}</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('terminal.password')" v-if="hostInfo.authMode === 'password'" prop="password">
|
|
||||||
<el-input clearable show-password type="password" v-model="hostInfo.password" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('terminal.key')" v-if="hostInfo.authMode === 'key'" prop="privateKey">
|
|
||||||
<el-input clearable type="textarea" v-model="hostInfo.privateKey" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="connVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
|
||||||
<!-- <el-button v-if="operation === 'conn'" type="primary" @click="submitAddHost(hostInfoRef)">
|
|
||||||
{{ $t('commons.button.conn') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button v-else type="primary" @click="submitAddHost(hostInfoRef)">
|
|
||||||
{{ $t('commons.button.confirm') }}
|
|
||||||
</el-button> -->
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onBeforeMount, ref, nextTick, reactive, getCurrentInstance } from 'vue';
|
import { onMounted, onBeforeMount, ref, watch, nextTick, reactive, getCurrentInstance } from 'vue';
|
||||||
import { Rules } from '@/global/form-rues';
|
import { Rules } from '@/global/form-rues';
|
||||||
// import { getHostList, addHost, editHost, deleteHost } from '@/api/modules/host';
|
import { testConn, getHostTree, addHost } from '@/api/modules/host';
|
||||||
// import { useDeleteData } from '@/hooks/use-delete-data';
|
import { getCommandList } from '@/api/modules/command';
|
||||||
// import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import type { ElForm } from 'element-plus';
|
import type { ElForm } from 'element-plus';
|
||||||
import { Host } from '@/api/interface/host';
|
import { Host } from '@/api/interface/host';
|
||||||
// import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import Terminal from '@/views/terminal/terminal/index.vue';
|
import Terminal from '@/views/terminal/terminal/index.vue';
|
||||||
|
import type Node from 'element-plus/es/components/tree/src/model/node';
|
||||||
|
import { ElTree } from 'element-plus';
|
||||||
let timer: NodeJS.Timer | null = null;
|
let timer: NodeJS.Timer | null = null;
|
||||||
|
|
||||||
const terminalValue = ref();
|
const terminalValue = ref();
|
||||||
const terminalTabs = ref([]) as any;
|
const terminalTabs = ref([]) as any;
|
||||||
let tabIndex = 0;
|
let tabIndex = 0;
|
||||||
// const data = ref();
|
const commandList = ref();
|
||||||
// const hostDrawer = ref(false);
|
|
||||||
|
|
||||||
// let searcConfig = reactive<Host.ReqSearchWithPage>({
|
|
||||||
// info: '',
|
|
||||||
// page: 1,
|
|
||||||
// pageSize: 8,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const paginationConfig = reactive({
|
|
||||||
// currentPage: 1,
|
|
||||||
// pageSize: 8,
|
|
||||||
// total: 0,
|
|
||||||
// });
|
|
||||||
|
|
||||||
let quickCmd = ref();
|
let quickCmd = ref();
|
||||||
const quickCmds = [
|
let batchVal = ref();
|
||||||
{ label: 'ls', value: 'ls -l' },
|
|
||||||
{ label: 'pwd', value: 'pwd' },
|
|
||||||
];
|
|
||||||
let batchInput = ref();
|
|
||||||
let isBatch = ref<boolean>(false);
|
let isBatch = ref<boolean>(false);
|
||||||
|
const popoverRef = ref();
|
||||||
|
|
||||||
const connVisiable = ref<boolean>(false);
|
const connVisiable = ref<boolean>(false);
|
||||||
const operation = ref();
|
|
||||||
// const hover = ref();
|
|
||||||
type FormInstance = InstanceType<typeof ElForm>;
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
|
||||||
const hostInfoRef = ref<FormInstance>();
|
const hostInfoRef = ref<FormInstance>();
|
||||||
|
const hostTree = ref<Array<Host.HostTree>>();
|
||||||
|
const treeRef = ref<InstanceType<typeof ElTree>>();
|
||||||
|
const defaultProps = {
|
||||||
|
label: 'label',
|
||||||
|
children: 'children',
|
||||||
|
};
|
||||||
|
const hostfilterInfo = ref('');
|
||||||
|
interface Tree {
|
||||||
|
id: number;
|
||||||
|
label: string;
|
||||||
|
children?: Tree[];
|
||||||
|
}
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
name: [Rules.requiredInput, Rules.name],
|
name: [Rules.requiredInput, Rules.name],
|
||||||
addr: [Rules.requiredInput, Rules.ip],
|
addr: [Rules.requiredInput, Rules.ip],
|
||||||
@ -199,6 +199,7 @@ const rules = reactive({
|
|||||||
let hostInfo = reactive<Host.HostOperate>({
|
let hostInfo = reactive<Host.HostOperate>({
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
|
groupBelong: '',
|
||||||
addr: '',
|
addr: '',
|
||||||
port: 22,
|
port: 22,
|
||||||
user: '',
|
user: '',
|
||||||
@ -210,112 +211,128 @@ let hostInfo = reactive<Host.HostOperate>({
|
|||||||
|
|
||||||
const ctx = getCurrentInstance() as any;
|
const ctx = getCurrentInstance() as any;
|
||||||
|
|
||||||
const handleTabsEdit = (targetName: string, action: 'remove' | 'add') => {
|
const handleTabsRemove = (targetName: string, action: 'remove' | 'add') => {
|
||||||
if (action === 'add') {
|
if (action !== 'remove') {
|
||||||
connVisiable.value = true;
|
return;
|
||||||
operation.value = 'conn';
|
}
|
||||||
if (hostInfoRef.value) {
|
if (ctx) {
|
||||||
hostInfoRef.value.resetFields();
|
ctx.refs[`Ref${targetName}`] && ctx.refs[`Ref${targetName}`][0].onClose();
|
||||||
}
|
}
|
||||||
} else if (action === 'remove') {
|
const tabs = terminalTabs.value;
|
||||||
if (ctx) {
|
let activeName = terminalValue.value;
|
||||||
ctx.refs[`Ref${targetName}`] && ctx.refs[`Ref${targetName}`][0].onClose();
|
if (activeName === targetName) {
|
||||||
}
|
tabs.forEach((tab: any, index: any) => {
|
||||||
const tabs = terminalTabs.value;
|
if (tab.key === targetName) {
|
||||||
let activeName = terminalValue.value;
|
const nextTab = tabs[index + 1] || tabs[index - 1];
|
||||||
if (activeName === targetName) {
|
if (nextTab) {
|
||||||
tabs.forEach((tab: any, index: any) => {
|
activeName = nextTab.key;
|
||||||
if (tab.key === targetName) {
|
|
||||||
const nextTab = tabs[index + 1] || tabs[index - 1];
|
|
||||||
if (nextTab) {
|
|
||||||
activeName = nextTab.key;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
terminalValue.value = activeName;
|
||||||
|
terminalTabs.value = tabs.filter((tab: any) => tab.key !== targetName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadHost = async () => {
|
||||||
|
const res = await getHostTree({});
|
||||||
|
hostTree.value = res.data;
|
||||||
|
};
|
||||||
|
watch(hostfilterInfo, (val: any) => {
|
||||||
|
treeRef.value!.filter(val);
|
||||||
|
});
|
||||||
|
const filterHost = (value: string, data: any) => {
|
||||||
|
if (!value) return true;
|
||||||
|
return data.label.includes(value);
|
||||||
|
};
|
||||||
|
const loadCommand = async () => {
|
||||||
|
const res = await getCommandList();
|
||||||
|
commandList.value = res.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
function quickInput(val: any) {
|
||||||
|
if (val !== '') {
|
||||||
|
if (ctx) {
|
||||||
|
ctx.refs[`Ref${terminalValue.value}`] && ctx.refs[`Ref${terminalValue.value}`][0].onSendMsg(val + '\n');
|
||||||
}
|
}
|
||||||
terminalValue.value = activeName;
|
}
|
||||||
terminalTabs.value = tabs.filter((tab: any) => tab.key !== targetName);
|
}
|
||||||
|
|
||||||
|
function batchInput() {
|
||||||
|
if (batchVal.value === '' || !ctx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isBatch.value) {
|
||||||
|
for (const tab of terminalTabs.value) {
|
||||||
|
ctx.refs[`Ref${tab.key}`] && ctx.refs[`Ref${tab.key}`][0].onSendMsg(batchVal.value + '\n');
|
||||||
|
}
|
||||||
|
batchVal.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.refs[`Ref${terminalValue.value}`] && ctx.refs[`Ref${terminalValue.value}`][0].onSendMsg(batchVal.value + '\n');
|
||||||
|
batchVal.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function beforeLeave(activeName: string) {
|
||||||
|
if (activeName === 'newTabs') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onNewTab = () => {
|
||||||
|
terminalTabs.value.push({
|
||||||
|
key: `127.0.0.1-${++tabIndex}`,
|
||||||
|
title: '127.0.0.1',
|
||||||
|
wsID: 0,
|
||||||
|
status: 'online',
|
||||||
|
});
|
||||||
|
terminalValue.value = `127.0.0.1-${tabIndex}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onNewSsh = () => {
|
||||||
|
connVisiable.value = true;
|
||||||
|
if (hostInfoRef.value) {
|
||||||
|
hostInfoRef.value.resetFields();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// const loadHost = async () => {
|
const onConn = (node: Node, data: Tree) => {
|
||||||
// searcConfig.page = paginationConfig.currentPage;
|
if (node.level === 1) {
|
||||||
// searcConfig.pageSize = paginationConfig.pageSize;
|
return;
|
||||||
// const res = await getHostList(searcConfig);
|
}
|
||||||
// data.value = res.data.items;
|
let addr = data.label.split('@')[1].split(':')[0];
|
||||||
// paginationConfig.total = res.data.total;
|
terminalTabs.value.push({
|
||||||
// };
|
key: `${addr}-${++tabIndex}`,
|
||||||
|
title: addr,
|
||||||
|
wsID: data.id,
|
||||||
|
status: 'online',
|
||||||
|
});
|
||||||
|
terminalValue.value = `${addr}-${tabIndex}`;
|
||||||
|
};
|
||||||
|
|
||||||
// const nextPage = () => {
|
const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
|
||||||
// if (paginationConfig.pageSize >= paginationConfig.total) {
|
if (!formEl) return;
|
||||||
// return;
|
formEl.validate(async (valid) => {
|
||||||
// }
|
if (!valid) return;
|
||||||
// paginationConfig.pageSize = paginationConfig.pageSize + 3;
|
switch (ops) {
|
||||||
// loadHost();
|
case 'testConn':
|
||||||
// };
|
await testConn(hostInfo);
|
||||||
|
ElMessage.success(i18n.global.t('terminal.connTestOk'));
|
||||||
// function onAddHost() {
|
break;
|
||||||
// connVisiable.value = true;
|
case 'saveAndConn':
|
||||||
// operation.value = 'create';
|
const res = await addHost(hostInfo);
|
||||||
// if (hostInfoRef.value) {
|
terminalTabs.value.push({
|
||||||
// hostInfoRef.value.resetFields();
|
key: `${res.data.addr}-${++tabIndex}`,
|
||||||
// }
|
title: res.data.addr,
|
||||||
// }
|
wsID: res.data.id,
|
||||||
|
status: 'online',
|
||||||
// function onEditHost(row: Host.Host) {
|
});
|
||||||
// hostInfo.id = row.id;
|
terminalValue.value = `${res.data.addr}-${tabIndex}`;
|
||||||
// hostInfo.name = row.name;
|
connVisiable.value = false;
|
||||||
// hostInfo.addr = row.addr;
|
loadHost();
|
||||||
// hostInfo.port = row.port;
|
}
|
||||||
// hostInfo.user = row.user;
|
});
|
||||||
// hostInfo.authMode = row.authMode;
|
};
|
||||||
// hostInfo.password = '';
|
|
||||||
// hostInfo.privateKey = '';
|
|
||||||
// operation.value = 'update';
|
|
||||||
// connVisiable.value = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const submitAddHost = (formEl: FormInstance | undefined) => {
|
|
||||||
// if (!formEl) return;
|
|
||||||
// formEl.validate(async (valid) => {
|
|
||||||
// if (!valid) return;
|
|
||||||
// try {
|
|
||||||
// switch (operation.value) {
|
|
||||||
// case 'create':
|
|
||||||
// await addHost(hostInfo);
|
|
||||||
// ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
// break;
|
|
||||||
// case 'update':
|
|
||||||
// await editHost(hostInfo);
|
|
||||||
// ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
|
||||||
// break;
|
|
||||||
// case 'conn':
|
|
||||||
// const res = await addHost(hostInfo);
|
|
||||||
// terminalTabs.value.push({
|
|
||||||
// key: `${res.data.addr}-${++tabIndex}`,
|
|
||||||
// title: res.data.addr,
|
|
||||||
// wsID: res.data.id,
|
|
||||||
// status: 'online',
|
|
||||||
// });
|
|
||||||
// terminalValue.value = `${res.data.addr}-${tabIndex}`;
|
|
||||||
// }
|
|
||||||
// connVisiable.value = false;
|
|
||||||
// // loadHost();
|
|
||||||
// } catch (error) {
|
|
||||||
// ElMessage.success(i18n.global.t('commons.msg.loginSuccess') + ':' + error);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const onConn = (row: Host.Host) => {
|
|
||||||
// terminalTabs.value.push({
|
|
||||||
// key: `${row.addr}-${++tabIndex}`,
|
|
||||||
// title: row.addr,
|
|
||||||
// wsID: row.id,
|
|
||||||
// status: 'online',
|
|
||||||
// });
|
|
||||||
// terminalValue.value = `${row.addr}-${tabIndex}`;
|
|
||||||
// };
|
|
||||||
|
|
||||||
const onConnLocal = () => {
|
const onConnLocal = () => {
|
||||||
terminalTabs.value.push({
|
terminalTabs.value.push({
|
||||||
@ -327,12 +344,6 @@ const onConnLocal = () => {
|
|||||||
terminalValue.value = `127.0.0.1-${tabIndex}`;
|
terminalValue.value = `127.0.0.1-${tabIndex}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// const onDeleteHost = async (row: Host.Host) => {
|
|
||||||
// let ids: Array<number> = [row.id];
|
|
||||||
// await useDeleteData(deleteHost, { ids: ids }, 'commons.msg.delete');
|
|
||||||
// loadHost();
|
|
||||||
// };
|
|
||||||
|
|
||||||
function changeFrameHeight() {
|
function changeFrameHeight() {
|
||||||
let ifm = document.getElementById('iframeTerminal') as HTMLInputElement | null;
|
let ifm = document.getElementById('iframeTerminal') as HTMLInputElement | null;
|
||||||
if (ifm) {
|
if (ifm) {
|
||||||
@ -343,6 +354,7 @@ function changeFrameHeight() {
|
|||||||
function syncTerminal() {
|
function syncTerminal() {
|
||||||
for (const terminal of terminalTabs.value) {
|
for (const terminal of terminalTabs.value) {
|
||||||
if (ctx && ctx.refs[`Ref${terminal.key}`]) {
|
if (ctx && ctx.refs[`Ref${terminal.key}`]) {
|
||||||
|
console.log(ctx.refs[`Ref${terminal.key}`][0]);
|
||||||
terminal.status = ctx.refs[`Ref${terminal.key}`][0].isWsOpen() ? 'online' : 'closed';
|
terminal.status = ctx.refs[`Ref${terminal.key}`][0].isWsOpen() ? 'online' : 'closed';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -354,7 +366,8 @@ onMounted(() => {
|
|||||||
changeFrameHeight();
|
changeFrameHeight();
|
||||||
window.addEventListener('resize', changeFrameHeight);
|
window.addEventListener('resize', changeFrameHeight);
|
||||||
});
|
});
|
||||||
// loadHost();
|
loadHost();
|
||||||
|
loadCommand();
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
syncTerminal();
|
syncTerminal();
|
||||||
}, 1000 * 8);
|
}, 1000 * 8);
|
||||||
@ -364,29 +377,6 @@ onBeforeMount(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.drawer-container {
|
|
||||||
transition: all 0.2s;
|
|
||||||
&:hover {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
position: fixed;
|
|
||||||
right: -90px;
|
|
||||||
top: 15%;
|
|
||||||
height: 40px;
|
|
||||||
width: 130px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 999;
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.terminal-input {
|
|
||||||
&:hover {
|
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
.terminal-tabs {
|
.terminal-tabs {
|
||||||
:deep .el-tabs__header {
|
:deep .el-tabs__header {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -408,23 +398,6 @@ onBeforeMount(() => {
|
|||||||
color: #ebeef5;
|
color: #ebeef5;
|
||||||
background-color: #575758;
|
background-color: #575758;
|
||||||
}
|
}
|
||||||
:deep .el-tabs__new-tab {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
float: right;
|
|
||||||
border: 1pxsolidvar (--el-border-color);
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
line-height: 20px;
|
|
||||||
margin: 10px 30px 10px 10px;
|
|
||||||
border-radius: 3px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 24px;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.15s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vertical-tabs > .el-tabs__content {
|
.vertical-tabs > .el-tabs__content {
|
||||||
@ -433,9 +406,14 @@ onBeforeMount(() => {
|
|||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.fullScreen {
|
||||||
.el-tabs--right .el-tabs__content,
|
position: absolute;
|
||||||
.el-tabs--left .el-tabs__content {
|
right: 50px;
|
||||||
height: 100%;
|
top: 85px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.el-tabs--top.el-tabs--card > .el-tabs__header .el-tabs__item:last-child {
|
||||||
|
padding-right: 0px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -73,7 +73,6 @@ const initTerm = () => {
|
|||||||
});
|
});
|
||||||
if (ifm) {
|
if (ifm) {
|
||||||
term.open(ifm);
|
term.open(ifm);
|
||||||
term.write('\n');
|
|
||||||
if (props.wsID === 0) {
|
if (props.wsID === 0) {
|
||||||
terminalSocket = new WebSocket(
|
terminalSocket = new WebSocket(
|
||||||
`ws://localhost:9999/api/v1/terminals/local?cols=${term.cols}&rows=${term.rows}`,
|
`ws://localhost:9999/api/v1/terminals/local?cols=${term.cols}&rows=${term.rows}`,
|
||||||
@ -129,6 +128,15 @@ function onClose() {
|
|||||||
term && term.dispose();
|
term && term.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSendMsg(command: string) {
|
||||||
|
terminalSocket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: 'cmd',
|
||||||
|
cmd: Base64.encode(command),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function changeTerminalSize() {
|
function changeTerminalSize() {
|
||||||
fitTerm();
|
fitTerm();
|
||||||
const { cols, rows } = term;
|
const { cols, rows } = term;
|
||||||
@ -146,6 +154,7 @@ function changeTerminalSize() {
|
|||||||
defineExpose({
|
defineExpose({
|
||||||
onClose,
|
onClose,
|
||||||
isWsOpen,
|
isWsOpen,
|
||||||
|
onSendMsg,
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user