Files
csm/web/src/views/DeviceDetail.vue
iven fd6fb5cca0 feat: 初始化项目基础架构和核心功能
- 添加项目基础结构:Cargo.toml、.gitignore、设备UID和密钥文件
- 实现前端Vue3项目结构:路由、登录页面、设备管理页面
- 添加核心协议定义(crates/protocol):设备状态、资产、USB事件等
- 实现客户端监控模块:系统状态收集、资产收集
- 实现服务端基础API和插件系统
- 添加数据库迁移脚本:设备管理、资产跟踪、告警系统等
- 实现前端设备状态展示和基本交互
- 添加使用时长统计和水印功能插件
2026-04-05 00:57:51 +08:00

132 lines
5.5 KiB
Vue

<template>
<div class="device-detail" v-loading="loading">
<el-page-header @back="$router.back()" :title="'返回'">
<template #content>
<span>{{ device?.hostname || deviceUid }}</span>
<el-tag v-if="device" :type="device.status === 'online' ? 'success' : 'info'" size="small" style="margin-left: 8px">
{{ device.status === 'online' ? '在线' : '离线' }}
</el-tag>
</template>
</el-page-header>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :span="8">
<el-card shadow="hover">
<template #header><span class="card-title">基本信息</span></template>
<el-descriptions :column="1" size="small" border>
<el-descriptions-item label="设备UID">{{ device?.device_uid }}</el-descriptions-item>
<el-descriptions-item label="主机名">{{ device?.hostname }}</el-descriptions-item>
<el-descriptions-item label="IP地址">{{ device?.ip_address }}</el-descriptions-item>
<el-descriptions-item label="MAC地址">{{ device?.mac_address || '-' }}</el-descriptions-item>
<el-descriptions-item label="操作系统">{{ device?.os_version || '-' }}</el-descriptions-item>
<el-descriptions-item label="客户端版本">{{ device?.client_version || '-' }}</el-descriptions-item>
<el-descriptions-item label="分组">{{ device?.group_name || '-' }}</el-descriptions-item>
<el-descriptions-item label="注册时间">{{ device?.registered_at || '-' }}</el-descriptions-item>
<el-descriptions-item label="最后心跳">{{ device?.last_heartbeat || '-' }}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-col>
<el-col :span="16">
<el-card shadow="hover" style="margin-bottom: 20px">
<template #header><span class="card-title">实时状态</span></template>
<el-row :gutter="20" v-if="status">
<el-col :span="6">
<div class="metric">
<div class="metric-label">CPU</div>
<el-progress type="dashboard" :percentage="Math.round(status.cpu_usage)" :width="100"
:color="progressColor(status.cpu_usage)" />
</div>
</el-col>
<el-col :span="6">
<div class="metric">
<div class="metric-label">内存</div>
<el-progress type="dashboard" :percentage="Math.round(status.memory_usage)" :width="100"
:color="progressColor(status.memory_usage)" />
<div class="metric-sub">{{ formatMB(status.memory_total_mb) }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric">
<div class="metric-label">磁盘</div>
<el-progress type="dashboard" :percentage="Math.round(status.disk_usage)" :width="100"
:color="progressColor(status.disk_usage)" />
<div class="metric-sub">{{ formatMB(status.disk_total_mb) }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric">
<div class="metric-label">进程</div>
<div class="metric-value">{{ status.running_procs }}</div>
</div>
</el-col>
</el-row>
<el-empty v-else description="暂无状态数据" :image-size="60" />
</el-card>
<el-card shadow="hover">
<template #header><span class="card-title">Top 进程</span></template>
<el-table :data="status?.top_processes || []" size="small" max-height="200">
<el-table-column prop="name" label="进程名" />
<el-table-column prop="pid" label="PID" width="80" />
<el-table-column label="CPU" width="120">
<template #default="{ row }">
<el-progress :percentage="Math.min(Math.round(row.cpu_usage), 100)" :stroke-width="6" :color="progressColor(row.cpu_usage)" />
</template>
</el-table-column>
<el-table-column label="内存" width="100">
<template #default="{ row }">{{ formatMB(row.memory_mb) }}</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { api } from '@/lib/api'
const route = useRoute()
const deviceUid = route.params.uid as string
const loading = ref(true)
const device = ref<any>(null)
const status = ref<any>(null)
function progressColor(value: number) {
if (value > 90) return '#F56C6C'
if (value > 70) return '#E6A23C'
return '#67C23A'
}
function formatMB(mb: number) {
if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`
return `${mb} MB`
}
onMounted(async () => {
try {
const [devData, statData] = await Promise.all([
api.get<any>(`/api/devices/${deviceUid}`),
api.get<any>(`/api/devices/${deviceUid}/status`),
])
device.value = devData
status.value = statData
} catch { /* api.ts handles 401 */ } finally {
loading.value = false
}
})
</script>
<style scoped>
.device-detail { padding: 20px; }
.card-title { font-weight: 600; font-size: 15px; }
.metric { text-align: center; padding: 10px 0; }
.metric-label { font-size: 13px; color: #909399; margin-bottom: 8px; }
.metric-value { font-size: 32px; font-weight: 700; color: #303133; margin-top: 16px; }
.metric-sub { font-size: 12px; color: #909399; margin-top: 4px; }
</style>