✨ 支持批量下发指令安装客户端
This commit is contained in:
parent
1cb0313cbd
commit
ebe1057684
@ -6,7 +6,7 @@ import 'element-plus/es/components/notification/style/css'
|
|||||||
// 如果使用 unplugin-element-plus 并且只使用组件 API,需要手动导入样式
|
// 如果使用 unplugin-element-plus 并且只使用组件 API,需要手动导入样式
|
||||||
// https://element-plus.org/zh-CN/guide/quickstart.html#%E6%89%8B%E5%8A%A8%E5%AF%BC%E5%85%A5
|
// https://element-plus.org/zh-CN/guide/quickstart.html#%E6%89%8B%E5%8A%A8%E5%AF%BC%E5%85%A5
|
||||||
export default (app) => {
|
export default (app) => {
|
||||||
app.config.globalProperties.$ELEMENT = { size: 'default' }
|
app.config.globalProperties.$ELEMENT = { size: 'small' }
|
||||||
app.config.globalProperties.$message = ElMessage
|
app.config.globalProperties.$message = ElMessage
|
||||||
app.config.globalProperties.$messageBox = ElMessageBox
|
app.config.globalProperties.$messageBox = ElMessageBox
|
||||||
app.config.globalProperties.$notification = ElNotification
|
app.config.globalProperties.$notification = ElNotification
|
||||||
|
@ -160,13 +160,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, computed, watch, nextTick, getCurrentInstance } from 'vue'
|
import { ref, reactive, onMounted, computed, watch, nextTick, getCurrentInstance, onActivated } from 'vue'
|
||||||
import { ArrowDown } from '@element-plus/icons-vue'
|
import { ArrowDown } from '@element-plus/icons-vue'
|
||||||
import socketIo from 'socket.io-client'
|
import socketIo from 'socket.io-client'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const { io } = socketIo
|
const { io } = socketIo
|
||||||
|
|
||||||
const { proxy: { $api, $notification,$messageBox, $message, $router, $serviceURI, $store } } = getCurrentInstance()
|
const { proxy: { $api, $notification,$messageBox, $message, $router, $serviceURI, $store } } = getCurrentInstance()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const formVisible = ref(false)
|
const formVisible = ref(false)
|
||||||
@ -403,6 +405,24 @@ const handleRemoveAll = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onActivated(async () => {
|
||||||
|
await nextTick()
|
||||||
|
const { host, execClientInstallScript } = route.query
|
||||||
|
if (!host) return
|
||||||
|
if (execClientInstallScript === 'true') {
|
||||||
|
let clientInstallScript = 'curl -o- https://mirror.ghproxy.com/https://raw.githubusercontent.com/chaos-zhu/easynode/main/client/easynode-client-install.sh | bash\n'
|
||||||
|
createExecShell(host.split(','), clientInstallScript, 300)
|
||||||
|
// $messageBox.confirm(`准备安装客户端服务监控应用:${ host }`, 'Warning', {
|
||||||
|
// confirmButtonText: '确定',
|
||||||
|
// cancelButtonText: '取消',
|
||||||
|
// type: 'warning'
|
||||||
|
// })
|
||||||
|
// .then(async () => {
|
||||||
|
// let clientInstallScript = 'curl -o- https://mirror.ghproxy.com/https://raw.githubusercontent.com/chaos-zhu/easynode/main/client/easynode-client-install.sh | bash\n'
|
||||||
|
// createExecShell([host,], clientInstallScript, 300)
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -6,6 +6,42 @@
|
|||||||
row-key="host"
|
row-key="host"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
|
<el-table-column type="expand">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<!-- { monitorData: { connect, cpuInfo, memInfo, driveInfo, ipInfo, netstatInfo } } -->
|
||||||
|
<el-descriptions
|
||||||
|
v-if="row.monitorData?.connect"
|
||||||
|
title="实例信息"
|
||||||
|
:column="5"
|
||||||
|
direction="vertical"
|
||||||
|
>
|
||||||
|
<el-descriptions-item label="CPU" width="35%">
|
||||||
|
{{ `${row.monitorData?.cpuInfo?.cpuModel}-${row.monitorData?.cpuInfo?.cpuCount}-(${row.monitorData?.cpuInfo?.cpuUsage}%)` }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="内存" width="15%">
|
||||||
|
{{ `${$tools.toFixed(row.monitorData?.memInfo?.usedMemMb / 1024)}GB / ${$tools.toFixed(row.monitorData?.memInfo?.totalMemMb / 1024)}GB-(${row.monitorData?.memInfo?.usedMemPercentage}%)` }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="硬盘" width="15%">
|
||||||
|
{{ `${$tools.toFixed(row.monitorData?.driveInfo?.usedGb)}GB / ${$tools.toFixed(row.monitorData?.driveInfo?.totalGb)}GB-(${row.monitorData?.driveInfo?.usedPercentage}%)` }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="网络" width="15%">
|
||||||
|
<el-icon><Upload /></el-icon>
|
||||||
|
{{ `${$tools.formatNetSpeed(row.monitorData?.netstatInfo.total?.outputMb)}` }}
|
||||||
|
<el-icon><Download /></el-icon>
|
||||||
|
{{ `${$tools.formatNetSpeed(row.monitorData?.netstatInfo.total?.inputMb)}` }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="位置" width="20%">
|
||||||
|
{{ row.monitorData?.ipInfo.country || '--' }} {{ row.monitorData?.ipInfo.regionName }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="其他" width="20%">
|
||||||
|
<span @click="handleToConsole(row)">服务商控制台</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<div v-else class="no_client_data">
|
||||||
|
监控客户端未安装,无法获取实时数据。<span class="go_install" @click="handleOnekey(row)">去安装</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column type="selection" reserve-selection />
|
<el-table-column type="selection" reserve-selection />
|
||||||
<el-table-column prop="index" label="序号" width="100px" />
|
<el-table-column prop="index" label="序号" width="100px" />
|
||||||
<el-table-column label="名称">
|
<el-table-column label="名称">
|
||||||
@ -19,7 +55,7 @@
|
|||||||
</el-table-column> -->
|
</el-table-column> -->
|
||||||
<el-table-column property="isConfig" label="监控服务">
|
<el-table-column property="isConfig" label="监控服务">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.connect || scope.row.monitorData?.connect" type="success">已安装</el-tag>
|
<el-tag v-if="scope.row.monitorData?.connect" type="success">已安装</el-tag>
|
||||||
<el-tag v-else type="warning">未安装</el-tag>
|
<el-tag v-else type="warning">未安装</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -43,19 +79,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, getCurrentInstance, nextTick, watch, defineExpose } from 'vue'
|
import { ref, computed, getCurrentInstance, nextTick, defineExpose } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { Download, Upload } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const { proxy: { $api, $router, $tools } } = getCurrentInstance()
|
const { proxy: { $message, $messageBox, $api, $router, $tools } } = getCurrentInstance()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
hosts: {
|
hosts: {
|
||||||
required: true,
|
required: true,
|
||||||
type: Array
|
type: Array
|
||||||
},
|
|
||||||
hiddenIp: {
|
|
||||||
required: true,
|
|
||||||
type: [Number, Boolean,]
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -63,50 +95,38 @@ const emit = defineEmits(['update-list', 'update-host', 'select-change',])
|
|||||||
|
|
||||||
let tableRef = ref(null)
|
let tableRef = ref(null)
|
||||||
|
|
||||||
const hostInfo = computed(() => props.hostInfo || {})
|
let hosts = computed(() => {
|
||||||
// const host = computed(() => hostInfo.value?.host)
|
return props.hosts
|
||||||
const name = computed(() => hostInfo.value?.name)
|
|
||||||
const ping = computed(() => hostInfo.value?.ping || '')
|
|
||||||
const expiredTime = computed(() => $tools.formatTimestamp(hostInfo.value?.expired, 'date'))
|
|
||||||
const consoleUrl = computed(() => hostInfo.value?.consoleUrl)
|
|
||||||
const ipInfo = computed(() => hostInfo.value?.ipInfo || {})
|
|
||||||
const isError = computed(() => !Boolean(hostInfo.value?.osInfo))
|
|
||||||
const cpuInfo = computed(() => hostInfo.value?.cpuInfo || {})
|
|
||||||
const memInfo = computed(() => hostInfo.value?.memInfo || {})
|
|
||||||
const osInfo = computed(() => hostInfo.value?.osInfo || {})
|
|
||||||
const driveInfo = computed(() => hostInfo.value?.driveInfo || {})
|
|
||||||
const netstatInfo = computed(() => {
|
|
||||||
let { total: netTotal, ...netCards } = hostInfo.value?.netstatInfo || {}
|
|
||||||
return { netTotal, netCards: netCards || {} }
|
|
||||||
})
|
})
|
||||||
const openedCount = computed(() => hostInfo.value?.openedCount || 0)
|
|
||||||
|
|
||||||
const setColor = (num) => {
|
|
||||||
num = Number(num)
|
|
||||||
return num ? (num < 80 ? '#595959' : (num >= 80 && num < 90 ? '#FF6600' : '#FF0000')) : '#595959'
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUpdate = (hostInfo) => {
|
const handleUpdate = (hostInfo) => {
|
||||||
emit('update-host', hostInfo)
|
emit('update-host', hostInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleToConsole = () => {
|
const handleToConsole = ({ consoleUrl }) => {
|
||||||
window.open(consoleUrl.value)
|
if (!consoleUrl) return $message({ message: '未配置服务商控制台地址', type: 'warning', center: true })
|
||||||
|
window.open(consoleUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSSH = async ({ host }) => {
|
const handleSSH = async (row) => {
|
||||||
// if (!hostInfo?.isConfig) {
|
let { host } = row
|
||||||
// ElMessage({
|
|
||||||
// message: '请先配置SSH连接信息',
|
|
||||||
// type: 'warning',
|
|
||||||
// center: true
|
|
||||||
// })
|
|
||||||
// handleUpdate()
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
$router.push({ path: '/terminal', query: { host } })
|
$router.push({ path: '/terminal', query: { host } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOnekey = async (row) => {
|
||||||
|
let { host, isConfig } = row
|
||||||
|
if (!isConfig) {
|
||||||
|
$message({
|
||||||
|
message: '请先配置SSH连接信息',
|
||||||
|
type: 'warning',
|
||||||
|
center: true
|
||||||
|
})
|
||||||
|
handleUpdate(row)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$router.push({ path: '/onekey', query: { host, execClientInstallScript: 'true' } })
|
||||||
|
}
|
||||||
|
|
||||||
let selectHosts = ref([])
|
let selectHosts = ref([])
|
||||||
const handleSelectionChange = (val) => {
|
const handleSelectionChange = (val) => {
|
||||||
// console.log('select: ', val)
|
// console.log('select: ', val)
|
||||||
@ -119,7 +139,7 @@ const getSelectHosts = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clearSelection = () => {
|
const clearSelection = () => {
|
||||||
tableRef.value.clearSelection()
|
nextTick(() => tableRef.value.clearSelection())
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
@ -128,18 +148,19 @@ defineExpose({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleRemoveHost = async ({ host }) => {
|
const handleRemoveHost = async ({ host }) => {
|
||||||
ElMessageBox.confirm('确认删除实例', 'Warning', {
|
$messageBox.confirm('确认删除实例', 'Warning', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
let { data } = await $api.removeHost({ host })
|
let { data } = await $api.removeHost({ host })
|
||||||
ElMessage({
|
$message({
|
||||||
message: data,
|
message: data,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
center: true
|
center: true
|
||||||
})
|
})
|
||||||
emit('update-list')
|
emit('update-list')
|
||||||
|
clearSelection()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -154,129 +175,20 @@ const handleRemoveHost = async ({ host }) => {
|
|||||||
// box-shadow: 0px 0px 15px rgba(6, 30, 37, 0.5);
|
// box-shadow: 0px 0px 15px rgba(6, 30, 37, 0.5);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
.host-state {
|
:deep(.el-descriptions__title) {
|
||||||
position: absolute;
|
display: none;
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 10px;
|
|
||||||
// transform: rotate(-45deg);
|
|
||||||
// transform: scale(0.95);
|
|
||||||
display: inline-block;
|
|
||||||
padding: 3px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.online {
|
|
||||||
color: #009933;
|
|
||||||
background-color: #e8fff3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.offline {
|
|
||||||
color: #FF0033;
|
|
||||||
background-color: #fff5f8;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.no_client_data {
|
||||||
display: flex;
|
font-size: 14px;
|
||||||
align-items: center;
|
font-weight: normal;
|
||||||
height: 50px;
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
&>div {
|
color: var(--el-color-warning);;
|
||||||
flex: 1
|
.go_install {
|
||||||
}
|
color: var(--el-color-primary);
|
||||||
|
cursor: pointer;
|
||||||
.field {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
width: 25px;
|
|
||||||
height: 25px;
|
|
||||||
color: #1989fa;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fields {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
// justify-content: center;
|
|
||||||
span {
|
|
||||||
padding: 3px 0;
|
|
||||||
margin-left: 5px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #595959;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
display: inline-block;
|
|
||||||
height: 19px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration-line: underline;
|
|
||||||
text-decoration-color: #1989fa;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
display: none;
|
|
||||||
width: 13px;
|
|
||||||
height: 13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
.actions-icon {
|
|
||||||
margin: 0 10px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
color: #1989fa;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.web-ssh {
|
|
||||||
|
|
||||||
// ::v-deep has been deprecated. Use :deep(<inner-selector>) instead.
|
|
||||||
:deep(.el-dropdown__caret-button) {
|
|
||||||
margin-left: -5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.field-detail {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 0px 0 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
span {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #797979;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
<el-dropdown-item @click="handleBatchSSH">连接终端</el-dropdown-item>
|
<el-dropdown-item @click="handleBatchSSH">连接终端</el-dropdown-item>
|
||||||
<el-dropdown-item @click="handleBatchModify">批量修改</el-dropdown-item>
|
<el-dropdown-item @click="handleBatchModify">批量修改</el-dropdown-item>
|
||||||
<el-dropdown-item @click="handleBatchRemove">批量删除</el-dropdown-item>
|
<el-dropdown-item @click="handleBatchRemove">批量删除</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click="handleBatchOnekey">安装客户端</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
@ -47,7 +48,6 @@
|
|||||||
<HostTable
|
<HostTable
|
||||||
ref="hostTableRefs"
|
ref="hostTableRefs"
|
||||||
:hosts="hosts"
|
:hosts="hosts"
|
||||||
:hidden-ip="hiddenIp"
|
|
||||||
@update-host="handleUpdateHost"
|
@update-host="handleUpdateHost"
|
||||||
@update-list="handleUpdateList"
|
@update-list="handleUpdateList"
|
||||||
/>
|
/>
|
||||||
@ -139,6 +139,7 @@ let handleBatchRemove = async () => {
|
|||||||
$message({ message: data, type: 'success', center: true })
|
$message({ message: data, type: 'success', center: true })
|
||||||
selectHosts.value = []
|
selectHosts.value = []
|
||||||
await handleUpdateList()
|
await handleUpdateList()
|
||||||
|
hostTableRefs.value.forEach(item => item.clearSelection())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +148,13 @@ let handleUpdateHost = (defaultData) => {
|
|||||||
updateHostData.value = defaultData
|
updateHostData.value = defaultData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let handleBatchOnekey = async () => {
|
||||||
|
collectSelectHost()
|
||||||
|
if (!selectHosts.value.length) return $message.warning('请选择要批量操作的实例')
|
||||||
|
let ips = selectHosts.value.map(item => item.host).join(',')
|
||||||
|
$router.push({ path: '/onekey', query: { host: ips, execClientInstallScript: 'true' } })
|
||||||
|
}
|
||||||
|
|
||||||
let handleHiddenIP = () => {
|
let handleHiddenIP = () => {
|
||||||
hiddenIp.value = hiddenIp.value ? 0 : 1
|
hiddenIp.value = hiddenIp.value ? 0 : 1
|
||||||
localStorage.setItem('hiddenIp', String(hiddenIp.value))
|
localStorage.setItem('hiddenIp', String(hiddenIp.value))
|
||||||
|
@ -220,14 +220,14 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update:inputCommandStyle', 'connect-sftp', 'click-input-command',])
|
const emit = defineEmits(['update:inputCommandStyle', 'connect-sftp', 'click-input-command',])
|
||||||
|
|
||||||
const socket = ref(null)
|
const socket = ref(null)
|
||||||
const name = ref('')
|
// const name = ref('')
|
||||||
const ping = ref(0)
|
const ping = ref(0)
|
||||||
const pingTimer = ref(null)
|
const pingTimer = ref(null)
|
||||||
const sftpStatus = ref(false)
|
// const sftpStatus = ref(false)
|
||||||
|
|
||||||
const token = computed(() => $store.token)
|
// const token = computed(() => $store.token)
|
||||||
const hostData = computed(() => props.hostInfo.monitorData || {})
|
const hostData = computed(() => props.hostInfo.monitorData || {})
|
||||||
const host = computed(() => hostData.value.host)
|
const host = computed(() => props.hostInfo.host)
|
||||||
const ipInfo = computed(() => hostData.value?.ipInfo || {})
|
const ipInfo = computed(() => hostData.value?.ipInfo || {})
|
||||||
// const isError = computed(() => !Boolean(hostData.value?.osInfo))
|
// const isError = computed(() => !Boolean(hostData.value?.osInfo))
|
||||||
const cpuInfo = computed(() => hostData.value?.cpuInfo || {})
|
const cpuInfo = computed(() => hostData.value?.cpuInfo || {})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user