255 lines
8.4 KiB
Vue
255 lines
8.4 KiB
Vue
<template>
|
|
<div class="os-devices-page">
|
|
<ElCard shadow="never">
|
|
<template #header>
|
|
<div class="card-header">
|
|
<span>系统添加</span>
|
|
<div class="header-actions">
|
|
<ElButton type="primary" @click="showScanDialog = true">
|
|
<ElIcon><Search /></ElIcon>
|
|
扫描网络
|
|
</ElButton>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- 扫描结果列表 -->
|
|
<div v-if="scanResults.length > 0" class="scan-results">
|
|
<div class="results-header">
|
|
<span>发现 {{ scanResults.length }} 台设备,请选择要添加的设备:</span>
|
|
<div>
|
|
<ElButton size="small" @click="selectAll">全选</ElButton>
|
|
<ElButton size="small" @click="deselectAll">取消全选</ElButton>
|
|
</div>
|
|
</div>
|
|
|
|
<ElTable :data="scanResults" @selection-change="handleSelectionChange" ref="tableRef">
|
|
<ElTableColumn type="selection" width="55" />
|
|
<ElTableColumn prop="ipAddress" label="IP 地址" width="150" />
|
|
<ElTableColumn label="操作系统" width="120">
|
|
<template #default="{ row }">
|
|
<ElTag :type="getOsTagType(row.osType)" size="small">{{ row.osType }}</ElTag>
|
|
</template>
|
|
</ElTableColumn>
|
|
<ElTableColumn prop="hostname" label="主机名" width="150">
|
|
<template #default="{ row }">
|
|
{{ row.hostname || '-' }}
|
|
</template>
|
|
</ElTableColumn>
|
|
<ElTableColumn label="状态" width="100">
|
|
<template #default="{ row }">
|
|
<ElTag :type="row.isOnline ? 'success' : 'danger'" size="small">
|
|
{{ row.isOnline ? '在线' : '离线' }}
|
|
</ElTag>
|
|
</template>
|
|
</ElTableColumn>
|
|
<ElTableColumn prop="discoveredAt" label="发现时间">
|
|
<template #default="{ row }">
|
|
{{ formatTime(row.discoveredAt) }}
|
|
</template>
|
|
</ElTableColumn>
|
|
</ElTable>
|
|
|
|
<div class="results-footer">
|
|
<span>已选择 {{ selectedDevices.length }} 台设备</span>
|
|
<div>
|
|
<ElButton @click="clearResults">取消</ElButton>
|
|
<ElButton type="primary" @click="saveSelectedDevices" :loading="saving" :disabled="selectedDevices.length === 0">
|
|
添加选中设备
|
|
</ElButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 空状态 -->
|
|
<ElEmpty v-else description="点击扫描网络发现局域网内的设备" />
|
|
</ElCard>
|
|
|
|
<!-- 扫描对话框 -->
|
|
<ElDialog v-model="showScanDialog" title="扫描网络" width="500px" :close-on-click-modal="false">
|
|
<ElForm :model="scanForm" label-width="100px">
|
|
<ElFormItem label="网段">
|
|
<ElInput v-model="scanForm.networkSegment" placeholder="例如: 192.168.1.0" />
|
|
</ElFormItem>
|
|
<ElFormItem label="子网掩码">
|
|
<ElSelect v-model="scanForm.subnetMask" style="width: 100%">
|
|
<ElOption label="/24 (255.255.255.0) - 254 台主机" value="/24" />
|
|
<ElOption label="/23 (255.255.254.0) - 510 台主机" value="/23" />
|
|
<ElOption label="/22 (255.255.252.0) - 1022 台主机" value="/22" />
|
|
</ElSelect>
|
|
</ElFormItem>
|
|
</ElForm>
|
|
|
|
<div v-if="scanning" class="scan-progress">
|
|
<ElProgress :percentage="scanProgress.progressPercentage" :format="formatProgress" />
|
|
<p>当前扫描: {{ scanProgress.currentIp || '-' }}</p>
|
|
<p>已发现: {{ scanProgress.foundDevices }} 台设备</p>
|
|
</div>
|
|
|
|
<template #footer>
|
|
<ElButton @click="cancelScan" v-if="scanning">取消扫描</ElButton>
|
|
<ElButton @click="showScanDialog = false" v-else>关闭</ElButton>
|
|
<ElButton type="primary" @click="startScan" :loading="scanning" :disabled="scanning">
|
|
{{ scanning ? '扫描中...' : '开始扫描' }}
|
|
</ElButton>
|
|
</template>
|
|
</ElDialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import { Search } from '@element-plus/icons-vue'
|
|
import { osDeviceApi } from '@/api/amt'
|
|
|
|
defineOptions({ name: 'OsDevices' })
|
|
|
|
const tableRef = ref()
|
|
const scanResults = ref<any[]>([])
|
|
const selectedDevices = ref<any[]>([])
|
|
const saving = ref(false)
|
|
|
|
// 扫描相关
|
|
const showScanDialog = ref(false)
|
|
const scanning = ref(false)
|
|
const scanForm = ref({ networkSegment: '192.168.1.0', subnetMask: '/24' })
|
|
const scanProgress = ref({ scannedCount: 0, totalCount: 0, foundDevices: 0, progressPercentage: 0, currentIp: '' })
|
|
let currentTaskId = ''
|
|
|
|
const getOsTagType = (osType: string) => {
|
|
switch (osType) {
|
|
case 'Windows': return 'primary'
|
|
case 'Linux': return 'success'
|
|
default: return 'info'
|
|
}
|
|
}
|
|
|
|
const formatTime = (time: string) => {
|
|
if (!time) return '-'
|
|
return new Date(time).toLocaleString()
|
|
}
|
|
|
|
const formatProgress = () => {
|
|
return scanProgress.value.scannedCount + '/' + scanProgress.value.totalCount
|
|
}
|
|
|
|
const handleSelectionChange = (selection: any[]) => {
|
|
selectedDevices.value = selection
|
|
}
|
|
|
|
const selectAll = () => {
|
|
tableRef.value?.toggleAllSelection()
|
|
}
|
|
|
|
const deselectAll = () => {
|
|
tableRef.value?.clearSelection()
|
|
}
|
|
|
|
const startScan = async () => {
|
|
scanning.value = true
|
|
scanProgress.value = { scannedCount: 0, totalCount: 0, foundDevices: 0, progressPercentage: 0, currentIp: '' }
|
|
scanResults.value = []
|
|
selectedDevices.value = []
|
|
|
|
try {
|
|
const result = await osDeviceApi.startScan(scanForm.value.networkSegment, scanForm.value.subnetMask)
|
|
currentTaskId = result.taskId
|
|
|
|
// 轮询扫描进度
|
|
const pollProgress = async () => {
|
|
if (!scanning.value) return
|
|
try {
|
|
const progress = await osDeviceApi.getScanStatus(currentTaskId)
|
|
|
|
// -1 表示任务不存在,停止轮询
|
|
if (progress.progressPercentage === -1) {
|
|
scanning.value = false
|
|
ElMessage.warning('扫描任务已失效')
|
|
return
|
|
}
|
|
|
|
scanProgress.value = progress
|
|
|
|
if (progress.progressPercentage < 100) {
|
|
setTimeout(pollProgress, 500)
|
|
} else {
|
|
// 扫描完成,获取结果
|
|
scanning.value = false
|
|
showScanDialog.value = false
|
|
await loadScanResults()
|
|
}
|
|
} catch (error) {
|
|
scanning.value = false
|
|
ElMessage.error('获取扫描进度失败')
|
|
}
|
|
}
|
|
|
|
setTimeout(pollProgress, 500)
|
|
} catch (error) {
|
|
scanning.value = false
|
|
ElMessage.error('启动扫描失败')
|
|
}
|
|
}
|
|
|
|
const cancelScan = async () => {
|
|
try {
|
|
await osDeviceApi.cancelScan(currentTaskId)
|
|
scanning.value = false
|
|
ElMessage.info('扫描已取消')
|
|
} catch (error) {
|
|
ElMessage.error('取消扫描失败')
|
|
}
|
|
}
|
|
|
|
const loadScanResults = async () => {
|
|
try {
|
|
const results = await osDeviceApi.getScanResults(currentTaskId)
|
|
scanResults.value = results
|
|
if (results.length === 0) {
|
|
ElMessage.info('未发现任何设备')
|
|
} else {
|
|
ElMessage.success(`发现 ${results.length} 台设备,请选择要添加的设备`)
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('获取扫描结果失败')
|
|
}
|
|
}
|
|
|
|
const saveSelectedDevices = async () => {
|
|
if (selectedDevices.value.length === 0) {
|
|
ElMessage.warning('请选择要添加的设备')
|
|
return
|
|
}
|
|
|
|
saving.value = true
|
|
try {
|
|
const selectedIps = selectedDevices.value.map(d => d.ipAddress)
|
|
const result = await osDeviceApi.saveSelectedDevices(currentTaskId, selectedIps)
|
|
ElMessage.success(result.message || `成功添加 ${result.savedCount} 台设备`)
|
|
clearResults()
|
|
} catch (error) {
|
|
ElMessage.error('添加设备失败')
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
const clearResults = () => {
|
|
scanResults.value = []
|
|
selectedDevices.value = []
|
|
currentTaskId = ''
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.os-devices-page { padding: 0; }
|
|
.card-header { display: flex; justify-content: space-between; align-items: center; font-size: 16px; font-weight: 500; }
|
|
.header-actions { display: flex; gap: 10px; }
|
|
.scan-results { }
|
|
.results-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
|
|
.results-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; padding-top: 15px; border-top: 1px solid #eee; }
|
|
.scan-progress { margin-top: 20px; text-align: center; }
|
|
.scan-progress p { margin: 10px 0; color: #666; }
|
|
</style>
|