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-02 10:57:32 +08:00 committed by ssongliu
parent d312dc998f
commit c46bf0ec4f
7 changed files with 163 additions and 137 deletions

View File

@ -3,7 +3,7 @@
<transition-group name="breadcrumb" mode="out-in"> <transition-group name="breadcrumb" mode="out-in">
<el-breadcrumb-item :to="{ path: HOME_URL }" key="/home">{{ $t('menu.home') }}</el-breadcrumb-item> <el-breadcrumb-item :to="{ path: HOME_URL }" key="/home">{{ $t('menu.home') }}</el-breadcrumb-item>
<el-breadcrumb-item v-for="item in matched" :key="item.path" :to="{ path: item.path }"> <el-breadcrumb-item v-for="item in matched" :key="item.path" :to="{ path: item.path }">
{{ item.meta?.title }} {{ $t(item.meta?.title as string) }}
</el-breadcrumb-item> </el-breadcrumb-item>
</transition-group> </transition-group>
</el-breadcrumb> </el-breadcrumb>

View File

@ -13,18 +13,20 @@ export default {
}, },
table: { table: {
name: 'Name', name: 'Name',
group: 'Group',
createdAt: 'Creation Time', createdAt: 'Creation Time',
date: 'Date',
updatedAt: 'Update Time', updatedAt: 'Update Time',
operate: 'Operations', operate: 'Operations',
message: 'message', message: 'Message',
description: 'Description',
}, },
msg: { msg: {
delete: 'This operation cannot be rolled back. Do you want to continue', delete: 'This operation cannot be rolled back. Do you want to continue',
title: 'Delete', deleteTitle: 'Delete',
deleteSuccess: 'Delete Success', deleteSuccess: 'Delete Success',
loginSuccess: 'Login Success', loginSuccess: 'Login Success',
requestTimeout: 'The request timed out, please try again later', requestTimeout: 'The request timed out, please try again later',
deleteTitle: 'Delete',
operationSuccess: 'Successful operation', operationSuccess: 'Successful operation',
infoTitle: 'Hint', infoTitle: 'Hint',
sureLogOut: 'Are you sure you want to log out?', sureLogOut: 'Are you sure you want to log out?',
@ -103,6 +105,7 @@ export default {
quickCommand: 'quick command', 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',
quickCmd: 'Quick command',
addHost: 'Add Host', addHost: 'Add Host',
localhost: 'Localhost', localhost: 'Localhost',
name: 'Name', name: 'Name',

View File

@ -67,7 +67,6 @@ export default {
terminal: '终端管理', terminal: '终端管理',
operations: '操作记录', operations: '操作记录',
}, },
home: { home: {
welcome: '欢迎使用', welcome: '欢迎使用',
}, },

View File

@ -1,5 +1,4 @@
import { Layout } from '@/routers/constant'; import { Layout } from '@/routers/constant';
import i18n from '@/lang';
const terminalRouter = { const terminalRouter = {
sort: 2, sort: 2,
@ -7,7 +6,7 @@ const terminalRouter = {
component: Layout, component: Layout,
redirect: '/terminal', redirect: '/terminal',
meta: { meta: {
title: i18n.global.t('menu.terminal'), title: 'menu.terminal',
icon: 'monitor', icon: 'monitor',
}, },
children: [ children: [
@ -18,32 +17,8 @@ const terminalRouter = {
meta: { meta: {
requiresAuth: true, requiresAuth: true,
key: 'Terminal', key: 'Terminal',
title: i18n.global.t('terminal.conn'), title: 'terminal.conn',
icon: 'connection', icon: 'Connection',
activeMenu: '/terminals',
},
},
{
path: '/terminals/host',
name: 'Host',
component: () => import('@/views/terminal/host/index.vue'),
meta: {
requiresAuth: true,
key: 'Host',
title: i18n.global.t('terminal.hostList'),
icon: 'platform',
activeMenu: '/terminals',
},
},
{
path: '/terminals/command',
name: 'Command',
component: () => import('@/views/terminal/command/index.vue'),
meta: {
requiresAuth: true,
key: 'Command',
title: i18n.global.t('terminal.quickCmd'),
icon: 'reading',
activeMenu: '/terminals', activeMenu: '/terminals',
}, },
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div style="margin: 20px; margin-left: 20px">
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search"> <ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
<template #toolbar> <template #toolbar>
<el-button @click="onCreate()">{{ $t('commons.button.create') }}</el-button> <el-button @click="onCreate()">{{ $t('commons.button.create') }}</el-button>
@ -38,7 +38,7 @@
import ComplexTable from '@/components/complex-table/index.vue'; import ComplexTable from '@/components/complex-table/index.vue';
import { Command } from '@/api/interface/command'; import { Command } from '@/api/interface/command';
import { addCommand, editCommand, deleteCommand, getCommandPage } from '@/api/modules/command'; import { addCommand, editCommand, deleteCommand, getCommandPage } from '@/api/modules/command';
import { onMounted, reactive, ref } from '@vue/runtime-core'; import { reactive, ref } from '@vue/runtime-core';
import { useDeleteData } from '@/hooks/use-delete-data'; import { useDeleteData } from '@/hooks/use-delete-data';
import type { ElForm } from 'element-plus'; import type { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rues'; import { Rules } from '@/global/form-rues';
@ -143,7 +143,10 @@ const search = async () => {
paginationConfig.total = res.data.total; paginationConfig.total = res.data.total;
}; };
onMounted(() => { function onInit() {
search(); search();
}
defineExpose({
onInit,
}); });
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-row style="margin: 20px; margin-left: 20px" class="row-box" :gutter="20"> <el-row style="margin-top: 10px; margin-left: 10px" class="row-box" :gutter="20">
<el-col :span="8"> <el-col :span="8">
<el-card class="el-card"> <el-card class="el-card">
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.createConn')" placement="top-start"> <el-tooltip class="box-item" effect="dark" :content="$t('terminal.createConn')" placement="top-start">
@ -129,7 +129,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'; import { ref, reactive } from 'vue';
import type { ElForm } from 'element-plus'; 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';
@ -297,9 +297,12 @@ const onEdit = async (node: Node, data: Tree) => {
} }
}; };
onMounted(() => { function onInit() {
loadHostTree(); loadHostTree();
loadGroups(); loadGroups();
}
defineExpose({
onInit,
}); });
</script> </script>

View File

@ -1,110 +1,135 @@
<template> <template>
<div> <div>
<el-tabs <el-tabs tab-position="left" @tab-click="handleClick" v-model="routeTab" class="demo-tabs">
type="card" <el-tab-pane name="terminal">
class="terminal-tabs"
style="background-color: #efefef"
v-model="terminalValue"
:before-leave="beforeLeave"
@edit="handleTabsRemove"
>
<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"> <el-tooltip class="box-item" effect="dark" :content="$t('terminal.conn')" placement="right">
<el-icon style="margin-top: 1px" color="#67C23A" v-if="item.status === 'online'"> <el-icon><Connection /></el-icon>
<circleCheck /> </el-tooltip>
</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>
</template> </template>
<Terminal <el-tabs
style="height: calc(100vh - 210px); background-color: #000" type="card"
:ref="'Ref' + item.key" class="terminal-tabs"
:wsID="item.wsID" style="background-color: #efefef"
:terminalID="item.key" v-model="terminalValue"
></Terminal> :before-leave="beforeLeave"
<div> @edit="handleTabsRemove"
<el-select >
v-model="quickCmd" <el-tab-pane
clearable :key="item.key"
filterable v-for="item in terminalTabs"
@change="quickInput" :closable="true"
style="width: 25%" :label="item.title"
:placeholder="$t('terminal.quickCommand')" :name="item.key"
size="small"
> >
<el-option <template #label>
v-for="cmd in commandList" <span class="custom-tabs-label">
:key="cmd.id" <el-icon style="margin-top: 1px" color="#67C23A" v-if="item.status === 'online'">
:label="cmd.name + ' [ ' + cmd.command + ' ] '" <circleCheck />
:value="cmd.command" </el-icon>
/> <el-icon style="margin-top: 1px" color="#F56C6C" v-if="item.status === 'closed'">
</el-select> <circleClose />
<el-input </el-icon>
:placeholder="$t('terminal.batchInput')" <span> &nbsp;{{ item.title }}&nbsp;&nbsp;</span>
v-model="batchVal" </span>
@keyup.enter="batchInput"
style="width: 75%"
size="small"
>
<template #append>
<el-switch size="small" v-model="isBatch" class="ml-2" />
</template> </template>
</el-input> <Terminal
</div> style="height: calc(100vh - 210px); background-color: #000"
:ref="'Ref' + item.key"
:wsID="item.wsID"
:terminalID="item.key"
></Terminal>
<div>
<el-select
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-input
:placeholder="$t('terminal.batchInput')"
v-model="batchVal"
@keyup.enter="batchInput"
style="width: 75%"
size="small"
>
<template #append>
<el-switch size="small" v-model="isBatch" class="ml-2" />
</template>
</el-input>
</div>
</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">
<el-empty
style="background-color: #000; height: calc(100vh - 210px)"
:description="$t('terminal.emptyTerminal')"
></el-empty>
</div>
</el-tabs>
<el-button @click="toggleFullscreen" class="fullScreen" icon="FullScreen"></el-button>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :closable="false" name="newTabs"> <el-tab-pane name="host">
<template #label> <template #label>
<el-button <el-tooltip class="box-item" effect="dark" :content="$t('terminal.hostList')" placement="right">
v-popover="popoverRef" <el-icon><Platform /></el-icon>
style="background-color: #ededed; border: 0" </el-tooltip>
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> </template>
<HostTab ref="hostTabRef" />
</el-tab-pane>
<el-tab-pane name="command">
<template #label>
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.quickCmd')" placement="right">
<el-icon><Reading /></el-icon>
</el-tooltip>
</template>
<CommandTab ref="commandTabRef" />
</el-tab-pane> </el-tab-pane>
<div v-if="terminalTabs.length === 0">
<el-empty
style="background-color: #000; height: calc(100vh - 210px)"
:description="$t('terminal.emptyTerminal')"
></el-empty>
</div>
</el-tabs> </el-tabs>
<el-button @click="toggleFullscreen" class="fullScreen" icon="FullScreen"></el-button>
<el-dialog v-model="connVisiable" :title="$t('terminal.addHost')" width="30%"> <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 ref="hostInfoRef" label-width="100px" label-position="left" :model="hostInfo" :rules="rules">
@ -154,16 +179,22 @@ import { Rules } from '@/global/form-rues';
import { testConn, getHostTree, addHost } from '@/api/modules/host'; import { testConn, getHostTree, addHost } from '@/api/modules/host';
import { getCommandList } from '@/api/modules/command'; import { getCommandList } from '@/api/modules/command';
import i18n from '@/lang'; import i18n from '@/lang';
import type { ElForm } from 'element-plus'; import { 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 HostTab from '@/views/terminal/host/index.vue';
import CommandTab from '@/views/terminal/command/index.vue';
import type Node from 'element-plus/es/components/tree/src/model/node'; import type Node from 'element-plus/es/components/tree/src/model/node';
import { ElTree } from 'element-plus'; import { ElTree } from 'element-plus';
import screenfull from 'screenfull'; import screenfull from 'screenfull';
let timer: NodeJS.Timer | null = null; let timer: NodeJS.Timer | null = null;
const routeTab = ref<string>('terminal');
const hostTabRef = ref();
const commandTabRef = ref();
const terminalValue = ref(); const terminalValue = ref();
const terminalTabs = ref([]) as any; const terminalTabs = ref([]) as any;
let tabIndex = 0; let tabIndex = 0;
@ -219,6 +250,18 @@ function toggleFullscreen() {
screenfull.toggle(); screenfull.toggle();
} }
} }
const handleClick = (tab: any) => {
if (tab.paneName === 'host') {
if (ctx) {
ctx.refs[`hostTabRef`] && ctx.refs[`hostTabRef`].onInit();
}
}
if (tab.paneName === 'command') {
if (ctx) {
ctx.refs[`commandTabRef`] && ctx.refs[`commandTabRef`].onInit();
}
}
};
const handleTabsRemove = (targetName: string, action: 'remove' | 'add') => { const handleTabsRemove = (targetName: string, action: 'remove' | 'add') => {
if (action !== 'remove') { if (action !== 'remove') {
@ -417,7 +460,7 @@ onBeforeMount(() => {
.fullScreen { .fullScreen {
position: absolute; position: absolute;
right: 50px; right: 50px;
top: 85px; top: 6px;
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
} }