兼容移动端UI

This commit is contained in:
chaos-zhu 2024-10-20 16:21:51 +08:00
parent d8f0938a11
commit 6b5f882808
27 changed files with 500 additions and 168 deletions

View File

@ -1,3 +1,12 @@
## [2.2.8](https://github.com/chaos-zhu/easynode/releases) (2024-10-20)
### Features
* 兼容移动端UI
* 调整终端功能菜单
* 修复终端选中文本无法复制的bug
* 修复无法展示服务端ping客户端延迟ms的bug
## [2.2.7](https://github.com/chaos-zhu/easynode/releases) (2024-10-17)
### Features

View File

@ -258,7 +258,7 @@ module.exports = (httpServer) => {
return sftpClient.list('/')
})
.then((rootLs) => {
// 普通文件-、目录文件d、链接文件l
// 普通文件-、目录文件d、链接文件l
socket.emit('root_ls', rootLs) // 先返回根目录
listenInput(sftpClient, socket) // 监听前端请求
})

View File

@ -1,8 +1,11 @@
<html lang="zh">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=0,user-scalable=yes">
<meta name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0">
<!-- <meta name="viewport" content="width=device-width, initial-scale=0,user-scalable=yes"> -->
<title>EasyNode</title>
<script src="//at.alicdn.com/t/c/font_3309550_x7zmcgwaxf.js"></script>
</head>

View File

@ -1,6 +1,6 @@
{
"name": "web",
"version": "2.2.7",
"version": "2.2.8",
"description": "easynode-web",
"private": true,
"scripts": {
@ -45,6 +45,7 @@
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.5",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"code-inspector-plugin": "^0.17.2",
"eslint": "^8.56.0",
"eslint-plugin-vue": "^9.27.0",
"sass": "^1.77.7",

View File

@ -1,4 +1,7 @@
// 滚动条
html {
font-size: 15px;
}
html,
body,
div,

View File

@ -0,0 +1,84 @@
.mobile_menu_btn {
margin-right: auto;
font-size: 18px;
}
.mobile_menu_drawer {
width: auto !important;
.mobile_logo_wrap {
display: flex;
align-items: center;
justify-content: center;
margin-top: 15px;
img {
width: 30px;
}
h1 {
font-size: 14px;
margin-left: 3px;
}
}
.el-drawer__body {
padding: 0;
}
}
@media screen and (min-width: 969px) {
[class^="mobile_"] {
display: none;
}
}
@media screen and (max-width: 968px) {
.view_container {
.aside_container {
display: none;
}
.top_bar_container {
width: 100%;
.bar_wrap {
h2 {
display: none;
}
}
}
.terminal_container {
.terminal_link_tips {
width: 100%;
}
.terminal_wrap {
.terminal_and_sftp_wrap {
flex: auto;
.sftp_tab_container {
section {
.left {
min-width: 150px;
max-width: 150px;
}
.right {
.filter_input {
width: auto;
min-width: auto;
margin: 0 5px;
}
.path {
display: inline-block;
padding-right: 15px;
}
.path_input {
width: auto;
min-width: auto;
}
}
}
}
}
}
}
}
.el-dialog {
--el-dialog-width: 94%!important;
}
}

View File

@ -6,25 +6,7 @@
<h1 v-show="!menuCollapse">EasyNode</h1>
</Transition>
</div>
<el-menu
:default-active="defaultActiveMenu"
:collapse="menuCollapse"
class="menu"
:collapse-transition="true"
@select="handleSelect"
>
<el-menu-item v-for="(item, index) in menuList" :key="index" :index="item.index">
<el-icon>
<component :is="item.icon" />
</el-icon>
<template #title>
<span>{{ item.name }}</span>
</template>
</el-menu-item>
</el-menu>
<!-- <div class="logout_wrap">
<el-button type="info" link @click="handleLogout">退出登录</el-button>
</div> -->
<MenuList />
<div class="collapse" @click="handleCollapse">
<el-icon v-if="menuCollapse"><Expand /></el-icon>
<el-icon v-else><Fold /></el-icon>
@ -33,82 +15,17 @@
</template>
<script setup>
import { reactive, markRaw, getCurrentInstance, computed, watchEffect } from 'vue'
import { getCurrentInstance, computed } from 'vue'
import {
Menu as IconMenu,
Key,
Setting,
ScaleToOriginal,
ArrowRight,
Pointer,
FolderOpened,
Expand,
Fold
} from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'
import MenuList from './menuList.vue'
const route = useRoute()
const { proxy: { $router, $store } } = getCurrentInstance()
let menuList = reactive([
{
name: '实例配置',
icon: markRaw(IconMenu),
index: '/server'
},
{
name: '连接终端',
icon: markRaw(ScaleToOriginal),
index: '/terminal'
},
{
name: '凭据管理',
icon: markRaw(Key),
index: '/credentials'
},
{
name: '分组管理',
icon: markRaw(FolderOpened),
index: '/group'
},
{
name: '脚本库',
icon: markRaw(ArrowRight),
index: '/scripts'
},
{
name: '批量指令',
icon: markRaw(Pointer),
index: '/onekey'
},
{
name: '系统设置',
icon: markRaw(Setting),
index: '/setting'
},
])
const { proxy: { $store } } = getCurrentInstance()
let menuCollapse = computed(() => $store.menuCollapse)
// eslint-disable-next-line no-useless-escape
const regex = /^\/([^\/]+)/
let defaultActiveMenu = computed(() => {
const match = route.path.match(regex)
return match[0]
})
watchEffect(() => {
let idx = route.path.match(regex)[0]
let targetRoute = menuList.find(item => item.index === idx)
$store.setTitle(targetRoute?.name || '')
})
const handleSelect = (path) => {
// console.log(path)
$router.push(path)
}
const handleCollapse = () => {
$store.setMenuCollapse(!menuCollapse.value)
}
@ -136,7 +53,8 @@ const handleCollapse = () => {
position: absolute;
left: 52px;
font-size: 14px;
// color: #1890ff;
color: var(--el-menu-active-color);
font-weight: 600;
}
}
.collapse {

View File

@ -23,7 +23,7 @@
<template #footer>
<footer>
<div class="btns">
<el-button type="primary" @click="handleSave">执行</el-button>
<el-button type="primary" @click="handleSave">发送到终端</el-button>
<el-button type="info" @click="visible = false">关闭</el-button>
</div>
</footer>

View File

@ -0,0 +1,96 @@
<template>
<el-menu
:default-active="defaultActiveMenu"
:collapse="menuCollapse"
class="menu"
:collapse-transition="true"
@select="handleSelect"
>
<el-menu-item v-for="(item, index) in list" :key="index" :index="item.index">
<el-icon>
<component :is="item.icon" />
</el-icon>
<template #title>
<span>{{ item.name }}</span>
</template>
</el-menu-item>
</el-menu>
</template>
<script setup>
import { reactive, markRaw, getCurrentInstance, computed, watchEffect, defineEmits } from 'vue'
import { useRoute } from 'vue-router'
import {
Menu as IconMenu,
Key,
Setting,
ScaleToOriginal,
ArrowRight,
Pointer,
FolderOpened
} from '@element-plus/icons-vue'
const { proxy: { $router, $store } } = getCurrentInstance()
const emit = defineEmits(['select',])
const route = useRoute()
const list = reactive([
{
name: '实例配置',
icon: markRaw(IconMenu),
index: '/server'
},
{
name: '连接终端',
icon: markRaw(ScaleToOriginal),
index: '/terminal'
},
{
name: '凭据管理',
icon: markRaw(Key),
index: '/credentials'
},
{
name: '分组管理',
icon: markRaw(FolderOpened),
index: '/group'
},
{
name: '脚本库',
icon: markRaw(ArrowRight),
index: '/scripts'
},
{
name: '批量指令',
icon: markRaw(Pointer),
index: '/onekey'
},
{
name: '系统设置',
icon: markRaw(Setting),
index: '/setting'
},
])
const menuCollapse = computed(() => $store.menuCollapse)
// eslint-disable-next-line no-useless-escape
const regex = /^\/([^\/]+)/
const defaultActiveMenu = computed(() => {
const match = route.path.match(regex)
return match[0]
})
watchEffect(() => {
const idx = route.path.match(regex)[0]
const targetRoute = list.find(item => item.index === idx)
$store.setTitle(targetRoute?.name || '')
})
const handleSelect = (path) => {
// console.log(path)
$router.push(path)
emit('select', path)
}
</script>

View File

@ -1,8 +1,10 @@
<template>
<div class="top_bar_container">
<div class="bar_wrap">
<div class="mobile_menu_btn">
<el-icon @click="handleCollapse"><Fold /></el-icon>
</div>
<h2>{{ title }}</h2>
<!-- <el-icon><UserFilled /></el-icon> -->
<el-switch
v-model="isDark"
inline-prompt
@ -33,6 +35,7 @@
<el-dialog
v-model="visible"
title="关于"
top="10vh"
width="30%"
:append-to-body="false"
>
@ -55,31 +58,50 @@
</p>
</div>
</el-dialog>
<el-drawer
v-model="menuCollapse"
:with-header="false"
direction="ltr"
class="mobile_menu_drawer"
>
<div class="mobile_logo_wrap">
<img src="@/assets/logo.png" alt="logo">
<h1>EasyNode</h1>
</div>
<MenuList @select="() => menuCollapse = false" />
</el-drawer>
</div>
</template>
<script setup>
import { ref, getCurrentInstance, computed } from 'vue'
import { User, Sunny, Moon } from '@element-plus/icons-vue'
import { User, Sunny, Moon, Fold } from '@element-plus/icons-vue'
import packageJson from '../../package.json'
import MenuList from './menuList.vue'
const { proxy: { $router, $store, $message } } = getCurrentInstance()
let visible = ref(false)
let checkVersionErr = ref(false)
let currentVersion = ref(`v${ packageJson.version }`)
let latestVersion = ref(null)
const visible = ref(false)
const checkVersionErr = ref(false)
const currentVersion = ref(`v${ packageJson.version }`)
const latestVersion = ref(null)
const menuCollapse = ref(false)
let isNew = computed(() => latestVersion.value && latestVersion.value !== currentVersion.value)
let user = computed(() => $store.user)
let title = computed(() => $store.title)
let isDark = computed({
const isNew = computed(() => latestVersion.value && latestVersion.value !== currentVersion.value)
const user = computed(() => $store.user)
const title = computed(() => $store.title)
const isDark = computed({
get: () => $store.isDark,
set: (isDark) => {
$store.setTheme(isDark)
}
})
const handleCollapse = () => {
menuCollapse.value = !menuCollapse.value
}
const handleLogout = () => {
$store.clearJwtToken()
$message({ type: 'success', message: '已安全退出', center: true })

View File

@ -0,0 +1,18 @@
import { ref, onMounted, onUnmounted } from 'vue'
export default function useMobileWidth(maxWidth = 968) {
const isMobileScreen = ref(window.innerWidth < maxWidth)
function updateScreenWidth() {
isMobileScreen.value = window.innerWidth < maxWidth
}
onMounted(() => {
console.log('window.innerWidth: ', window.innerWidth)
console.log('window.innerWidth: ', window.innerWidth)
window.addEventListener('resize', updateScreenWidth)
})
onUnmounted(() => {
window.removeEventListener('resize', updateScreenWidth)
})
return { isMobileScreen }
}

View File

@ -10,6 +10,7 @@ import api from './api'
import App from './app.vue'
import './assets/scss/reset.scss'
import './assets/scss/global.scss'
import './assets/scss/mobile.scss'
const app = createApp(App)
elementPlugins(app)

View File

@ -1,4 +1,3 @@
import { reactive } from 'vue'
import JSRsaEncrypt from 'jsencrypt'
import CryptoJS from 'crypto-js'
@ -93,7 +92,20 @@ export const sortDirTree = (tree = []) => {
}
sort(dirsAndlinks)
sort(others)
return [].concat(dirsAndlinks, others)
let res = [].concat(dirsAndlinks, others)
let homeDirIndex = res.findIndex(item => item.name === 'home')
if (homeDirIndex !== -1) {
let homeDir = res[homeDirIndex]
res.splice(homeDirIndex, 1)
res.unshift(homeDir)
}
let rootDirIndex = res.findIndex(item => item.name === 'root')
if (rootDirIndex !== -1) {
let rootDir = res[rootDirIndex]
res.splice(rootDirIndex, 1)
res.unshift(rootDir)
}
return res
}
export const downloadFile = ({ buffer, name }) => {
@ -132,3 +144,8 @@ export const exportFile = (data, filename, mimeType = 'application/json') =>{
export const isHttps = () => {
return window.location.protocol === 'https:'
}
export const isMobile = () => {
let userAgent = navigator.userAgent || navigator.vendor || window.opera
return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent)
}

View File

@ -10,7 +10,7 @@
{{ row.authType === 'privateKey' ? '密钥' : '密码' }}
</template>
</el-table-column>
<el-table-column label="操作">
<el-table-column width="160px" label="操作">
<template #default="{ row }">
<el-button type="primary" @click="handleChange(row)">修改</el-button>
<el-button v-show="row.id !== 'default'" type="danger" @click="removeSSH(row)">删除</el-button>

View File

@ -4,9 +4,9 @@
<el-button type="primary" @click="addGroup">添加分组</el-button>
</div>
<el-table v-loading="loading" :data="list">
<el-table-column prop="index" label="序号" width="100px" />
<el-table-column prop="index" label="序号" />
<el-table-column prop="name" label="分组名称" />
<el-table-column label="关联实例数量">
<el-table-column label="关联实例数量" min-width="115px">
<template #default="{ row }">
<el-popover
v-if="row.hosts.list.length !== 0"
@ -28,7 +28,7 @@
<u v-else class="host_count">0</u>
</template>
</el-table-column>
<el-table-column label="操作">
<el-table-column label="操作" fixed="right" width="160px">
<template #default="{ row }">
<el-button type="primary" @click="handleChange(row)">修改</el-button>
<el-button v-show="row.id !== 'default'" type="danger" @click="deleteGroup(row)">删除</el-button>

View File

@ -32,26 +32,41 @@
</div>
</template>
</el-table-column>
<el-table-column prop="name" label="实例">
<el-table-column
prop="name"
label="实例"
show-overflow-tooltip
min-width="120px"
>
<template #default="{ row }">
<span style="letter-spacing: 2px;"> {{ row.name }} </span> -
<span style="letter-spacing: 2px;"> {{ row.host }} </span> :
<span style="letter-spacing: 2px;"> {{ row.port }} </span>
</template>
</el-table-column>
<el-table-column prop="command" label="指令" show-overflow-tooltip>
<el-table-column
prop="command"
label="指令"
show-overflow-tooltip
min-width="150px"
>
<template #default="{ row }">
<span> {{ row.command }} </span>
</template>
</el-table-column>
<el-table-column prop="status" label="执行结果" show-overflow-tooltip>
<el-table-column
prop="status"
label="执行结果"
show-overflow-tooltip
min-width="100px"
>
<template #default="{ row }">
<el-tag :color="getStatusType(row.status)">
<span style="color: rgb(54, 52, 52);">{{ row.status }}</span>
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<el-table-column label="操作" fixed="right" width="90px">
<template #default="{ row }">
<el-button
v-if="!row.pending"

View File

@ -8,7 +8,7 @@
<el-table-column prop="name" label="名称" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="command" label="指令内容" show-overflow-tooltip />
<el-table-column label="操作">
<el-table-column label="操作" fixed="right" width="160px">
<template #default="{ row }">
<template v-if="row.index !== '--'">
<el-button type="primary" @click="handleChange(row)">修改</el-button>

View File

@ -41,7 +41,7 @@
</el-descriptions-item>
</el-descriptions>
<div v-else class="no_client_data">
监控客户端服务未连接无法获取实例监控数据<span class="link" @click="handleOnekey(row)">去安装</span>
客户端监控服务未安装或连接失败无法获取实例监控数据<span class="link" @click="handleOnekey(row)">去安装</span>
</div>
</template>
</el-table-column>
@ -79,7 +79,7 @@
</template>
</el-table-column>
<!-- <el-table-column property="isConfig" label="登录配置" /> -->
<el-table-column label="操作" width="300px">
<el-table-column label="操作" fixed="right" width="260px">
<template #default="{ row }">
<el-tooltip
:disabled="row.isConfig"

View File

@ -197,7 +197,7 @@
</el-descriptions-item>
</el-descriptions>
<el-divider content-position="center">FEATURE</el-divider>
<!-- <el-divider content-position="center">FEATURE</el-divider> -->
<!-- <el-button
:type="sftpStatus ? 'primary' : 'success'"
style="display: block;width: 80%;margin: 30px auto;"
@ -205,13 +205,13 @@
>
{{ sftpStatus ? '关闭SFTP' : '连接SFTP' }}
</el-button> -->
<el-button
<!-- <el-button
:type="inputCommandStyle ? 'primary' : 'success'"
style="display: block;width: 80%;margin: 15px auto;"
@click="clickInputCommand"
>
长指令输入
</el-button>
</el-button> -->
</div>
</template>
<script setup>
@ -238,7 +238,7 @@ const props = defineProps({
}
})
const emit = defineEmits(['update:inputCommandStyle', 'connect-sftp', 'click-input-command',])
// const emit = defineEmits(['update:inputCommandStyle', 'click-input-command',])
const socket = ref(null)
const pingTimer = ref(null)
@ -275,12 +275,12 @@ const input = computed(() => {
if (inputMb >= 1) return `${ inputMb.toFixed(2) } MB/s`
return `${ (inputMb * 1024).toFixed(1) } KB/s`
})
const inputCommandStyle = computed({
get: () => props.showInputCommand,
set: (val) => {
emit('update:inputCommandStyle', val)
}
})
// const inputCommandStyle = computed({
// get: () => props.showInputCommand,
// set: (val) => {
// emit('update:inputCommandStyle', val)
// }
// })
const pingMs = computed(() => {
let curPingData = props.pingData[host.value] || {}
@ -288,16 +288,11 @@ const pingMs = computed(() => {
return Number(curPingData?.time).toFixed(0)
})
// const handleSftp = () => {
// sftpStatus.value = !sftpStatus.value
// emit('connect-sftp', sftpStatus.value)
// const clickInputCommand = () => {
// inputCommandStyle.value = true
// emit('click-input-command')
// }
const clickInputCommand = () => {
inputCommandStyle.value = true
emit('click-input-command')
}
const handleCopy = async () => {
await navigator.clipboard.writeText(host.value)
$message.success({ message: 'success', center: true })

View File

@ -92,7 +92,7 @@
</template>
</el-dropdown>
</div>
<div class="filter-input">
<div class="filter_input">
<el-input
v-model="filterKey"
size="small"
@ -104,7 +104,7 @@
v-if="showPathInput"
ref="pathInputRef"
v-model="pathInput"
class="path-input"
class="path_input"
size="small"
clearable
@blur="showPathInput = false"
@ -159,7 +159,7 @@
import { ref, computed, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue'
import socketIo from 'socket.io-client'
import CodeEdit from '@/components/code-edit/index.vue'
import { EventBus, isDir, isFile, sortDirTree, downloadFile } from '@/utils'
import { EventBus, isDir, isFile, sortDirTree, downloadFile, isMobile } from '@/utils'
import dirIcon from '@/assets/image/system/dir.png'
import linkIcon from '@/assets/image/system/link.png'
import fileIcon from '@/assets/image/system/file.png'
@ -408,6 +408,7 @@ const handleClosedCode = () => {
}
const selectFile = (item) => {
if (isMobile()) openTarget(item)
curTarget.value = item
}
@ -696,12 +697,12 @@ defineExpose({
}
}
}
.filter-input {
.filter_input {
width: 200px;
min-width: 200px;
margin: 0 20px 0 10px;
}
.path-input {
.path_input {
width: 450px;
min-width: 450px;
}
@ -730,7 +731,7 @@ defineExpose({
}
li {
font-size: 14px;
padding: 5px 3px;
padding: 5px 0 5px 3px;
display: flex;
align-items: center;
// cursor: pointer;
@ -749,7 +750,7 @@ defineExpose({
}
}
.left {
width: 200px;
min-width: 200px;
border-right: 1px solid #dcdfe6;
.dir-list {
li:nth-child(n+2){

View File

@ -179,7 +179,7 @@ const changeBackground = (url) => {
display: flex;
flex-wrap: wrap;
li {
width: 130px;
width: 126px;
height: 75px;
box-sizing: border-box;
border-radius: 3px;

View File

@ -277,6 +277,7 @@ const onSelectionChange = () => {
term.value.onSelectionChange(() => {
if (!quickCopy.value) return
let str = term.value.getSelection()
console.log(str)
if (!str) return
const text = new Blob([str,], { type: 'text/plain' })
const item = new ClipboardItem({

View File

@ -3,7 +3,7 @@
<div class="terminal_top">
<div class="left_menu">
<el-dropdown trigger="click">
<span class="link_text">连接管理<el-icon><arrow-down /></el-icon></span>
<span class="link_text">连接<el-icon><arrow-down /></el-icon></span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item class="link_close_all" @click="handleCloseAllTab">
@ -47,9 +47,12 @@
</template>
</el-dropdown> -->
<el-dropdown trigger="click">
<span class="link_text">首选<el-icon><arrow-down /></el-icon></span>
<span class="link_text">功能<el-icon><arrow-down /></el-icon></span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="showInputCommand = true">
<span>长指令输入</span>
</el-dropdown-item>
<el-dropdown-item @click="handleFullScreen">
<span>启用全屏</span>
</el-dropdown-item>
@ -61,6 +64,11 @@
</el-dropdown>
</div>
<div class="right_overview">
<div v-if="isMobileScreen" class="switch_wrap">
<el-button :type="curHost?.monitorData?.connect ? 'success' : 'danger'" text @click="() => showMobileInfoSideDialog = true">
状态
</el-button>
</div>
<div class="switch_wrap">
<el-tooltip
effect="dark"
@ -98,17 +106,30 @@
</el-icon> -->
</div>
</div>
<div class="info_box">
<el-drawer
v-if="isMobileScreen"
v-model="showMobileInfoSideDialog"
:with-header="false"
direction="ltr"
class="mobile_menu_drawer"
>
<InfoSide
ref="infoSideRef"
:host-info="curHost"
:visible="visible"
:ping-data="pingData"
/>
</el-drawer>
<div v-else class="info_box">
<InfoSide
ref="infoSideRef"
v-model:show-input-command="showInputCommand"
:host-info="curHost"
:visible="visible"
:ping-data="pingData"
@click-input-command="clickInputCommand"
/>
</div>
<div class="terminals_sftp_wrap">
<div class="terminal_and_sftp_wrap">
<el-tabs
v-model="activeTabIndex"
type="border-card"
@ -162,13 +183,14 @@
<script setup>
import { ref, computed, getCurrentInstance, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import useMobileWidth from '@/composables/useMobileWidth'
import InputCommand from '@/components/input-command/index.vue'
import { terminalStatusList } from '@/utils/enum'
import TerminalTab from './terminal-tab.vue'
import InfoSide from './info-side.vue'
import Sftp from './sftp.vue'
import InputCommand from '@/components/input-command/index.vue'
import HostForm from '../../server/components/host-form.vue'
import TerminalSetting from './terminal-setting.vue'
import { terminalStatusList } from '@/utils/enum'
const { proxy: { $nextTick, $store, $message } } = getCurrentInstance()
@ -180,7 +202,7 @@ const props = defineProps({
})
const emit = defineEmits(['closed', 'close-all-tab', 'removeTab', 'add-host',])
const { isMobileScreen } = useMobileWidth()
const showInputCommand = ref(false)
const infoSideRef = ref(null)
const pingData = ref({})
@ -194,6 +216,7 @@ const isSyncAllSession = ref(false)
const hostFormVisible = ref(false)
const updateHostData = ref(null)
const showSetting = ref(false)
const showMobileInfoSideDialog = ref(false)
const terminalTabs = computed(() => props.terminalTabs)
const terminalTabsLen = computed(() => props.terminalTabs.length)
@ -227,7 +250,7 @@ const handleUpdateList = async ({ host }) => {
const handleResizeTerminalSftp = () => {
$nextTick(() => {
mainHeight.value = document.querySelector('.terminals_sftp_wrap')?.offsetHeight - 45 // 45 is tab-header height+15
mainHeight.value = document.querySelector('.terminal_and_sftp_wrap')?.offsetHeight - 45 // 45 is tab-header height+15
})
}
@ -311,10 +334,6 @@ watch(showSftp, () => {
// }
// }
const clickInputCommand = () => {
showInputCommand.value = true
}
const removeTab = (index) => {
emit('removeTab', index)
if (index === activeTabIndex.value) {
@ -325,7 +344,7 @@ const removeTab = (index) => {
}
const handleFullScreen = () => {
document.getElementsByClassName('terminals_sftp_wrap')[0].requestFullscreen()
document.getElementsByClassName('terminal_and_sftp_wrap')[0].requestFullscreen()
}
// const registryDbClick = () => {
@ -387,7 +406,7 @@ const handleInputCommand = async (command) => {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
padding: 0 5px 0 15px;
position: sticky;
top: 0;
background: var(--el-fill-color-light);
@ -410,7 +429,7 @@ const handleInputCommand = async (command) => {
color: var(--el-text-color-regular);
// color: var(--el-color-primary);
cursor: pointer;
margin-right: 15px;
margin-right: 10px;
.hidden_icon {
opacity: 0;
@ -448,7 +467,7 @@ const handleInputCommand = async (command) => {
border: var(--el-descriptions-table-border);
}
.terminals_sftp_wrap {
.terminal_and_sftp_wrap {
height: calc(100% - $terminalTopHeight);
overflow: hidden;
flex: 1;

View File

@ -10,7 +10,7 @@
}}</span>
</template>
</el-table-column>
<el-table-column v-show="!isAllConfssh">
<el-table-column v-show="!isAllConfssh" fixed="right" width="80px">
<template #default="{ row }">
<div class="actios_btns">
<el-button
@ -124,7 +124,7 @@ onActivated(async () => {
height: calc(100vh - 60px - 20px);
overflow: auto;
.terminal_link_tips {
width: 50%;
width: 735px;
display: flex;
flex-direction: column;
justify-content: center;

View File

@ -1,5 +1,4 @@
import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
@ -7,6 +6,7 @@ import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import viteCompression from 'vite-plugin-compression'
import { codeInspectorPlugin } from 'code-inspector-plugin'
const serviceURI = 'http://localhost:8082/'
const serviceApiPrefix = '/api/v1'
@ -16,7 +16,7 @@ export default defineConfig({
server: {
host: '0.0.0.0',
port: 18090,
strictPort: true,
// strictPort: true,
cors: true,
proxy: {
[serviceApiPrefix]: {
@ -57,12 +57,19 @@ export default defineConfig({
algorithm: 'gzip',
deleteOriginFile: false
}),
codeInspectorPlugin({
bundler: 'vite'
}),
],
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "@/assets/scss/element/index.scss" as *;'
}
},
postcss: {
plugins: [
]
}
},
resolve: {

128
yarn.lock
View File

@ -206,11 +206,21 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d"
integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==
"@babel/helper-string-parser@^7.25.7":
version "7.25.7"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54"
integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db"
integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
"@babel/helper-validator-identifier@^7.25.7":
version "7.25.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5"
integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==
"@babel/helper-validator-option@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d"
@ -244,6 +254,13 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.8.tgz#58a4dbbcad7eb1d48930524a3fd93d93e9084c6f"
integrity sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==
"@babel/parser@^7.25.3":
version "7.25.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2"
integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==
dependencies:
"@babel/types" "^7.25.8"
"@babel/plugin-syntax-jsx@^7.23.3":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d"
@ -318,6 +335,15 @@
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.25.8":
version "7.25.8"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1"
integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==
dependencies:
"@babel/helper-string-parser" "^7.25.7"
"@babel/helper-validator-identifier" "^7.25.7"
to-fast-properties "^2.0.0"
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.3.2", "@codemirror/autocomplete@^6.7.1":
version "6.17.0"
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.17.0.tgz#24ff5fc37fd91f6439df6f4ff9c8e910cde1b053"
@ -1274,6 +1300,17 @@
estree-walker "^2.0.2"
source-map-js "^1.2.0"
"@vue/compiler-core@3.5.12":
version "3.5.12"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.12.tgz#bd70b7dabd12b0b6f31bc53418ba3da77994c437"
integrity sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==
dependencies:
"@babel/parser" "^7.25.3"
"@vue/shared" "3.5.12"
entities "^4.5.0"
estree-walker "^2.0.2"
source-map-js "^1.2.0"
"@vue/compiler-dom@3.4.31":
version "3.4.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz#30961ca847f5d6ad18ffa26236c219f61b195f6b"
@ -1282,6 +1319,14 @@
"@vue/compiler-core" "3.4.31"
"@vue/shared" "3.4.31"
"@vue/compiler-dom@^3.2.47":
version "3.5.12"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz#456d631d11102535b7ee6fd954cf2c93158d0354"
integrity sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==
dependencies:
"@vue/compiler-core" "3.5.12"
"@vue/shared" "3.5.12"
"@vue/compiler-sfc@3.4.31", "@vue/compiler-sfc@^3.4.15":
version "3.4.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz#cc6bfccda17df8268cc5440842277f61623c591f"
@ -1348,6 +1393,11 @@
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.31.tgz#af9981f57def2c3f080c14bf219314fc0dc808a0"
integrity sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==
"@vue/shared@3.5.12":
version "3.5.12"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.12.tgz#f9e45b7f63f2c3f40d84237b1194b7f67de192e3"
integrity sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==
"@vueuse/core@^9.1.0":
version "9.13.0"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.13.0.tgz#2f69e66d1905c1e4eebc249a01759cf88ea00cf4"
@ -1497,6 +1547,13 @@ async-validator@^4.2.5:
resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-4.2.5.tgz#c96ea3332a521699d0afaaceed510a54656c6339"
integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==
async@^2.6.4:
version "2.6.4"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
dependencies:
lodash "^4.17.14"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@ -1666,6 +1723,14 @@ caniuse-lite@^1.0.30001640:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz#6aa6610eb24067c246d30c57f055a9d0a7f8d05f"
integrity sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==
chalk@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@ -1675,7 +1740,7 @@ chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0, chalk@^4.1.2:
chalk@^4.0.0, chalk@^4.1.1, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -1742,6 +1807,26 @@ co@^4.6.0:
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
code-inspector-core@0.17.2:
version "0.17.2"
resolved "https://registry.yarnpkg.com/code-inspector-core/-/code-inspector-core-0.17.2.tgz#4109c21a4582fe86fc1658771b715634d0f1b938"
integrity sha512-3Y46plc5CYO/+p3wLmi3Rx0H5gmFrktRLkxBBVSSH8vG8UhNDCc5xOv/UxBBB0iwCznDu5/VnzGR0Ru4yi5Z6g==
dependencies:
"@vue/compiler-dom" "^3.2.47"
chalk "^4.1.1"
portfinder "^1.0.28"
code-inspector-plugin@^0.17.2:
version "0.17.2"
resolved "https://registry.yarnpkg.com/code-inspector-plugin/-/code-inspector-plugin-0.17.2.tgz#b757c6199e4061e354ae201e645e466ad221d16b"
integrity sha512-dKir/M3uoJVocw05FfBNO3GOBZnwECzvwsiILs4RsYfwQToUp5plCg3HGS83CYgr/1MxzJyr04hiXFheQbbOpw==
dependencies:
chalk "4.1.1"
code-inspector-core "0.17.2"
esbuild-code-inspector-plugin "0.17.2"
vite-code-inspector-plugin "0.17.2"
webpack-code-inspector-plugin "0.17.2"
codemirror@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29"
@ -1956,7 +2041,7 @@ debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug
dependencies:
ms "2.1.2"
debug@^3.1.0:
debug@^3.1.0, debug@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
@ -2175,6 +2260,13 @@ es-errors@^1.3.0:
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
esbuild-code-inspector-plugin@0.17.2:
version "0.17.2"
resolved "https://registry.yarnpkg.com/esbuild-code-inspector-plugin/-/esbuild-code-inspector-plugin-0.17.2.tgz#b36f7354c4736e22462156eab6e65c6d21076751"
integrity sha512-cHk/QYaGSV1E3YgJARC3RUTdnknUSGyfLFtczHXO1FHYBiXKFFD9dr5z43u/ApD5PaE7Eb1f3/eaLlEtD0hdUQ==
dependencies:
code-inspector-core "0.17.2"
esbuild@^0.21.3:
version "0.21.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
@ -3294,7 +3386,7 @@ lodash.once@^4.0.0:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
lodash@^4.17.21:
lodash@^4.17.14, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -3432,6 +3524,13 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@^0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
dependencies:
minimist "^1.2.6"
mlly@^1.4.2, mlly@^1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.1.tgz#e0336429bb0731b6a8e887b438cbdae522c8f32f"
@ -3766,6 +3865,15 @@ pkg@5.8:
resolve "^1.22.0"
stream-meter "^1.0.4"
portfinder@^1.0.28:
version "1.0.32"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81"
integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==
dependencies:
async "^2.6.4"
debug "^3.2.7"
mkdirp "^0.5.6"
possible-typed-array-names@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"
@ -4588,6 +4696,13 @@ vary@^1, vary@^1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
vite-code-inspector-plugin@0.17.2:
version "0.17.2"
resolved "https://registry.yarnpkg.com/vite-code-inspector-plugin/-/vite-code-inspector-plugin-0.17.2.tgz#4d993efed826144fb8fc5df1a754b48498c3a8d0"
integrity sha512-EM4BIY1ulqrmgpbMM+RYi+IRp2SiA1VSE4hrnOw9XRFwr11y73n/An+RW3k29GmdHnSESiF8xKVzm919GMkmDw==
dependencies:
code-inspector-core "0.17.2"
vite-plugin-compression@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz#a75b0d8f48357ebb377b65016da9f20885ef39b6"
@ -4664,6 +4779,13 @@ webidl-conversions@^3.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
webpack-code-inspector-plugin@0.17.2:
version "0.17.2"
resolved "https://registry.yarnpkg.com/webpack-code-inspector-plugin/-/webpack-code-inspector-plugin-0.17.2.tgz#bd6038fbbd8a76e723d03716471827e90486a7ee"
integrity sha512-NGsApY7c87MrIwFo8JzU70w7xRBnDQYOgLtuNQo2iUKTrFHqcgCwejazZv51ub2HuAoOTx86A6VQ/SONXPntTQ==
dependencies:
code-inspector-core "0.17.2"
webpack-sources@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"