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

feat: 快速命令与批量输入实现

This commit is contained in:
ssongliu 2022-09-01 16:52:58 +08:00 committed by ssongliu
parent 5b9372a148
commit bea9321fc8
11 changed files with 311 additions and 305 deletions

View File

@ -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

View File

@ -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"`
} }

View File

@ -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)
} }
} }

View File

@ -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)
} }
} }

View File

@ -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;
} }

View File

@ -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);
}; };

View File

@ -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',

View File

@ -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: '命令',

View File

@ -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);

View File

@ -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> &nbsp;{{ item.title }}&nbsp;&nbsp;</span> <span> &nbsp;{{ item.title }}&nbsp;&nbsp;</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>

View File

@ -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(() => {