2024-02-07 06:37:59 +00:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="flex-all-center">
|
|
|
|
|
|
<!-- 文档: https://antoniandre.github.io/splitpanes/ -->
|
|
|
|
|
|
<Splitpanes class="default-theme" @resized="onResizeTagTree">
|
|
|
|
|
|
<Pane size="20" max-size="30">
|
|
|
|
|
|
<tag-tree
|
|
|
|
|
|
class="machine-terminal-tree"
|
|
|
|
|
|
ref="tagTreeRef"
|
2024-04-09 12:55:51 +08:00
|
|
|
|
:resource-type="TagResourceTypeEnum.MachineAuthCert.value"
|
2024-02-07 06:37:59 +00:00
|
|
|
|
:tag-path-node-type="NodeTypeTagPath"
|
2024-02-07 21:14:29 +08:00
|
|
|
|
>
|
|
|
|
|
|
<template #prefix="{ data }">
|
2024-04-06 04:03:38 +00:00
|
|
|
|
<SvgIcon
|
2024-04-09 12:55:51 +08:00
|
|
|
|
v-if="data.icon && data.params.status == 1 && data.params.protocol == MachineProtocolEnum.Ssh.value"
|
|
|
|
|
|
:name="data.icon.name"
|
|
|
|
|
|
:color="data.icon.color"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<SvgIcon
|
|
|
|
|
|
v-if="data.icon && data.params.status == -1 && data.params.protocol == MachineProtocolEnum.Ssh.value"
|
2024-04-06 04:03:38 +00:00
|
|
|
|
:name="data.icon.name"
|
|
|
|
|
|
color="var(--el-color-danger)"
|
|
|
|
|
|
/>
|
2024-04-09 12:55:51 +08:00
|
|
|
|
<SvgIcon v-if="data.icon && data.params.protocol != MachineProtocolEnum.Ssh.value" :name="data.icon.name" :color="data.icon.color" />
|
2024-02-07 21:14:29 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<template #suffix="{ data }">
|
2024-04-09 12:55:51 +08:00
|
|
|
|
<span style="color: #c4c9c4; font-size: 9px" v-if="data.type.value == MachineNodeType.AuthCert">{{
|
|
|
|
|
|
` ${data.params.selectAuthCert.username}@${data.params.ip}:${data.params.port}`
|
2024-02-07 21:14:29 +08:00
|
|
|
|
}}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</tag-tree>
|
2024-02-07 06:37:59 +00:00
|
|
|
|
</Pane>
|
|
|
|
|
|
|
|
|
|
|
|
<Pane>
|
2024-02-07 21:14:29 +08:00
|
|
|
|
<div class="machine-terminal-tabs card pd5">
|
2024-02-07 06:37:59 +00:00
|
|
|
|
<el-tabs
|
|
|
|
|
|
v-if="state.tabs.size > 0"
|
|
|
|
|
|
type="card"
|
|
|
|
|
|
@tab-remove="onRemoveTab"
|
|
|
|
|
|
@tab-change="onTabChange"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
v-model="state.activeTermName"
|
2024-02-07 21:14:29 +08:00
|
|
|
|
class="h100"
|
2024-02-07 06:37:59 +00:00
|
|
|
|
>
|
|
|
|
|
|
<el-tab-pane class="h100" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
|
|
|
|
|
|
<template #label>
|
2024-04-06 04:03:38 +00:00
|
|
|
|
<el-popconfirm @confirm="handleReconnect(dt, true)" title="确认重新连接?">
|
2024-02-07 06:37:59 +00:00
|
|
|
|
<template #reference>
|
2024-02-07 21:14:29 +08:00
|
|
|
|
<el-icon class="mr5" :color="dt.status == 1 ? '#67c23a' : '#f56c6c'" :title="dt.status == 1 ? '' : '点击重连'"
|
2024-02-07 06:37:59 +00:00
|
|
|
|
><Connection />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-popconfirm>
|
2024-02-07 21:14:29 +08:00
|
|
|
|
<el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250">
|
2024-02-07 06:37:59 +00:00
|
|
|
|
<template #reference>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span class="machine-terminal-tab-label">{{ dt.label }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template #default>
|
|
|
|
|
|
<el-descriptions :column="1" size="small">
|
|
|
|
|
|
<el-descriptions-item label="机器名"> {{ dt.params?.name }} </el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="host"> {{ dt.params?.ip }} : {{ dt.params?.port }} </el-descriptions-item>
|
2024-02-07 21:14:29 +08:00
|
|
|
|
<el-descriptions-item label="username"> {{ dt.params?.username }} </el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="remark"> {{ dt.params?.remark }} </el-descriptions-item>
|
2024-02-07 06:37:59 +00:00
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-popover>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2024-04-06 04:03:38 +00:00
|
|
|
|
<div :ref="(el: any) => setTerminalWrapperRef(el, dt.key)" class="terminal-wrapper" style="height: calc(100vh - 155px)">
|
2024-02-07 06:37:59 +00:00
|
|
|
|
<TerminalBody
|
2024-04-09 12:55:51 +08:00
|
|
|
|
v-if="dt.params.protocol == MachineProtocolEnum.Ssh.value"
|
2024-02-29 22:12:50 +08:00
|
|
|
|
:mount-init="false"
|
2024-02-07 06:37:59 +00:00
|
|
|
|
@status-change="terminalStatusChange(dt.key, $event)"
|
2024-04-06 04:03:38 +00:00
|
|
|
|
:ref="(el: any) => setTerminalRef(el, dt.key)"
|
2024-02-07 21:14:29 +08:00
|
|
|
|
:socket-url="dt.socketUrl"
|
2024-02-07 06:37:59 +00:00
|
|
|
|
/>
|
2024-04-06 04:03:38 +00:00
|
|
|
|
<machine-rdp
|
2024-04-09 12:55:51 +08:00
|
|
|
|
v-if="dt.params.protocol != MachineProtocolEnum.Ssh.value"
|
2024-04-06 04:03:38 +00:00
|
|
|
|
:machine-id="dt.params.id"
|
2024-04-12 07:53:42 +00:00
|
|
|
|
:auth-cert="dt.authCert"
|
2024-04-06 04:03:38 +00:00
|
|
|
|
:ref="(el: any) => setTerminalRef(el, dt.key)"
|
|
|
|
|
|
@status-change="terminalStatusChange(dt.key, $event)"
|
|
|
|
|
|
/>
|
2024-02-07 06:37:59 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
</el-tabs>
|
|
|
|
|
|
|
|
|
|
|
|
<el-dialog v-model="infoDialog.visible">
|
|
|
|
|
|
<el-descriptions title="详情" :column="3" border>
|
|
|
|
|
|
<el-descriptions-item :span="1.5" label="机器id">{{ infoDialog.data.id }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item :span="1.5" label="名称">{{ infoDialog.data.name }}</el-descriptions-item>
|
|
|
|
|
|
|
2024-04-09 12:55:51 +08:00
|
|
|
|
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="infoDialog.data.tags" /></el-descriptions-item>
|
2024-02-07 06:37:59 +00:00
|
|
|
|
|
|
|
|
|
|
<el-descriptions-item :span="2" label="IP">{{ infoDialog.data.ip }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
|
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<process-list v-model:visible="processDialog.visible" v-model:machineId="processDialog.machineId" />
|
|
|
|
|
|
|
2024-04-10 13:04:31 +08:00
|
|
|
|
<script-manage
|
|
|
|
|
|
:title="serviceDialog.title"
|
|
|
|
|
|
v-model:visible="serviceDialog.visible"
|
|
|
|
|
|
v-model:machineId="serviceDialog.machineId"
|
|
|
|
|
|
:auth-cert-name="serviceDialog.authCertName"
|
|
|
|
|
|
/>
|
2024-02-07 06:37:59 +00:00
|
|
|
|
|
2024-04-06 04:03:38 +00:00
|
|
|
|
<file-conf-list
|
|
|
|
|
|
:title="fileDialog.title"
|
2024-04-10 13:04:31 +08:00
|
|
|
|
:auth-cert-name="fileDialog.authCertName"
|
2024-04-06 04:03:38 +00:00
|
|
|
|
v-model:visible="fileDialog.visible"
|
|
|
|
|
|
v-model:machineId="fileDialog.machineId"
|
|
|
|
|
|
:protocol="fileDialog.protocol"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
destroy-on-close
|
|
|
|
|
|
:title="state.filesystemDialog.title"
|
|
|
|
|
|
v-model="state.filesystemDialog.visible"
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
width="70%"
|
|
|
|
|
|
>
|
|
|
|
|
|
<machine-file
|
|
|
|
|
|
:machine-id="state.filesystemDialog.machineId"
|
2024-04-10 13:04:31 +08:00
|
|
|
|
:auth-cert-name="state.filesystemDialog.authCertName"
|
2024-04-06 04:03:38 +00:00
|
|
|
|
:protocol="state.filesystemDialog.protocol"
|
|
|
|
|
|
:file-id="state.filesystemDialog.fileId"
|
|
|
|
|
|
:path="state.filesystemDialog.path"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-dialog>
|
2024-02-07 06:37:59 +00:00
|
|
|
|
|
|
|
|
|
|
<machine-stats v-model:visible="machineStatsDialog.visible" :machineId="machineStatsDialog.machineId" :title="machineStatsDialog.title" />
|
|
|
|
|
|
|
|
|
|
|
|
<machine-rec v-model:visible="machineRecDialog.visible" :machineId="machineRecDialog.machineId" :title="machineRecDialog.title" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Pane>
|
|
|
|
|
|
</Splitpanes>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2024-04-06 04:03:38 +00:00
|
|
|
|
import { defineAsyncComponent, nextTick, reactive, ref, toRefs, watch } from 'vue';
|
2024-02-07 06:37:59 +00:00
|
|
|
|
import { useRouter } from 'vue-router';
|
2024-04-06 04:03:38 +00:00
|
|
|
|
import { getMachineTerminalSocketUrl, machineApi } from './api';
|
2024-02-07 06:37:59 +00:00
|
|
|
|
import { dateFormat } from '@/common/utils/date';
|
|
|
|
|
|
import { hasPerms } from '@/components/auth/auth';
|
|
|
|
|
|
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
|
|
|
|
|
import { NodeType, TagTreeNode } from '../component/tag';
|
|
|
|
|
|
import TagTree from '../component/TagTree.vue';
|
2024-04-06 04:03:38 +00:00
|
|
|
|
import { Pane, Splitpanes } from 'splitpanes';
|
2024-02-07 06:37:59 +00:00
|
|
|
|
import { ContextmenuItem } from '@/components/contextmenu/index';
|
2024-04-06 04:03:38 +00:00
|
|
|
|
import TerminalBody from '@/components/terminal/TerminalBody.vue';
|
|
|
|
|
|
import { TerminalStatus } from '@/components/terminal/common';
|
|
|
|
|
|
import MachineRdp from '@/components/terminal-rdp/MachineRdp.vue';
|
|
|
|
|
|
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
|
2024-04-09 12:55:51 +08:00
|
|
|
|
import ResourceTags from '../component/ResourceTags.vue';
|
|
|
|
|
|
import { MachineProtocolEnum } from './enums';
|
|
|
|
|
|
|
2024-02-07 06:37:59 +00:00
|
|
|
|
// 组件
|
|
|
|
|
|
const ScriptManage = defineAsyncComponent(() => import('./ScriptManage.vue'));
|
|
|
|
|
|
const FileConfList = defineAsyncComponent(() => import('./file/FileConfList.vue'));
|
|
|
|
|
|
const MachineStats = defineAsyncComponent(() => import('./MachineStats.vue'));
|
|
|
|
|
|
const MachineRec = defineAsyncComponent(() => import('./MachineRec.vue'));
|
|
|
|
|
|
const ProcessList = defineAsyncComponent(() => import('./ProcessList.vue'));
|
|
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
|
|
const perms = {
|
|
|
|
|
|
addMachine: 'machine:add',
|
|
|
|
|
|
updateMachine: 'machine:update',
|
|
|
|
|
|
delMachine: 'machine:del',
|
|
|
|
|
|
terminal: 'machine:terminal',
|
|
|
|
|
|
closeCli: 'machine:close-cli',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 该用户拥有的的操作列按钮权限,使用v-if进行判断,v-auth对el-dropdown-item无效
|
|
|
|
|
|
const actionBtns = hasPerms([perms.updateMachine, perms.closeCli]);
|
|
|
|
|
|
|
|
|
|
|
|
class MachineNodeType {
|
|
|
|
|
|
static Machine = 1;
|
2024-04-09 12:55:51 +08:00
|
|
|
|
static AuthCert = 2;
|
2024-02-07 06:37:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const state = reactive({
|
|
|
|
|
|
params: {
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 0,
|
|
|
|
|
|
ip: null,
|
|
|
|
|
|
name: null,
|
|
|
|
|
|
tagPath: '',
|
|
|
|
|
|
},
|
|
|
|
|
|
infoDialog: {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
data: null as any,
|
|
|
|
|
|
},
|
|
|
|
|
|
serviceDialog: {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
machineId: 0,
|
2024-04-10 13:04:31 +08:00
|
|
|
|
authCertName: '',
|
2024-02-07 06:37:59 +00:00
|
|
|
|
title: '',
|
|
|
|
|
|
},
|
|
|
|
|
|
processDialog: {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
machineId: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
fileDialog: {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
machineId: 0,
|
2024-04-06 04:03:38 +00:00
|
|
|
|
protocol: 1,
|
|
|
|
|
|
title: '',
|
2024-04-10 13:04:31 +08:00
|
|
|
|
authCertName: '',
|
2024-04-06 04:03:38 +00:00
|
|
|
|
},
|
|
|
|
|
|
filesystemDialog: {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
machineId: 0,
|
2024-04-10 13:04:31 +08:00
|
|
|
|
authCertName: '',
|
2024-04-06 04:03:38 +00:00
|
|
|
|
protocol: 1,
|
2024-02-07 06:37:59 +00:00
|
|
|
|
title: '',
|
2024-04-06 04:03:38 +00:00
|
|
|
|
fileId: 0,
|
|
|
|
|
|
path: '',
|
2024-02-07 06:37:59 +00:00
|
|
|
|
},
|
|
|
|
|
|
machineStatsDialog: {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
stats: null,
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
machineId: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
machineRecDialog: {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
machineId: 0,
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
},
|
|
|
|
|
|
activeTermName: '',
|
2024-02-07 21:14:29 +08:00
|
|
|
|
tabs: new Map<string, any>(),
|
2024-02-07 06:37:59 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const { infoDialog, serviceDialog, processDialog, fileDialog, machineStatsDialog, machineRecDialog } = toRefs(state);
|
|
|
|
|
|
|
|
|
|
|
|
const tagTreeRef: any = ref(null);
|
|
|
|
|
|
|
2024-04-09 12:55:51 +08:00
|
|
|
|
let openIds = {};
|
|
|
|
|
|
|
|
|
|
|
|
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (node: TagTreeNode) => {
|
2024-02-07 06:37:59 +00:00
|
|
|
|
// 加载标签树下的机器列表
|
|
|
|
|
|
state.params.tagPath = node.key;
|
|
|
|
|
|
state.params.pageNum = 1;
|
|
|
|
|
|
state.params.pageSize = 1000;
|
|
|
|
|
|
const res = await search();
|
|
|
|
|
|
// 把list 根据name字段排序
|
|
|
|
|
|
res.list = res.list.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
2024-02-07 21:14:29 +08:00
|
|
|
|
return res.list.map((x: any) =>
|
2024-04-09 12:55:51 +08:00
|
|
|
|
new TagTreeNode(x.id, x.name, NodeTypeMachine)
|
2024-02-07 21:14:29 +08:00
|
|
|
|
.withParams(x)
|
2024-04-09 12:55:51 +08:00
|
|
|
|
.withDisabled(x.status == -1 && x.protocol == MachineProtocolEnum.Ssh.value)
|
2024-02-07 21:14:29 +08:00
|
|
|
|
.withIcon({
|
|
|
|
|
|
name: 'Monitor',
|
|
|
|
|
|
color: '#409eff',
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
2024-02-07 06:37:59 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
2024-04-09 12:55:51 +08:00
|
|
|
|
const NodeTypeMachine = new NodeType(MachineNodeType.Machine)
|
|
|
|
|
|
.withLoadNodesFunc((node: TagTreeNode) => {
|
|
|
|
|
|
const machine = node.params;
|
|
|
|
|
|
// 获取授权凭证列表
|
|
|
|
|
|
const authCerts = machine.authCerts;
|
|
|
|
|
|
return authCerts.map((x: any) =>
|
|
|
|
|
|
new TagTreeNode(x.id, x.username, NodeTypeAuthCert)
|
|
|
|
|
|
.withParams({ ...machine, selectAuthCert: x })
|
|
|
|
|
|
.withDisabled(machine.status == -1 && machine.protocol == MachineProtocolEnum.Ssh.value)
|
|
|
|
|
|
.withIcon({
|
|
|
|
|
|
name: 'Ticket',
|
|
|
|
|
|
color: '#409eff',
|
|
|
|
|
|
})
|
|
|
|
|
|
.withIsLeaf(true)
|
|
|
|
|
|
);
|
|
|
|
|
|
})
|
|
|
|
|
|
.withContextMenuItems([
|
|
|
|
|
|
new ContextmenuItem('detail', '详情').withIcon('More').withOnClick((node: any) => showInfo(node.params)),
|
2024-04-10 13:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
new ContextmenuItem('status', '状态')
|
|
|
|
|
|
.withIcon('Compass')
|
|
|
|
|
|
.withHideFunc((node: any) => node.params.protocol != MachineProtocolEnum.Ssh.value)
|
|
|
|
|
|
.withOnClick((node: any) => showMachineStats(node.params)),
|
|
|
|
|
|
|
|
|
|
|
|
new ContextmenuItem('process', '进程')
|
|
|
|
|
|
.withIcon('DataLine')
|
|
|
|
|
|
.withHideFunc((node: any) => node.params.protocol != MachineProtocolEnum.Ssh.value)
|
|
|
|
|
|
.withOnClick((node: any) => showProcess(node.params)),
|
|
|
|
|
|
|
2024-04-09 12:55:51 +08:00
|
|
|
|
new ContextmenuItem('edit', '终端回放')
|
|
|
|
|
|
.withIcon('Compass')
|
|
|
|
|
|
.withOnClick((node: any) => showRec(node.params))
|
|
|
|
|
|
.withHideFunc((node: any) => actionBtns[perms.updateMachine] && node.params.enableRecorder == 1),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
const NodeTypeAuthCert = new NodeType(MachineNodeType.AuthCert)
|
|
|
|
|
|
.withNodeDblclickFunc((node: TagTreeNode) => {
|
|
|
|
|
|
openTerminal(node.params);
|
|
|
|
|
|
})
|
|
|
|
|
|
.withContextMenuItems([
|
|
|
|
|
|
new ContextmenuItem('term', '打开终端').withIcon('Monitor').withOnClick((node: any) => openTerminal(node.params)),
|
|
|
|
|
|
new ContextmenuItem('term-ex', '打开终端(新窗口)').withIcon('Monitor').withOnClick((node: any) => openTerminal(node.params, true)),
|
|
|
|
|
|
new ContextmenuItem('files', '文件管理').withIcon('FolderOpened').withOnClick((node: any) => showFileManage(node.params)),
|
2024-04-10 13:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
new ContextmenuItem('scripts', '脚本管理')
|
|
|
|
|
|
.withIcon('Files')
|
|
|
|
|
|
.withHideFunc((node: any) => node.params.protocol != MachineProtocolEnum.Ssh.value)
|
|
|
|
|
|
.withOnClick((node: any) => serviceManager(node.params)),
|
2024-04-09 12:55:51 +08:00
|
|
|
|
]);
|
2024-02-07 06:37:59 +00:00
|
|
|
|
|
2024-02-07 21:14:29 +08:00
|
|
|
|
const openTerminal = (machine: any, ex?: boolean) => {
|
2024-04-09 12:55:51 +08:00
|
|
|
|
// 授权凭证名
|
|
|
|
|
|
const ac = machine.selectAuthCert.name;
|
|
|
|
|
|
|
2024-02-07 21:14:29 +08:00
|
|
|
|
// 新窗口打开
|
2024-02-07 06:37:59 +00:00
|
|
|
|
if (ex) {
|
2024-04-09 12:55:51 +08:00
|
|
|
|
if (machine.protocol == MachineProtocolEnum.Ssh.value) {
|
2024-04-06 04:03:38 +00:00
|
|
|
|
const { href } = router.resolve({
|
|
|
|
|
|
path: `/machine/terminal`,
|
|
|
|
|
|
query: {
|
2024-04-09 12:55:51 +08:00
|
|
|
|
ac,
|
2024-04-06 04:03:38 +00:00
|
|
|
|
name: machine.name,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
window.open(href, '_blank');
|
|
|
|
|
|
return;
|
2024-04-09 12:55:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (machine.protocol == MachineProtocolEnum.Rdp.value) {
|
2024-04-06 04:03:38 +00:00
|
|
|
|
const { href } = router.resolve({
|
|
|
|
|
|
path: `/machine/terminal-rdp`,
|
|
|
|
|
|
query: {
|
2024-04-12 07:53:42 +00:00
|
|
|
|
machineId: machine.id,
|
|
|
|
|
|
ac: ac,
|
2024-04-06 04:03:38 +00:00
|
|
|
|
name: machine.name,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
window.open(href, '_blank');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2024-02-07 06:37:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-09 12:55:51 +08:00
|
|
|
|
let { name, username } = machine;
|
|
|
|
|
|
const labelName = `${machine.selectAuthCert.username}@${name}`;
|
2024-02-07 06:37:59 +00:00
|
|
|
|
|
|
|
|
|
|
// 同一个机器的终端打开多次,key后添加下划线和数字区分
|
2024-04-09 12:55:51 +08:00
|
|
|
|
openIds[ac] = openIds[ac] ? ++openIds[ac] : 1;
|
|
|
|
|
|
let sameIndex = openIds[ac];
|
2024-02-07 06:37:59 +00:00
|
|
|
|
|
2024-04-09 12:55:51 +08:00
|
|
|
|
let key = `${ac}_${username}_${sameIndex}`;
|
|
|
|
|
|
// 只保留name的15个字,超出部分只保留前后10个字符,中间用省略号代替
|
|
|
|
|
|
const label = labelName.length > 15 ? labelName.slice(0, 10) + '...' + labelName.slice(-10) : labelName;
|
2024-02-07 06:37:59 +00:00
|
|
|
|
|
2024-04-06 04:03:38 +00:00
|
|
|
|
let tab = {
|
2024-02-07 06:37:59 +00:00
|
|
|
|
key,
|
2024-02-07 21:14:29 +08:00
|
|
|
|
label: `${label}${sameIndex === 1 ? '' : ':' + sameIndex}`, // label组成为:总打开term次数+name+同一个机器打开的次数
|
2024-02-07 06:37:59 +00:00
|
|
|
|
params: machine,
|
2024-04-12 07:53:42 +00:00
|
|
|
|
authCert: ac,
|
2024-04-09 12:55:51 +08:00
|
|
|
|
socketUrl: getMachineTerminalSocketUrl(ac),
|
2024-04-06 04:03:38 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
state.tabs.set(key, tab);
|
2024-02-07 06:37:59 +00:00
|
|
|
|
state.activeTermName = key;
|
2024-02-29 22:12:50 +08:00
|
|
|
|
|
|
|
|
|
|
nextTick(() => {
|
2024-04-06 04:03:38 +00:00
|
|
|
|
handleReconnect(tab);
|
2024-02-29 22:12:50 +08:00
|
|
|
|
});
|
2024-02-07 06:37:59 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const serviceManager = (row: any) => {
|
2024-04-10 13:04:31 +08:00
|
|
|
|
const authCert = row.selectAuthCert;
|
2024-02-07 06:37:59 +00:00
|
|
|
|
state.serviceDialog.machineId = row.id;
|
|
|
|
|
|
state.serviceDialog.visible = true;
|
2024-04-10 13:04:31 +08:00
|
|
|
|
state.serviceDialog.authCertName = authCert.name;
|
|
|
|
|
|
state.serviceDialog.title = `${row.name} => ${authCert.username}@${row.ip}`;
|
2024-02-07 06:37:59 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 显示机器状态统计信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
const showMachineStats = async (machine: any) => {
|
|
|
|
|
|
state.machineStatsDialog.machineId = machine.id;
|
|
|
|
|
|
state.machineStatsDialog.title = `机器状态: ${machine.name} => ${machine.ip}`;
|
|
|
|
|
|
state.machineStatsDialog.visible = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const search = async () => {
|
|
|
|
|
|
const res = await machineApi.list.request(state.params);
|
|
|
|
|
|
return res;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const showFileManage = (selectionData: any) => {
|
2024-04-10 13:04:31 +08:00
|
|
|
|
const authCert = selectionData.selectAuthCert;
|
2024-04-06 04:03:38 +00:00
|
|
|
|
if (selectionData.protocol == 1) {
|
|
|
|
|
|
state.fileDialog.visible = true;
|
|
|
|
|
|
state.fileDialog.protocol = selectionData.protocol;
|
|
|
|
|
|
state.fileDialog.machineId = selectionData.id;
|
2024-04-10 13:04:31 +08:00
|
|
|
|
state.fileDialog.authCertName = authCert.name;
|
|
|
|
|
|
state.fileDialog.title = `${selectionData.name} => ${authCert.username}@${selectionData.ip}`;
|
2024-04-06 04:03:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (selectionData.protocol == 2) {
|
|
|
|
|
|
state.filesystemDialog.protocol = 2;
|
|
|
|
|
|
state.filesystemDialog.machineId = selectionData.id;
|
2024-04-10 13:04:31 +08:00
|
|
|
|
state.filesystemDialog.authCertName = authCert.name;
|
2024-04-06 04:03:38 +00:00
|
|
|
|
state.filesystemDialog.fileId = selectionData.id;
|
|
|
|
|
|
state.filesystemDialog.path = '/';
|
|
|
|
|
|
state.filesystemDialog.title = `远程桌面文件管理`;
|
|
|
|
|
|
state.filesystemDialog.visible = true;
|
|
|
|
|
|
}
|
2024-02-07 06:37:59 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const showInfo = (info: any) => {
|
|
|
|
|
|
state.infoDialog.data = info;
|
|
|
|
|
|
state.infoDialog.visible = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const showProcess = (row: any) => {
|
|
|
|
|
|
state.processDialog.machineId = row.id;
|
|
|
|
|
|
state.processDialog.visible = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const showRec = (row: any) => {
|
|
|
|
|
|
state.machineRecDialog.title = `${row.name}[${row.ip}]-终端回放记录`;
|
|
|
|
|
|
state.machineRecDialog.machineId = row.id;
|
|
|
|
|
|
state.machineRecDialog.visible = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onRemoveTab = (targetName: string) => {
|
|
|
|
|
|
let activeTermName = state.activeTermName;
|
|
|
|
|
|
const tabNames = [...state.tabs.keys()];
|
|
|
|
|
|
for (let i = 0; i < tabNames.length; i++) {
|
|
|
|
|
|
const tabName = tabNames[i];
|
|
|
|
|
|
if (tabName !== targetName) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
const nextTab = tabNames[i + 1] || tabNames[i - 1];
|
|
|
|
|
|
if (nextTab) {
|
|
|
|
|
|
activeTermName = nextTab;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
activeTermName = '';
|
|
|
|
|
|
}
|
2024-02-07 21:14:29 +08:00
|
|
|
|
let info = state.tabs.get(targetName);
|
|
|
|
|
|
if (info) {
|
|
|
|
|
|
terminalRefs[info.key]?.close();
|
|
|
|
|
|
}
|
2024-02-07 06:37:59 +00:00
|
|
|
|
|
|
|
|
|
|
state.tabs.delete(targetName);
|
|
|
|
|
|
state.activeTermName = activeTermName;
|
|
|
|
|
|
onTabChange();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-04-06 04:03:38 +00:00
|
|
|
|
watch(
|
|
|
|
|
|
() => state.activeTermName,
|
|
|
|
|
|
(newValue, oldValue) => {
|
|
|
|
|
|
console.log('oldValue', oldValue);
|
|
|
|
|
|
oldValue && terminalRefs[oldValue]?.blur && terminalRefs[oldValue]?.blur();
|
|
|
|
|
|
terminalRefs[newValue]?.focus && terminalRefs[newValue]?.focus();
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2024-02-07 06:37:59 +00:00
|
|
|
|
const terminalStatusChange = (key: string, status: TerminalStatus) => {
|
|
|
|
|
|
state.tabs.get(key).status = status;
|
|
|
|
|
|
};
|
2024-02-07 21:14:29 +08:00
|
|
|
|
|
2024-02-07 06:37:59 +00:00
|
|
|
|
const terminalRefs: any = {};
|
|
|
|
|
|
const setTerminalRef = (el: any, key: any) => {
|
|
|
|
|
|
if (key) {
|
|
|
|
|
|
terminalRefs[key] = el;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-04-06 04:03:38 +00:00
|
|
|
|
const terminalWrapperRefs: any = {};
|
|
|
|
|
|
const setTerminalWrapperRef = (el: any, key: any) => {
|
|
|
|
|
|
if (key) {
|
|
|
|
|
|
terminalWrapperRefs[key] = el;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-02-07 21:14:29 +08:00
|
|
|
|
const onResizeTagTree = () => {
|
2024-02-07 06:37:59 +00:00
|
|
|
|
fitTerminal();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onTabChange = () => {
|
|
|
|
|
|
fitTerminal();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const fitTerminal = () => {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
let info = state.tabs.get(state.activeTermName);
|
|
|
|
|
|
if (info) {
|
2024-04-06 04:03:38 +00:00
|
|
|
|
terminalRefs[info.key]?.fitTerminal && terminalRefs[info.key]?.fitTerminal();
|
|
|
|
|
|
terminalRefs[info.key]?.focus && terminalRefs[info.key]?.focus();
|
2024-02-07 06:37:59 +00:00
|
|
|
|
}
|
2024-02-07 21:14:29 +08:00
|
|
|
|
}, 100);
|
2024-02-07 06:37:59 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2024-04-06 04:03:38 +00:00
|
|
|
|
const handleReconnect = (tab: any, force = false) => {
|
|
|
|
|
|
let width = terminalWrapperRefs[tab.key].offsetWidth;
|
|
|
|
|
|
let height = terminalWrapperRefs[tab.key].offsetHeight;
|
|
|
|
|
|
terminalRefs[tab.key]?.init(width, height, force);
|
2024-02-07 06:37:59 +00:00
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
|
.machine-terminal-tabs {
|
2024-02-07 21:14:29 +08:00
|
|
|
|
height: calc(100vh - 108px);
|
2024-02-07 06:37:59 +00:00
|
|
|
|
--el-tabs-header-height: 30px;
|
2024-02-07 21:14:29 +08:00
|
|
|
|
|
|
|
|
|
|
.el-tabs {
|
|
|
|
|
|
--el-tabs-header-height: 30px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-07 06:37:59 +00:00
|
|
|
|
.machine-terminal-tab-label {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.el-tabs__header {
|
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.el-tabs__item {
|
2024-02-07 21:14:29 +08:00
|
|
|
|
padding: 0 8px !important;
|
2024-02-07 06:37:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|