feat: 实现OS设备扫描和UUID绑定功能
- 添加OsDevice模型和OsDevicesController - 实现WindowsScannerService用于网络扫描和WMI查询 - 添加AMT设备UUID查询功能(从CIM_ComputerSystemPackage获取PlatformGUID) - 实现PlatformGUID到标准UUID格式的转换(字节序转换) - 修复HardwareInfoRepository保存UUID的问题 - 前端添加OS设备管理页面和UUID获取/刷新按钮 - 添加数据库迁移脚本
This commit is contained in:
parent
c546d4635a
commit
eebbacafde
@ -311,3 +311,83 @@ export const remoteDesktopApi = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 操作系统设备 API
|
||||||
|
export const osDeviceApi = {
|
||||||
|
// 获取所有操作系统设备
|
||||||
|
getAll() {
|
||||||
|
return request.get<any[]>({
|
||||||
|
url: '/api/os-devices'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取单个设备
|
||||||
|
getById(id: number) {
|
||||||
|
return request.get({
|
||||||
|
url: `/api/os-devices/${id}`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 启动操作系统扫描
|
||||||
|
startScan(networkSegment: string, subnetMask: string) {
|
||||||
|
return request.post({
|
||||||
|
url: '/api/os-devices/scan/start',
|
||||||
|
params: { networkSegment, subnetMask }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取扫描状态
|
||||||
|
getScanStatus(taskId: string) {
|
||||||
|
return request.get({
|
||||||
|
url: `/api/os-devices/scan/status/${taskId}`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 取消扫描
|
||||||
|
cancelScan(taskId: string) {
|
||||||
|
return request.post({
|
||||||
|
url: `/api/os-devices/scan/cancel/${taskId}`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取设备详细信息(通过 WMI)
|
||||||
|
fetchInfo(id: number, credentials: { username: string; password: string }) {
|
||||||
|
return request.post({
|
||||||
|
url: `/api/os-devices/${id}/fetch-info`,
|
||||||
|
data: credentials,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 手动绑定 AMT 设备
|
||||||
|
bindAmt(id: number, amtDeviceId: number) {
|
||||||
|
return request.post({
|
||||||
|
url: `/api/os-devices/${id}/bind-amt/${amtDeviceId}`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 解除 AMT 绑定
|
||||||
|
unbindAmt(id: number) {
|
||||||
|
return request.post({
|
||||||
|
url: `/api/os-devices/${id}/unbind-amt`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 自动绑定所有设备
|
||||||
|
autoBind() {
|
||||||
|
return request.post({
|
||||||
|
url: '/api/os-devices/auto-bind',
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除设备
|
||||||
|
delete(id: number) {
|
||||||
|
return request.del({
|
||||||
|
url: `/api/os-devices/${id}`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -28,6 +28,23 @@
|
|||||||
|
|
||||||
<ElTable :data="devices" v-loading="loading" stripe style="width: 100%">
|
<ElTable :data="devices" v-loading="loading" stripe style="width: 100%">
|
||||||
<ElTableColumn prop="ipAddress" label="IP 地址" width="140" />
|
<ElTableColumn prop="ipAddress" label="IP 地址" width="140" />
|
||||||
|
<ElTableColumn prop="systemUuid" label="系统 UUID" width="360">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span v-if="row.systemUuid" style="font-family: monospace; font-size: 12px;">{{ row.systemUuid }}</span>
|
||||||
|
<ElTag v-else type="info" size="small">未获取</ElTag>
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="handleFetchUuid(row)"
|
||||||
|
:loading="row.fetchingUuid"
|
||||||
|
>
|
||||||
|
{{ row.systemUuid ? '刷新' : '获取' }}
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
<ElTableColumn label="AMT 版本" width="100">
|
<ElTableColumn label="AMT 版本" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.majorVersion }}.{{ row.minorVersion }}
|
{{ row.majorVersion }}.{{ row.minorVersion }}
|
||||||
@ -130,7 +147,7 @@
|
|||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Search, Refresh, ArrowDown, VideoPlay, VideoPause, RefreshRight, CircleClose } from '@element-plus/icons-vue'
|
import { Search, Refresh, ArrowDown, VideoPlay, VideoPause, RefreshRight, CircleClose } from '@element-plus/icons-vue'
|
||||||
import { deviceApi, powerApi } from '@/api/amt'
|
import { deviceApi, powerApi, hardwareApi } from '@/api/amt'
|
||||||
import HardwareInfoModal from './modules/hardware-info-modal.vue'
|
import HardwareInfoModal from './modules/hardware-info-modal.vue'
|
||||||
import RemoteDesktopModal from './modules/remote-desktop-modal.vue'
|
import RemoteDesktopModal from './modules/remote-desktop-modal.vue'
|
||||||
|
|
||||||
@ -291,6 +308,24 @@ const saveCredentials = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFetchUuid = async (device: any) => {
|
||||||
|
device.fetchingUuid = true
|
||||||
|
try {
|
||||||
|
// 强制刷新硬件信息以获取 UUID
|
||||||
|
const hardwareInfo = await hardwareApi.getHardwareInfo(device.id, true)
|
||||||
|
if (hardwareInfo.systemInfo?.uuid) {
|
||||||
|
device.systemUuid = hardwareInfo.systemInfo.uuid
|
||||||
|
ElMessage.success('UUID 获取成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('未能从设备获取 UUID')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error('获取 UUID 失败: ' + (error.message || '未知错误'))
|
||||||
|
} finally {
|
||||||
|
device.fetchingUuid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handlePowerCommand = async (command: string, device: any) => {
|
const handlePowerCommand = async (command: string, device: any) => {
|
||||||
const actionMap: Record<string, { api: Function; name: string; confirmMsg: string }> = {
|
const actionMap: Record<string, { api: Function; name: string; confirmMsg: string }> = {
|
||||||
'power-on': { api: powerApi.powerOn, name: '开机', confirmMsg: '确定要开机吗?' },
|
'power-on': { api: powerApi.powerOn, name: '开机', confirmMsg: '确定要开机吗?' },
|
||||||
|
|||||||
@ -29,9 +29,12 @@
|
|||||||
<ElDescriptionsItem label="型号" v-if="hardwareInfo.systemInfo?.model">
|
<ElDescriptionsItem label="型号" v-if="hardwareInfo.systemInfo?.model">
|
||||||
{{ hardwareInfo.systemInfo.model }}
|
{{ hardwareInfo.systemInfo.model }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="序列号" v-if="hardwareInfo.systemInfo?.serialNumber" :span="2">
|
<ElDescriptionsItem label="序列号" v-if="hardwareInfo.systemInfo?.serialNumber">
|
||||||
{{ hardwareInfo.systemInfo.serialNumber }}
|
{{ hardwareInfo.systemInfo.serialNumber }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="系统 UUID" v-if="hardwareInfo.systemInfo?.uuid">
|
||||||
|
{{ hardwareInfo.systemInfo.uuid }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
|
|
||||||
<!-- CPU 信息 -->
|
<!-- CPU 信息 -->
|
||||||
|
|||||||
330
adminSystem/src/views/desktop-manage/os-devices.vue
Normal file
330
adminSystem/src/views/desktop-manage/os-devices.vue
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
<template>
|
||||||
|
<div class="os-devices-page">
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="left">
|
||||||
|
<ElButton type="primary" @click="showScanDialog = true">
|
||||||
|
<ElIcon><Search /></ElIcon>
|
||||||
|
扫描操作系统
|
||||||
|
</ElButton>
|
||||||
|
<ElButton @click="handleAutoBind" :loading="autoBinding">
|
||||||
|
<ElIcon><Link /></ElIcon>
|
||||||
|
自动绑定 AMT
|
||||||
|
</ElButton>
|
||||||
|
<ElButton @click="loadDevices" :loading="loading">
|
||||||
|
<ElIcon><Refresh /></ElIcon>
|
||||||
|
刷新
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<ElInput v-model="searchKeyword" placeholder="搜索 IP/主机名" clearable style="width: 200px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 设备列表 -->
|
||||||
|
<ElTable :data="filteredDevices" v-loading="loading" stripe>
|
||||||
|
<ElTableColumn prop="ipAddress" label="IP 地址" width="140" />
|
||||||
|
<ElTableColumn prop="hostname" label="主机名" width="150" />
|
||||||
|
<ElTableColumn label="操作系统" width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag :type="getOsTagType(row.osType)" size="small">{{ row.osType }}</ElTag>
|
||||||
|
<span v-if="row.osVersion" style="margin-left: 5px; font-size: 12px; color: #999">
|
||||||
|
{{ row.osVersion?.substring(0, 30) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="systemUuid" label="UUID" width="280">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.systemUuid" style="font-family: monospace; font-size: 11px">{{ row.systemUuid }}</span>
|
||||||
|
<ElTag v-else type="warning" size="small">未获取</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="AMT 绑定" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag v-if="row.amtDeviceId" type="success" size="small">
|
||||||
|
{{ row.amtDeviceIp }}
|
||||||
|
</ElTag>
|
||||||
|
<ElTag v-else type="info" size="small">未绑定</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="状态" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag :type="row.isOnline ? 'success' : 'danger'" size="small">
|
||||||
|
{{ row.isOnline ? '在线' : '离线' }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="操作" width="280" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElButton size="small" @click="handleFetchInfo(row)">获取信息</ElButton>
|
||||||
|
<ElButton size="small" @click="handleBindAmt(row)" v-if="!row.amtDeviceId">绑定 AMT</ElButton>
|
||||||
|
<ElButton size="small" type="warning" @click="handleUnbindAmt(row)" v-else>解绑</ElButton>
|
||||||
|
<ElButton size="small" type="danger" @click="handleDelete(row)">删除</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
|
||||||
|
<!-- 扫描对话框 -->
|
||||||
|
<ElDialog v-model="showScanDialog" title="扫描操作系统" width="500px">
|
||||||
|
<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)" value="/24" />
|
||||||
|
<ElOption label="/16 (255.255.0.0)" value="/16" />
|
||||||
|
<ElOption label="/8 (255.0.0.0)" value="/8" />
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
<div v-if="scanning" class="scan-progress">
|
||||||
|
<ElProgress :percentage="scanProgress.progressPercentage" :format="() => `${scanProgress.scannedCount}/${scanProgress.totalCount}`" />
|
||||||
|
<p>当前扫描: {{ scanProgress.currentIp }}</p>
|
||||||
|
<p>已发现: {{ scanProgress.foundDevices }} 台设备</p>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="showScanDialog = false" :disabled="scanning">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="startScan" :loading="scanning">
|
||||||
|
{{ scanning ? '扫描中...' : '开始扫描' }}
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 获取信息对话框 -->
|
||||||
|
<ElDialog v-model="showFetchDialog" title="获取系统信息" width="400px">
|
||||||
|
<ElAlert type="info" :closable="false" style="margin-bottom: 15px">
|
||||||
|
需要提供 Windows 管理员凭据通过 WMI 获取系统信息
|
||||||
|
</ElAlert>
|
||||||
|
<ElForm :model="fetchForm" label-width="80px">
|
||||||
|
<ElFormItem label="用户名">
|
||||||
|
<ElInput v-model="fetchForm.username" placeholder="administrator" />
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="密码">
|
||||||
|
<ElInput v-model="fetchForm.password" type="password" show-password />
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="showFetchDialog = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="doFetchInfo" :loading="fetching">获取</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 绑定 AMT 对话框 -->
|
||||||
|
<ElDialog v-model="showBindDialog" title="绑定 AMT 设备" width="500px">
|
||||||
|
<ElTable :data="amtDevices" v-loading="loadingAmt" @row-click="selectAmtDevice" highlight-current-row>
|
||||||
|
<ElTableColumn prop="ipAddress" label="IP 地址" width="140" />
|
||||||
|
<ElTableColumn prop="hostname" label="主机名" />
|
||||||
|
<ElTableColumn prop="systemUuid" label="UUID" width="280">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.systemUuid" style="font-family: monospace; font-size: 11px">{{ row.systemUuid }}</span>
|
||||||
|
<ElTag v-else type="warning" size="small">未获取</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="showBindDialog = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="doBind" :disabled="!selectedAmtDevice">绑定</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { Search, Refresh, Link } from '@element-plus/icons-vue'
|
||||||
|
import { osDeviceApi, deviceApi } from '@/api/amt'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const devices = ref<any[]>([])
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
const autoBinding = 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 scanTaskId = ''
|
||||||
|
|
||||||
|
// 获取信息相关
|
||||||
|
const showFetchDialog = ref(false)
|
||||||
|
const fetching = ref(false)
|
||||||
|
const fetchForm = ref({ username: '', password: '' })
|
||||||
|
let currentFetchDevice: any = null
|
||||||
|
|
||||||
|
// 绑定相关
|
||||||
|
const showBindDialog = ref(false)
|
||||||
|
const loadingAmt = ref(false)
|
||||||
|
const amtDevices = ref<any[]>([])
|
||||||
|
const selectedAmtDevice = ref<any>(null)
|
||||||
|
let currentBindDevice: any = null
|
||||||
|
|
||||||
|
const filteredDevices = computed(() => {
|
||||||
|
if (!searchKeyword.value) return devices.value
|
||||||
|
const kw = searchKeyword.value.toLowerCase()
|
||||||
|
return devices.value.filter(d =>
|
||||||
|
d.ipAddress?.toLowerCase().includes(kw) ||
|
||||||
|
d.hostname?.toLowerCase().includes(kw)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getOsTagType = (osType: string) => {
|
||||||
|
switch (osType) {
|
||||||
|
case 'Windows': return 'primary'
|
||||||
|
case 'Linux': return 'success'
|
||||||
|
default: return 'info'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadDevices = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
devices.value = await osDeviceApi.getAll()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载设备失败', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startScan = async () => {
|
||||||
|
scanning.value = true
|
||||||
|
scanProgress.value = { scannedCount: 0, totalCount: 0, foundDevices: 0, progressPercentage: 0, currentIp: '' }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await osDeviceApi.startScan(scanForm.value.networkSegment, scanForm.value.subnetMask)
|
||||||
|
scanTaskId = result.taskId
|
||||||
|
|
||||||
|
let retryCount = 0
|
||||||
|
const maxRetries = 3
|
||||||
|
|
||||||
|
// 轮询扫描进度
|
||||||
|
const pollProgress = async () => {
|
||||||
|
if (!scanning.value) return
|
||||||
|
try {
|
||||||
|
const progress = await osDeviceApi.getScanStatus(scanTaskId)
|
||||||
|
retryCount = 0 // 成功后重置重试计数
|
||||||
|
scanProgress.value = progress
|
||||||
|
|
||||||
|
if (progress.progressPercentage < 100) {
|
||||||
|
setTimeout(pollProgress, 500)
|
||||||
|
} else {
|
||||||
|
scanning.value = false
|
||||||
|
showScanDialog.value = false
|
||||||
|
ElMessage.success(`扫描完成,发现 ${progress.foundDevices} 台设备`)
|
||||||
|
loadDevices()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
retryCount++
|
||||||
|
if (retryCount < maxRetries) {
|
||||||
|
// 重试,增加延迟
|
||||||
|
setTimeout(pollProgress, 1000)
|
||||||
|
} else {
|
||||||
|
scanning.value = false
|
||||||
|
ElMessage.error('获取扫描进度失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟500ms后开始轮询,给后端一点时间注册任务
|
||||||
|
setTimeout(pollProgress, 500)
|
||||||
|
} catch (error) {
|
||||||
|
scanning.value = false
|
||||||
|
ElMessage.error('启动扫描失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAutoBind = async () => {
|
||||||
|
autoBinding.value = true
|
||||||
|
try {
|
||||||
|
await osDeviceApi.autoBind()
|
||||||
|
loadDevices()
|
||||||
|
} finally {
|
||||||
|
autoBinding.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFetchInfo = (row: any) => {
|
||||||
|
currentFetchDevice = row
|
||||||
|
fetchForm.value = { username: '', password: '' }
|
||||||
|
showFetchDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const doFetchInfo = async () => {
|
||||||
|
if (!fetchForm.value.username || !fetchForm.value.password) {
|
||||||
|
ElMessage.warning('请输入凭据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fetching.value = true
|
||||||
|
try {
|
||||||
|
await osDeviceApi.fetchInfo(currentFetchDevice.id, fetchForm.value)
|
||||||
|
showFetchDialog.value = false
|
||||||
|
loadDevices()
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error.message || '获取信息失败')
|
||||||
|
} finally {
|
||||||
|
fetching.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBindAmt = async (row: any) => {
|
||||||
|
currentBindDevice = row
|
||||||
|
selectedAmtDevice.value = null
|
||||||
|
showBindDialog.value = true
|
||||||
|
loadingAmt.value = true
|
||||||
|
try {
|
||||||
|
amtDevices.value = await deviceApi.getAllDevices()
|
||||||
|
} finally {
|
||||||
|
loadingAmt.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectAmtDevice = (row: any) => {
|
||||||
|
selectedAmtDevice.value = row
|
||||||
|
}
|
||||||
|
|
||||||
|
const doBind = async () => {
|
||||||
|
if (!selectedAmtDevice.value) return
|
||||||
|
try {
|
||||||
|
await osDeviceApi.bindAmt(currentBindDevice.id, selectedAmtDevice.value.id)
|
||||||
|
showBindDialog.value = false
|
||||||
|
loadDevices()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('绑定失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUnbindAmt = async (row: any) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定要解除 AMT 绑定吗?', '确认')
|
||||||
|
await osDeviceApi.unbindAmt(row.id)
|
||||||
|
loadDevices()
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') ElMessage.error('解绑失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (row: any) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定要删除此设备吗?', '确认删除', { type: 'warning' })
|
||||||
|
await osDeviceApi.delete(row.id)
|
||||||
|
loadDevices()
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadDevices()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.os-devices-page { padding: 20px; }
|
||||||
|
.toolbar { display: flex; justify-content: space-between; margin-bottom: 20px; }
|
||||||
|
.toolbar .left { display: flex; gap: 10px; }
|
||||||
|
.scan-progress { margin-top: 20px; text-align: center; }
|
||||||
|
.scan-progress p { margin: 10px 0; color: #666; }
|
||||||
|
</style>
|
||||||
@ -16,6 +16,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
|
<PackageReference Include="System.Management" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
323
backend-csharp/AmtScanner.Api/Controllers/OsDevicesController.cs
Normal file
323
backend-csharp/AmtScanner.Api/Controllers/OsDevicesController.cs
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
using AmtScanner.Api.Data;
|
||||||
|
using AmtScanner.Api.Models;
|
||||||
|
using AmtScanner.Api.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace AmtScanner.Api.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/os-devices")]
|
||||||
|
public class OsDevicesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
private readonly IWindowsScannerService _scannerService;
|
||||||
|
private readonly ILogger<OsDevicesController> _logger;
|
||||||
|
private static readonly ConcurrentDictionary<string, OsScanProgress> _scanProgress = new();
|
||||||
|
|
||||||
|
public OsDevicesController(
|
||||||
|
AppDbContext context,
|
||||||
|
IWindowsScannerService scannerService,
|
||||||
|
ILogger<OsDevicesController> logger)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_scannerService = scannerService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有操作系统设备
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<ActionResult<ApiResponse<List<OsDeviceDto>>>> GetAll()
|
||||||
|
{
|
||||||
|
var devices = await _context.OsDevices
|
||||||
|
.Include(o => o.AmtDevice)
|
||||||
|
.OrderByDescending(o => o.LastUpdatedAt)
|
||||||
|
.Select(o => new OsDeviceDto
|
||||||
|
{
|
||||||
|
Id = o.Id,
|
||||||
|
IpAddress = o.IpAddress,
|
||||||
|
SystemUuid = o.SystemUuid,
|
||||||
|
Hostname = o.Hostname,
|
||||||
|
OsType = o.OsType.ToString(),
|
||||||
|
OsVersion = o.OsVersion,
|
||||||
|
Architecture = o.Architecture,
|
||||||
|
LoggedInUser = o.LoggedInUser,
|
||||||
|
LastBootTime = o.LastBootTime,
|
||||||
|
MacAddress = o.MacAddress,
|
||||||
|
IsOnline = o.IsOnline,
|
||||||
|
LastOnlineAt = o.LastOnlineAt,
|
||||||
|
DiscoveredAt = o.DiscoveredAt,
|
||||||
|
LastUpdatedAt = o.LastUpdatedAt,
|
||||||
|
Description = o.Description,
|
||||||
|
AmtDeviceId = o.AmtDeviceId,
|
||||||
|
AmtDeviceIp = o.AmtDevice != null ? o.AmtDevice.IpAddress : null
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(ApiResponse<List<OsDeviceDto>>.Success(devices));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单个操作系统设备
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<ActionResult<ApiResponse<OsDeviceDto>>> GetById(long id)
|
||||||
|
{
|
||||||
|
var device = await _context.OsDevices
|
||||||
|
.Include(o => o.AmtDevice)
|
||||||
|
.FirstOrDefaultAsync(o => o.Id == id);
|
||||||
|
|
||||||
|
if (device == null)
|
||||||
|
return Ok(ApiResponse<OsDeviceDto>.Fail(404, "设备不存在"));
|
||||||
|
|
||||||
|
return Ok(ApiResponse<OsDeviceDto>.Success(new OsDeviceDto
|
||||||
|
{
|
||||||
|
Id = device.Id,
|
||||||
|
IpAddress = device.IpAddress,
|
||||||
|
SystemUuid = device.SystemUuid,
|
||||||
|
Hostname = device.Hostname,
|
||||||
|
OsType = device.OsType.ToString(),
|
||||||
|
OsVersion = device.OsVersion,
|
||||||
|
Architecture = device.Architecture,
|
||||||
|
LoggedInUser = device.LoggedInUser,
|
||||||
|
LastBootTime = device.LastBootTime,
|
||||||
|
MacAddress = device.MacAddress,
|
||||||
|
IsOnline = device.IsOnline,
|
||||||
|
LastOnlineAt = device.LastOnlineAt,
|
||||||
|
DiscoveredAt = device.DiscoveredAt,
|
||||||
|
LastUpdatedAt = device.LastUpdatedAt,
|
||||||
|
Description = device.Description,
|
||||||
|
AmtDeviceId = device.AmtDeviceId,
|
||||||
|
AmtDeviceIp = device.AmtDevice?.IpAddress
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动操作系统扫描
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("scan/start")]
|
||||||
|
public async Task<ActionResult<ApiResponse<OsScanStartResponse>>> StartScan(
|
||||||
|
[FromBody] OsScanRequest request)
|
||||||
|
{
|
||||||
|
var taskId = Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
|
var progress = new Progress<OsScanProgress>(p =>
|
||||||
|
{
|
||||||
|
_scanProgress[taskId] = p;
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _scannerService.ScanNetworkAsync(taskId, request.NetworkSegment, request.SubnetMask, progress);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "OS scan failed for task {TaskId}", taskId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(ApiResponse<OsScanStartResponse>.Success(new OsScanStartResponse
|
||||||
|
{
|
||||||
|
TaskId = taskId,
|
||||||
|
Message = "操作系统扫描已启动"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取扫描进度
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("scan/status/{taskId}")]
|
||||||
|
public ActionResult<ApiResponse<OsScanProgress>> GetScanStatus(string taskId)
|
||||||
|
{
|
||||||
|
if (_scanProgress.TryGetValue(taskId, out var progress))
|
||||||
|
{
|
||||||
|
return Ok(ApiResponse<OsScanProgress>.Success(progress));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse<OsScanProgress>.Fail(404, "扫描任务不存在"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 取消扫描
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("scan/cancel/{taskId}")]
|
||||||
|
public ActionResult<ApiResponse<object>> CancelScan(string taskId)
|
||||||
|
{
|
||||||
|
_scannerService.CancelScan(taskId);
|
||||||
|
return Ok(ApiResponse<object>.Success(null, "扫描已取消"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取设备详细信息(通过 WMI)
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("{id}/fetch-info")]
|
||||||
|
public async Task<ActionResult<ApiResponse<OsDeviceDto>>> FetchDeviceInfo(
|
||||||
|
long id,
|
||||||
|
[FromBody] WmiCredentials credentials)
|
||||||
|
{
|
||||||
|
var device = await _context.OsDevices.FindAsync(id);
|
||||||
|
if (device == null)
|
||||||
|
return Ok(ApiResponse<OsDeviceDto>.Fail(404, "设备不存在"));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osInfo = await _scannerService.GetOsInfoAsync(
|
||||||
|
device.IpAddress,
|
||||||
|
credentials.Username,
|
||||||
|
credentials.Password);
|
||||||
|
|
||||||
|
if (osInfo == null)
|
||||||
|
return Ok(ApiResponse<OsDeviceDto>.Fail(500, "无法获取系统信息。可能原因:1) 目标机器WMI服务未启动 2) 防火墙阻止连接 3) 凭据不正确 4) 目标机器不允许远程WMI连接"));
|
||||||
|
|
||||||
|
// 更新设备信息
|
||||||
|
device.SystemUuid = osInfo.SystemUuid;
|
||||||
|
device.Hostname = osInfo.Hostname;
|
||||||
|
device.OsVersion = osInfo.OsVersion;
|
||||||
|
device.Architecture = osInfo.Architecture;
|
||||||
|
device.LoggedInUser = osInfo.LoggedInUser;
|
||||||
|
device.LastBootTime = osInfo.LastBootTime;
|
||||||
|
device.MacAddress = osInfo.MacAddress;
|
||||||
|
device.LastUpdatedAt = DateTime.UtcNow;
|
||||||
|
device.Description = "通过 WMI 获取详细信息";
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// 尝试绑定 AMT 设备
|
||||||
|
await _scannerService.BindAmtDevicesAsync();
|
||||||
|
|
||||||
|
// 重新加载以获取关联的 AMT 设备
|
||||||
|
await _context.Entry(device).Reference(d => d.AmtDevice).LoadAsync();
|
||||||
|
|
||||||
|
return Ok(ApiResponse<OsDeviceDto>.Success(new OsDeviceDto
|
||||||
|
{
|
||||||
|
Id = device.Id,
|
||||||
|
IpAddress = device.IpAddress,
|
||||||
|
SystemUuid = device.SystemUuid,
|
||||||
|
Hostname = device.Hostname,
|
||||||
|
OsType = device.OsType.ToString(),
|
||||||
|
OsVersion = device.OsVersion,
|
||||||
|
Architecture = device.Architecture,
|
||||||
|
LoggedInUser = device.LoggedInUser,
|
||||||
|
LastBootTime = device.LastBootTime,
|
||||||
|
MacAddress = device.MacAddress,
|
||||||
|
IsOnline = device.IsOnline,
|
||||||
|
LastOnlineAt = device.LastOnlineAt,
|
||||||
|
DiscoveredAt = device.DiscoveredAt,
|
||||||
|
LastUpdatedAt = device.LastUpdatedAt,
|
||||||
|
Description = device.Description,
|
||||||
|
AmtDeviceId = device.AmtDeviceId,
|
||||||
|
AmtDeviceIp = device.AmtDevice?.IpAddress
|
||||||
|
}, "系统信息已更新"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取设备 {Id} 系统信息失败", id);
|
||||||
|
return Ok(ApiResponse<OsDeviceDto>.Fail(500, $"获取系统信息失败: {ex.Message}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 手动绑定 AMT 设备
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("{id}/bind-amt/{amtDeviceId}")]
|
||||||
|
public async Task<ActionResult<ApiResponse<object>>> BindAmtDevice(long id, long amtDeviceId)
|
||||||
|
{
|
||||||
|
var osDevice = await _context.OsDevices.FindAsync(id);
|
||||||
|
if (osDevice == null)
|
||||||
|
return Ok(ApiResponse<object>.Fail(404, "操作系统设备不存在"));
|
||||||
|
|
||||||
|
var amtDevice = await _context.AmtDevices.FindAsync(amtDeviceId);
|
||||||
|
if (amtDevice == null)
|
||||||
|
return Ok(ApiResponse<object>.Fail(404, "AMT 设备不存在"));
|
||||||
|
|
||||||
|
osDevice.AmtDeviceId = amtDeviceId;
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(ApiResponse<object>.Success(null, "绑定成功"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解除 AMT 绑定
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("{id}/unbind-amt")]
|
||||||
|
public async Task<ActionResult<ApiResponse<object>>> UnbindAmtDevice(long id)
|
||||||
|
{
|
||||||
|
var osDevice = await _context.OsDevices.FindAsync(id);
|
||||||
|
if (osDevice == null)
|
||||||
|
return Ok(ApiResponse<object>.Fail(404, "设备不存在"));
|
||||||
|
|
||||||
|
osDevice.AmtDeviceId = null;
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(ApiResponse<object>.Success(null, "已解除绑定"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自动绑定所有设备
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("auto-bind")]
|
||||||
|
public async Task<ActionResult<ApiResponse<object>>> AutoBindAll()
|
||||||
|
{
|
||||||
|
await _scannerService.BindAmtDevicesAsync();
|
||||||
|
return Ok(ApiResponse<object>.Success(null, "自动绑定完成"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除设备
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<ActionResult<ApiResponse<object>>> Delete(long id)
|
||||||
|
{
|
||||||
|
var device = await _context.OsDevices.FindAsync(id);
|
||||||
|
if (device == null)
|
||||||
|
return Ok(ApiResponse<object>.Fail(404, "设备不存在"));
|
||||||
|
|
||||||
|
_context.OsDevices.Remove(device);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(ApiResponse<object>.Success(null, "删除成功"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OsDeviceDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string IpAddress { get; set; } = string.Empty;
|
||||||
|
public string? SystemUuid { get; set; }
|
||||||
|
public string? Hostname { get; set; }
|
||||||
|
public string? OsType { get; set; }
|
||||||
|
public string? OsVersion { get; set; }
|
||||||
|
public string? Architecture { get; set; }
|
||||||
|
public string? LoggedInUser { get; set; }
|
||||||
|
public DateTime? LastBootTime { get; set; }
|
||||||
|
public string? MacAddress { get; set; }
|
||||||
|
public bool IsOnline { get; set; }
|
||||||
|
public DateTime? LastOnlineAt { get; set; }
|
||||||
|
public DateTime DiscoveredAt { get; set; }
|
||||||
|
public DateTime LastUpdatedAt { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public long? AmtDeviceId { get; set; }
|
||||||
|
public string? AmtDeviceIp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WmiCredentials
|
||||||
|
{
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OsScanStartResponse
|
||||||
|
{
|
||||||
|
public string TaskId { get; set; } = string.Empty;
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OsScanRequest
|
||||||
|
{
|
||||||
|
public string NetworkSegment { get; set; } = string.Empty;
|
||||||
|
public string SubnetMask { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ public class AppDbContext : DbContext
|
|||||||
public DbSet<StorageDevice> StorageDevices { get; set; }
|
public DbSet<StorageDevice> StorageDevices { get; set; }
|
||||||
public DbSet<WindowsCredential> WindowsCredentials { get; set; }
|
public DbSet<WindowsCredential> WindowsCredentials { get; set; }
|
||||||
public DbSet<RemoteAccessToken> RemoteAccessTokens { get; set; }
|
public DbSet<RemoteAccessToken> RemoteAccessTokens { get; set; }
|
||||||
|
public DbSet<OsDevice> OsDevices { get; set; }
|
||||||
|
|
||||||
// 用户认证相关
|
// 用户认证相关
|
||||||
public DbSet<User> Users { get; set; }
|
public DbSet<User> Users { get; set; }
|
||||||
@ -161,5 +162,35 @@ public class AppDbContext : DbContext
|
|||||||
.WithMany(m => m.RoleMenus)
|
.WithMany(m => m.RoleMenus)
|
||||||
.HasForeignKey(rm => rm.MenuId)
|
.HasForeignKey(rm => rm.MenuId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
// OsDevice 配置
|
||||||
|
modelBuilder.Entity<OsDevice>()
|
||||||
|
.Property(o => o.IpAddress)
|
||||||
|
.HasMaxLength(50);
|
||||||
|
|
||||||
|
modelBuilder.Entity<OsDevice>()
|
||||||
|
.HasIndex(o => o.IpAddress)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
modelBuilder.Entity<OsDevice>()
|
||||||
|
.Property(o => o.SystemUuid)
|
||||||
|
.HasMaxLength(50);
|
||||||
|
|
||||||
|
modelBuilder.Entity<OsDevice>()
|
||||||
|
.HasIndex(o => o.SystemUuid);
|
||||||
|
|
||||||
|
modelBuilder.Entity<OsDevice>()
|
||||||
|
.HasOne(o => o.AmtDevice)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(o => o.AmtDeviceId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
// AmtDevice SystemUuid 索引
|
||||||
|
modelBuilder.Entity<AmtDevice>()
|
||||||
|
.Property(d => d.SystemUuid)
|
||||||
|
.HasMaxLength(50);
|
||||||
|
|
||||||
|
modelBuilder.Entity<AmtDevice>()
|
||||||
|
.HasIndex(d => d.SystemUuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
738
backend-csharp/AmtScanner.Api/Migrations/20260121062236_AddOsDeviceAndSystemUuid.Designer.cs
generated
Normal file
738
backend-csharp/AmtScanner.Api/Migrations/20260121062236_AddOsDeviceAndSystemUuid.Designer.cs
generated
Normal file
@ -0,0 +1,738 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using AmtScanner.Api.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace AmtScanner.Api.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260121062236_AddOsDeviceAndSystemUuid")]
|
||||||
|
partial class AddOsDeviceAndSystemUuid
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.0")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.AmtCredential", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDefault")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.ToTable("AmtCredentials");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.AmtDevice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("AmtOnline")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DiscoveredAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Hostname")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<int>("MajorVersion")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("MinorVersion")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("OsOnline")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<int>("ProvisioningState")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("SystemUuid")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("WindowsPassword")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("WindowsUsername")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IpAddress")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("SystemUuid");
|
||||||
|
|
||||||
|
b.ToTable("AmtDevices");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<long>("DeviceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUpdated")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<int?>("ProcessorCores")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("ProcessorCurrentClockSpeed")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("ProcessorMaxClockSpeed")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ProcessorModel")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("ProcessorThreads")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("SystemManufacturer")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SystemModel")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SystemSerialNumber")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<long?>("TotalMemoryBytes")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId");
|
||||||
|
|
||||||
|
b.HasIndex("LastUpdated");
|
||||||
|
|
||||||
|
b.ToTable("HardwareInfos");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.MemoryModule", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("CapacityBytes")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("HardwareInfoId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Manufacturer")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("MemoryType")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("PartNumber")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SerialNumber")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SlotLocation")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("SpeedMHz")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("HardwareInfoId");
|
||||||
|
|
||||||
|
b.ToTable("MemoryModules");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Menu", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("AuthList")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("varchar(1000)");
|
||||||
|
|
||||||
|
b.Property<string>("Component")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsHide")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsHideTab")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsIframe")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsSystem")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("KeepAlive")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Link")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<int?>("ParentId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Roles")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<int>("Sort")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.HasIndex("ParentId");
|
||||||
|
|
||||||
|
b.ToTable("Menus");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.OsDevice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("AmtDeviceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Architecture")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DiscoveredAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Hostname")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsOnline")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastBootTime")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastOnlineAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("LoggedInUser")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("MacAddress")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("OsType")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("OsVersion")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SystemUuid")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AmtDeviceId");
|
||||||
|
|
||||||
|
b.HasIndex("IpAddress")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("SystemUuid");
|
||||||
|
|
||||||
|
b.ToTable("OsDevices");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<long>("DeviceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsUsed")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<int>("MaxUseCount")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Token")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("varchar(64)");
|
||||||
|
|
||||||
|
b.Property<int>("UseCount")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UsedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId");
|
||||||
|
|
||||||
|
b.HasIndex("Token")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("RemoteAccessTokens");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("RoleCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("RoleName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleCode")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Roles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.RoleMenu", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("MenuId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("RoleId", "MenuId");
|
||||||
|
|
||||||
|
b.HasIndex("MenuId");
|
||||||
|
|
||||||
|
b.ToTable("RoleMenus");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.StorageDevice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("CapacityBytes")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("DeviceId")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<long>("HardwareInfoId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("InterfaceType")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("HardwareInfoId");
|
||||||
|
|
||||||
|
b.ToTable("StorageDevices");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Avatar")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Gender")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1)
|
||||||
|
.HasColumnType("varchar(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("NickName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("varchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RefreshTokenExpiryTime")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1)
|
||||||
|
.HasColumnType("varchar(1)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("UpdatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.WindowsCredential", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Domain")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDefault")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.ToTable("WindowsCredentials");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DeviceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Device");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.MemoryModule", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.HardwareInfo", "HardwareInfo")
|
||||||
|
.WithMany("MemoryModules")
|
||||||
|
.HasForeignKey("HardwareInfoId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("HardwareInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Menu", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.Menu", "Parent")
|
||||||
|
.WithMany("Children")
|
||||||
|
.HasForeignKey("ParentId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("Parent");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.OsDevice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.AmtDevice", "AmtDevice")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AmtDeviceId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("AmtDevice");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DeviceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Device");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.RoleMenu", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.Menu", "Menu")
|
||||||
|
.WithMany("RoleMenus")
|
||||||
|
.HasForeignKey("MenuId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("AmtScanner.Api.Models.Role", "Role")
|
||||||
|
.WithMany("RoleMenus")
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Menu");
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.StorageDevice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.HardwareInfo", "HardwareInfo")
|
||||||
|
.WithMany("StorageDevices")
|
||||||
|
.HasForeignKey("HardwareInfoId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("HardwareInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.Role", "Role")
|
||||||
|
.WithMany("UserRoles")
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("AmtScanner.Api.Models.User", "User")
|
||||||
|
.WithMany("UserRoles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("MemoryModules");
|
||||||
|
|
||||||
|
b.Navigation("StorageDevices");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Menu", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Children");
|
||||||
|
|
||||||
|
b.Navigation("RoleMenus");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("RoleMenus");
|
||||||
|
|
||||||
|
b.Navigation("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("UserRoles");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace AmtScanner.Api.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddOsDeviceAndSystemUuid : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "SystemUuid",
|
||||||
|
table: "AmtDevices",
|
||||||
|
type: "varchar(50)",
|
||||||
|
maxLength: 50,
|
||||||
|
nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "OsDevices",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
IpAddress = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
SystemUuid = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Hostname = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
OsType = table.Column<int>(type: "int", nullable: false),
|
||||||
|
OsVersion = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Architecture = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
LoggedInUser = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
LastBootTime = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||||
|
MacAddress = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
IsOnline = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
LastOnlineAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||||
|
DiscoveredAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
LastUpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
Description = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
AmtDeviceId = table.Column<long>(type: "bigint", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_OsDevices", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_OsDevices_AmtDevices_AmtDeviceId",
|
||||||
|
column: x => x.AmtDeviceId,
|
||||||
|
principalTable: "AmtDevices",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AmtDevices_SystemUuid",
|
||||||
|
table: "AmtDevices",
|
||||||
|
column: "SystemUuid");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OsDevices_AmtDeviceId",
|
||||||
|
table: "OsDevices",
|
||||||
|
column: "AmtDeviceId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OsDevices_IpAddress",
|
||||||
|
table: "OsDevices",
|
||||||
|
column: "IpAddress",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OsDevices_SystemUuid",
|
||||||
|
table: "OsDevices",
|
||||||
|
column: "SystemUuid");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "OsDevices");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_AmtDevices_SystemUuid",
|
||||||
|
table: "AmtDevices");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "SystemUuid",
|
||||||
|
table: "AmtDevices");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
741
backend-csharp/AmtScanner.Api/Migrations/20260121071149_AddSystemUuidToHardwareInfo.Designer.cs
generated
Normal file
741
backend-csharp/AmtScanner.Api/Migrations/20260121071149_AddSystemUuidToHardwareInfo.Designer.cs
generated
Normal file
@ -0,0 +1,741 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using AmtScanner.Api.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace AmtScanner.Api.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260121071149_AddSystemUuidToHardwareInfo")]
|
||||||
|
partial class AddSystemUuidToHardwareInfo
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.0")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.AmtCredential", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDefault")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.ToTable("AmtCredentials");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.AmtDevice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("AmtOnline")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DiscoveredAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Hostname")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<int>("MajorVersion")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("MinorVersion")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("OsOnline")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<int>("ProvisioningState")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("SystemUuid")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("WindowsPassword")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("WindowsUsername")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IpAddress")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("SystemUuid");
|
||||||
|
|
||||||
|
b.ToTable("AmtDevices");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<long>("DeviceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUpdated")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<int?>("ProcessorCores")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("ProcessorCurrentClockSpeed")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("ProcessorMaxClockSpeed")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ProcessorModel")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("ProcessorThreads")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("SystemManufacturer")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SystemModel")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SystemSerialNumber")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SystemUuid")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<long?>("TotalMemoryBytes")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId");
|
||||||
|
|
||||||
|
b.HasIndex("LastUpdated");
|
||||||
|
|
||||||
|
b.ToTable("HardwareInfos");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.MemoryModule", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("CapacityBytes")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("HardwareInfoId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Manufacturer")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("MemoryType")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("PartNumber")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SerialNumber")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SlotLocation")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("SpeedMHz")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("HardwareInfoId");
|
||||||
|
|
||||||
|
b.ToTable("MemoryModules");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Menu", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("AuthList")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("varchar(1000)");
|
||||||
|
|
||||||
|
b.Property<string>("Component")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsHide")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsHideTab")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsIframe")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsSystem")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("KeepAlive")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Link")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<int?>("ParentId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Roles")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<int>("Sort")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.HasIndex("ParentId");
|
||||||
|
|
||||||
|
b.ToTable("Menus");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.OsDevice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("AmtDeviceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Architecture")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DiscoveredAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Hostname")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsOnline")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastBootTime")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastOnlineAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("LoggedInUser")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("MacAddress")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("OsType")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("OsVersion")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SystemUuid")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AmtDeviceId");
|
||||||
|
|
||||||
|
b.HasIndex("IpAddress")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("SystemUuid");
|
||||||
|
|
||||||
|
b.ToTable("OsDevices");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<long>("DeviceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsUsed")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<int>("MaxUseCount")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Token")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("varchar(64)");
|
||||||
|
|
||||||
|
b.Property<int>("UseCount")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UsedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId");
|
||||||
|
|
||||||
|
b.HasIndex("Token")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("RemoteAccessTokens");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("RoleCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("RoleName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleCode")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Roles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.RoleMenu", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("MenuId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("RoleId", "MenuId");
|
||||||
|
|
||||||
|
b.HasIndex("MenuId");
|
||||||
|
|
||||||
|
b.ToTable("RoleMenus");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.StorageDevice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("CapacityBytes")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("DeviceId")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<long>("HardwareInfoId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("InterfaceType")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Model")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("HardwareInfoId");
|
||||||
|
|
||||||
|
b.ToTable("StorageDevices");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Avatar")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Gender")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1)
|
||||||
|
.HasColumnType("varchar(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("NickName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("varchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RefreshTokenExpiryTime")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1)
|
||||||
|
.HasColumnType("varchar(1)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("UpdatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.WindowsCredential", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Domain")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDefault")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("varchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.ToTable("WindowsCredentials");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DeviceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Device");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.MemoryModule", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.HardwareInfo", "HardwareInfo")
|
||||||
|
.WithMany("MemoryModules")
|
||||||
|
.HasForeignKey("HardwareInfoId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("HardwareInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Menu", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.Menu", "Parent")
|
||||||
|
.WithMany("Children")
|
||||||
|
.HasForeignKey("ParentId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("Parent");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.OsDevice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.AmtDevice", "AmtDevice")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AmtDeviceId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("AmtDevice");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DeviceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Device");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.RoleMenu", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.Menu", "Menu")
|
||||||
|
.WithMany("RoleMenus")
|
||||||
|
.HasForeignKey("MenuId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("AmtScanner.Api.Models.Role", "Role")
|
||||||
|
.WithMany("RoleMenus")
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Menu");
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.StorageDevice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.HardwareInfo", "HardwareInfo")
|
||||||
|
.WithMany("StorageDevices")
|
||||||
|
.HasForeignKey("HardwareInfoId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("HardwareInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.Role", "Role")
|
||||||
|
.WithMany("UserRoles")
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("AmtScanner.Api.Models.User", "User")
|
||||||
|
.WithMany("UserRoles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("MemoryModules");
|
||||||
|
|
||||||
|
b.Navigation("StorageDevices");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Menu", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Children");
|
||||||
|
|
||||||
|
b.Navigation("RoleMenus");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("RoleMenus");
|
||||||
|
|
||||||
|
b.Navigation("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("UserRoles");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace AmtScanner.Api.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddSystemUuidToHardwareInfo : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "SystemUuid",
|
||||||
|
table: "HardwareInfos",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "SystemUuid",
|
||||||
|
table: "HardwareInfos");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -95,6 +95,10 @@ namespace AmtScanner.Api.Migrations
|
|||||||
b.Property<int>("ProvisioningState")
|
b.Property<int>("ProvisioningState")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("SystemUuid")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
b.Property<string>("WindowsPassword")
|
b.Property<string>("WindowsPassword")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
@ -106,6 +110,8 @@ namespace AmtScanner.Api.Migrations
|
|||||||
b.HasIndex("IpAddress")
|
b.HasIndex("IpAddress")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("SystemUuid");
|
||||||
|
|
||||||
b.ToTable("AmtDevices");
|
b.ToTable("AmtDevices");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -148,6 +154,9 @@ namespace AmtScanner.Api.Migrations
|
|||||||
b.Property<string>("SystemSerialNumber")
|
b.Property<string>("SystemSerialNumber")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SystemUuid")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<long?>("TotalMemoryBytes")
|
b.Property<long?>("TotalMemoryBytes")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
@ -273,6 +282,72 @@ namespace AmtScanner.Api.Migrations
|
|||||||
b.ToTable("Menus");
|
b.ToTable("Menus");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.OsDevice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("AmtDeviceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Architecture")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DiscoveredAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Hostname")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsOnline")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastBootTime")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastOnlineAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("LoggedInUser")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("MacAddress")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("OsType")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("OsVersion")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("SystemUuid")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AmtDeviceId");
|
||||||
|
|
||||||
|
b.HasIndex("IpAddress")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("SystemUuid");
|
||||||
|
|
||||||
|
b.ToTable("OsDevices");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b =>
|
modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<long>("Id")
|
||||||
@ -562,6 +637,16 @@ namespace AmtScanner.Api.Migrations
|
|||||||
b.Navigation("Parent");
|
b.Navigation("Parent");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("AmtScanner.Api.Models.OsDevice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("AmtScanner.Api.Models.AmtDevice", "AmtDevice")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AmtDeviceId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("AmtDevice");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b =>
|
modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device")
|
b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device")
|
||||||
|
|||||||
@ -12,6 +12,11 @@ public class AmtDevice
|
|||||||
|
|
||||||
public string? Hostname { get; set; }
|
public string? Hostname { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 系统 UUID(SMBIOS UUID,用于与操作系统绑定)
|
||||||
|
/// </summary>
|
||||||
|
public string? SystemUuid { get; set; }
|
||||||
|
|
||||||
public int MajorVersion { get; set; }
|
public int MajorVersion { get; set; }
|
||||||
|
|
||||||
public int MinorVersion { get; set; }
|
public int MinorVersion { get; set; }
|
||||||
|
|||||||
@ -19,6 +19,11 @@ public class HardwareInfo
|
|||||||
public string? SystemModel { get; set; }
|
public string? SystemModel { get; set; }
|
||||||
public string? SystemSerialNumber { get; set; }
|
public string? SystemSerialNumber { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 系统 UUID(SMBIOS UUID,用于与操作系统绑定)
|
||||||
|
/// </summary>
|
||||||
|
public string? SystemUuid { get; set; }
|
||||||
|
|
||||||
// Processor Information
|
// Processor Information
|
||||||
public string? ProcessorModel { get; set; }
|
public string? ProcessorModel { get; set; }
|
||||||
public int? ProcessorCores { get; set; }
|
public int? ProcessorCores { get; set; }
|
||||||
|
|||||||
@ -16,6 +16,7 @@ public class SystemInfoDto
|
|||||||
public string? Manufacturer { get; set; }
|
public string? Manufacturer { get; set; }
|
||||||
public string? Model { get; set; }
|
public string? Model { get; set; }
|
||||||
public string? SerialNumber { get; set; }
|
public string? SerialNumber { get; set; }
|
||||||
|
public string? Uuid { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ProcessorInfoDto
|
public class ProcessorInfoDto
|
||||||
|
|||||||
103
backend-csharp/AmtScanner.Api/Models/OsDevice.cs
Normal file
103
backend-csharp/AmtScanner.Api/Models/OsDevice.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace AmtScanner.Api.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作系统设备实体
|
||||||
|
/// </summary>
|
||||||
|
public class OsDevice
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IP 地址
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string IpAddress { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 系统 UUID(SMBIOS UUID,用于与 AMT 绑定)
|
||||||
|
/// </summary>
|
||||||
|
public string? SystemUuid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主机名
|
||||||
|
/// </summary>
|
||||||
|
public string? Hostname { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作系统类型
|
||||||
|
/// </summary>
|
||||||
|
public OsType OsType { get; set; } = OsType.Unknown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作系统版本
|
||||||
|
/// </summary>
|
||||||
|
public string? OsVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作系统架构
|
||||||
|
/// </summary>
|
||||||
|
public string? Architecture { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前登录用户
|
||||||
|
/// </summary>
|
||||||
|
public string? LoggedInUser { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 系统最后启动时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? LastBootTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MAC 地址
|
||||||
|
/// </summary>
|
||||||
|
public string? MacAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否在线
|
||||||
|
/// </summary>
|
||||||
|
public bool IsOnline { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最后在线时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? LastOnlineAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发现时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime DiscoveredAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最后更新时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastUpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关联的 AMT 设备 ID
|
||||||
|
/// </summary>
|
||||||
|
public long? AmtDeviceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关联的 AMT 设备
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(AmtDeviceId))]
|
||||||
|
public AmtDevice? AmtDevice { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum OsType
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Windows = 1,
|
||||||
|
Linux = 2,
|
||||||
|
MacOS = 3
|
||||||
|
}
|
||||||
@ -60,6 +60,7 @@ builder.Services.AddScoped<IAmtHardwareQueryService, AmtHardwareQueryService>();
|
|||||||
builder.Services.AddScoped<IHardwareInfoService, HardwareInfoService>();
|
builder.Services.AddScoped<IHardwareInfoService, HardwareInfoService>();
|
||||||
builder.Services.AddScoped<IHardwareInfoRepository, HardwareInfoRepository>();
|
builder.Services.AddScoped<IHardwareInfoRepository, HardwareInfoRepository>();
|
||||||
builder.Services.AddScoped<IAmtPowerService, AmtPowerService>();
|
builder.Services.AddScoped<IAmtPowerService, AmtPowerService>();
|
||||||
|
builder.Services.AddScoped<IWindowsScannerService, WindowsScannerService>();
|
||||||
builder.Services.AddHttpClient<IGuacamoleService, GuacamoleService>();
|
builder.Services.AddHttpClient<IGuacamoleService, GuacamoleService>();
|
||||||
|
|
||||||
// Add JWT Configuration
|
// Add JWT Configuration
|
||||||
|
|||||||
@ -44,6 +44,7 @@ public class HardwareInfoRepository : IHardwareInfoRepository
|
|||||||
existing.SystemManufacturer = hardwareInfo.SystemManufacturer;
|
existing.SystemManufacturer = hardwareInfo.SystemManufacturer;
|
||||||
existing.SystemModel = hardwareInfo.SystemModel;
|
existing.SystemModel = hardwareInfo.SystemModel;
|
||||||
existing.SystemSerialNumber = hardwareInfo.SystemSerialNumber;
|
existing.SystemSerialNumber = hardwareInfo.SystemSerialNumber;
|
||||||
|
existing.SystemUuid = hardwareInfo.SystemUuid; // 保存 UUID
|
||||||
existing.ProcessorModel = hardwareInfo.ProcessorModel;
|
existing.ProcessorModel = hardwareInfo.ProcessorModel;
|
||||||
existing.ProcessorCores = hardwareInfo.ProcessorCores;
|
existing.ProcessorCores = hardwareInfo.ProcessorCores;
|
||||||
existing.ProcessorThreads = hardwareInfo.ProcessorThreads;
|
existing.ProcessorThreads = hardwareInfo.ProcessorThreads;
|
||||||
|
|||||||
@ -152,12 +152,168 @@ public class AmtHardwareQueryService : IAmtHardwareQueryService
|
|||||||
|
|
||||||
_logger.LogDebug("System info: {Manufacturer} {Model}",
|
_logger.LogDebug("System info: {Manufacturer} {Model}",
|
||||||
hardwareInfo.SystemManufacturer, hardwareInfo.SystemModel);
|
hardwareInfo.SystemManufacturer, hardwareInfo.SystemModel);
|
||||||
|
|
||||||
|
// Query UUID from CIM_ComputerSystemPackage (在同一个 try 块内查询,确保连接有效)
|
||||||
|
QuerySystemUuid(connection, hardwareInfo);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(ex, "Failed to query system information");
|
_logger.LogWarning(ex, "Failed to query system information");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void QuerySystemUuid(IWsmanConnection connection, HardwareInfo hardwareInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Querying system UUID");
|
||||||
|
|
||||||
|
// 通过 CIM_ComputerSystemPackage 获取 PlatformGUID (UUID)
|
||||||
|
var query = connection.ExecQuery("SELECT * FROM CIM_ComputerSystemPackage");
|
||||||
|
|
||||||
|
foreach (IWsmanItem item in query)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 尝试获取 PlatformGUID
|
||||||
|
var platformGuid = item.Object.GetProperty("PlatformGUID");
|
||||||
|
_logger.LogInformation("PlatformGUID IsNull: {IsNull}, Value: {Value}",
|
||||||
|
platformGuid.IsNull, platformGuid.IsNull ? "null" : platformGuid.ToString());
|
||||||
|
if (!platformGuid.IsNull && !string.IsNullOrWhiteSpace(platformGuid.ToString()))
|
||||||
|
{
|
||||||
|
var rawGuid = platformGuid.ToString().Trim();
|
||||||
|
// 将 PlatformGUID 转换为标准 UUID 格式
|
||||||
|
var formattedUuid = FormatPlatformGuidToUuid(rawGuid);
|
||||||
|
hardwareInfo.SystemUuid = formattedUuid;
|
||||||
|
_logger.LogInformation("Found UUID from CIM_ComputerSystemPackage.PlatformGUID: Raw={Raw}, Formatted={Formatted}",
|
||||||
|
rawGuid, formattedUuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(ex, "PlatformGUID not available from CIM_ComputerSystemPackage");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 备选:尝试从 Antecedent (CIM_Chassis) 获取 UUID
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var antecedent = item.Object.GetProperty("Antecedent");
|
||||||
|
_logger.LogInformation("Antecedent IsNull: {IsNull}, IsA CIM_Chassis: {IsChassis}",
|
||||||
|
antecedent.IsNull, !antecedent.IsNull && antecedent.IsA("CIM_Chassis"));
|
||||||
|
if (!antecedent.IsNull && antecedent.IsA("CIM_Chassis"))
|
||||||
|
{
|
||||||
|
var chassisObj = antecedent.Ref.Get();
|
||||||
|
|
||||||
|
var uuid = chassisObj.GetProperty("UUID");
|
||||||
|
_logger.LogInformation("CIM_Chassis UUID IsNull: {IsNull}, Value: {Value}",
|
||||||
|
uuid.IsNull, uuid.IsNull ? "null" : uuid.ToString());
|
||||||
|
if (!uuid.IsNull && !string.IsNullOrWhiteSpace(uuid.ToString()))
|
||||||
|
{
|
||||||
|
hardwareInfo.SystemUuid = uuid.ToString().Trim();
|
||||||
|
_logger.LogInformation("Found UUID from CIM_Chassis: {Uuid}", hardwareInfo.SystemUuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(ex, "UUID not available from CIM_Chassis");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 备选方案:尝试从 CIM_PhysicalPackage 获取
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Trying CIM_PhysicalPackage for UUID");
|
||||||
|
var physicalQuery = connection.ExecQuery("SELECT * FROM CIM_PhysicalPackage");
|
||||||
|
foreach (IWsmanItem item in physicalQuery)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var uuid = item.Object.GetProperty("UUID");
|
||||||
|
_logger.LogInformation("CIM_PhysicalPackage UUID IsNull: {IsNull}, Value: {Value}",
|
||||||
|
uuid.IsNull, uuid.IsNull ? "null" : uuid.ToString());
|
||||||
|
if (!uuid.IsNull && !string.IsNullOrWhiteSpace(uuid.ToString()))
|
||||||
|
{
|
||||||
|
hardwareInfo.SystemUuid = uuid.ToString().Trim();
|
||||||
|
_logger.LogInformation("Found UUID from CIM_PhysicalPackage: {Uuid}", hardwareInfo.SystemUuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(ex, "UUID property not available from CIM_PhysicalPackage item");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(ex, "Failed to query CIM_PhysicalPackage for UUID");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning("Could not find system UUID from any source");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to query system UUID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 AMT PlatformGUID 转换为标准 UUID 格式
|
||||||
|
/// PlatformGUID 是 32 位十六进制字符串,需要转换为 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 格式
|
||||||
|
///
|
||||||
|
/// 示例:
|
||||||
|
/// AMT PlatformGUID: B826D58CD3E2E31187820B47ABD01400
|
||||||
|
/// Windows UUID: 8CD526B8-E2D3-11E3-8782-0B47ABD01400
|
||||||
|
///
|
||||||
|
/// 转换规则:前三组需要按字节反转(每2个十六进制字符为1字节)
|
||||||
|
/// </summary>
|
||||||
|
private string FormatPlatformGuidToUuid(string platformGuid)
|
||||||
|
{
|
||||||
|
// 移除可能存在的连字符
|
||||||
|
var cleanGuid = platformGuid.Replace("-", "").ToUpperInvariant();
|
||||||
|
|
||||||
|
if (cleanGuid.Length != 32)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Invalid PlatformGUID length: {Length}, expected 32", cleanGuid.Length);
|
||||||
|
return platformGuid; // 返回原始值
|
||||||
|
}
|
||||||
|
|
||||||
|
// AMT PlatformGUID 格式(32字符):
|
||||||
|
// B826D58C D3E2 E311 8782 0B47ABD01400
|
||||||
|
// 位置: 0-7 8-11 12-15 16-19 20-31
|
||||||
|
|
||||||
|
// 标准 UUID 格式:
|
||||||
|
// 8CD526B8-E2D3-11E3-8782-0B47ABD01400
|
||||||
|
|
||||||
|
// 第一组:4 字节(8字符),按字节反转
|
||||||
|
// B826D58C -> 8C D5 26 B8 -> 8CD526B8
|
||||||
|
var part1 = cleanGuid.Substring(0, 8);
|
||||||
|
var part1Reversed = $"{part1[6]}{part1[7]}{part1[4]}{part1[5]}{part1[2]}{part1[3]}{part1[0]}{part1[1]}";
|
||||||
|
|
||||||
|
// 第二组:2 字节(4字符),按字节反转
|
||||||
|
// D3E2 -> E2 D3 -> E2D3
|
||||||
|
var part2 = cleanGuid.Substring(8, 4);
|
||||||
|
var part2Reversed = $"{part2[2]}{part2[3]}{part2[0]}{part2[1]}";
|
||||||
|
|
||||||
|
// 第三组:2 字节(4字符),按字节反转
|
||||||
|
// E311 -> 11 E3 -> 11E3
|
||||||
|
var part3 = cleanGuid.Substring(12, 4);
|
||||||
|
var part3Reversed = $"{part3[2]}{part3[3]}{part3[0]}{part3[1]}";
|
||||||
|
|
||||||
|
// 第四组:2 字节(4字符),不反转
|
||||||
|
var part4 = cleanGuid.Substring(16, 4);
|
||||||
|
|
||||||
|
// 第五组:6 字节(12字符),不反转
|
||||||
|
var part5 = cleanGuid.Substring(20, 12);
|
||||||
|
|
||||||
|
var result = $"{part1Reversed}-{part2Reversed}-{part3Reversed}-{part4}-{part5}";
|
||||||
|
_logger.LogInformation("UUID conversion: {Raw} -> {Formatted}", cleanGuid, result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private void QueryProcessorInfo(IWsmanConnection connection, HardwareInfo hardwareInfo)
|
private void QueryProcessorInfo(IWsmanConnection connection, HardwareInfo hardwareInfo)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -89,6 +89,15 @@ public class HardwareInfoService : IHardwareInfoService
|
|||||||
|
|
||||||
hardwareInfo.DeviceId = deviceId;
|
hardwareInfo.DeviceId = deviceId;
|
||||||
|
|
||||||
|
// 如果查询到了 UUID,保存到 AmtDevice
|
||||||
|
if (!string.IsNullOrEmpty(hardwareInfo.SystemUuid) && device.SystemUuid != hardwareInfo.SystemUuid)
|
||||||
|
{
|
||||||
|
device.SystemUuid = hardwareInfo.SystemUuid;
|
||||||
|
context.AmtDevices.Update(device);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation("Updated device {DeviceId} with UUID: {Uuid}", deviceId, hardwareInfo.SystemUuid);
|
||||||
|
}
|
||||||
|
|
||||||
// Save to cache
|
// Save to cache
|
||||||
await _repository.SaveAsync(hardwareInfo);
|
await _repository.SaveAsync(hardwareInfo);
|
||||||
|
|
||||||
@ -149,7 +158,8 @@ public class HardwareInfoService : IHardwareInfoService
|
|||||||
{
|
{
|
||||||
Manufacturer = hardwareInfo.SystemManufacturer,
|
Manufacturer = hardwareInfo.SystemManufacturer,
|
||||||
Model = hardwareInfo.SystemModel,
|
Model = hardwareInfo.SystemModel,
|
||||||
SerialNumber = hardwareInfo.SystemSerialNumber
|
SerialNumber = hardwareInfo.SystemSerialNumber,
|
||||||
|
Uuid = hardwareInfo.SystemUuid
|
||||||
},
|
},
|
||||||
Processor = new ProcessorInfoDto
|
Processor = new ProcessorInfoDto
|
||||||
{
|
{
|
||||||
|
|||||||
465
backend-csharp/AmtScanner.Api/Services/WindowsScannerService.cs
Normal file
465
backend-csharp/AmtScanner.Api/Services/WindowsScannerService.cs
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
using AmtScanner.Api.Data;
|
||||||
|
using AmtScanner.Api.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Management;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace AmtScanner.Api.Services;
|
||||||
|
|
||||||
|
public interface IWindowsScannerService
|
||||||
|
{
|
||||||
|
Task<List<OsDevice>> ScanNetworkAsync(string taskId, string networkSegment, string subnetMask,
|
||||||
|
IProgress<OsScanProgress> progress, CancellationToken cancellationToken = default);
|
||||||
|
Task<OsDevice?> GetOsInfoAsync(string ipAddress, string username, string password);
|
||||||
|
Task<string?> GetSystemUuidAsync(string ipAddress, string username, string password);
|
||||||
|
Task BindAmtDevicesAsync();
|
||||||
|
void CancelScan(string taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WindowsScannerService : IWindowsScannerService
|
||||||
|
{
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
private readonly ILogger<WindowsScannerService> _logger;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly ConcurrentDictionary<string, CancellationTokenSource> _cancellationTokens = new();
|
||||||
|
|
||||||
|
public WindowsScannerService(
|
||||||
|
IServiceScopeFactory scopeFactory,
|
||||||
|
ILogger<WindowsScannerService> logger,
|
||||||
|
IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
_logger = logger;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<OsDevice>> ScanNetworkAsync(
|
||||||
|
string taskId,
|
||||||
|
string networkSegment,
|
||||||
|
string subnetMask,
|
||||||
|
IProgress<OsScanProgress> progress,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Starting OS scan for task: {TaskId}", taskId);
|
||||||
|
|
||||||
|
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
|
_cancellationTokens[taskId] = cts;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ipList = CalculateIpRange(networkSegment, subnetMask);
|
||||||
|
var foundDevices = new ConcurrentBag<OsDevice>();
|
||||||
|
int scannedCount = 0;
|
||||||
|
int foundCount = 0;
|
||||||
|
|
||||||
|
var threadPoolSize = _configuration.GetValue<int>("Scanner:ThreadPoolSize", 50);
|
||||||
|
var parallelOptions = new ParallelOptions
|
||||||
|
{
|
||||||
|
MaxDegreeOfParallelism = threadPoolSize,
|
||||||
|
CancellationToken = cts.Token
|
||||||
|
};
|
||||||
|
|
||||||
|
await Parallel.ForEachAsync(ipList, parallelOptions, async (ip, ct) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var device = await ScanSingleHostAsync(ip, ct);
|
||||||
|
var scanned = Interlocked.Increment(ref scannedCount);
|
||||||
|
|
||||||
|
if (device != null)
|
||||||
|
{
|
||||||
|
foundDevices.Add(device);
|
||||||
|
var found = Interlocked.Increment(ref foundCount);
|
||||||
|
await SaveOsDeviceAsync(device);
|
||||||
|
|
||||||
|
progress.Report(new OsScanProgress
|
||||||
|
{
|
||||||
|
TaskId = taskId,
|
||||||
|
ScannedCount = scanned,
|
||||||
|
TotalCount = ipList.Count,
|
||||||
|
FoundDevices = found,
|
||||||
|
ProgressPercentage = (double)scanned / ipList.Count * 100,
|
||||||
|
CurrentIp = ip,
|
||||||
|
LatestDevice = device
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
progress.Report(new OsScanProgress
|
||||||
|
{
|
||||||
|
TaskId = taskId,
|
||||||
|
ScannedCount = scanned,
|
||||||
|
TotalCount = ipList.Count,
|
||||||
|
FoundDevices = foundCount,
|
||||||
|
ProgressPercentage = (double)scanned / ipList.Count * 100,
|
||||||
|
CurrentIp = ip
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Error scanning {Ip}", ip);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 扫描完成后尝试绑定 AMT 设备
|
||||||
|
await BindAmtDevicesAsync();
|
||||||
|
|
||||||
|
return foundDevices.ToList();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_cancellationTokens.TryRemove(taskId, out _);
|
||||||
|
cts.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelScan(string taskId)
|
||||||
|
{
|
||||||
|
if (_cancellationTokens.TryGetValue(taskId, out var cts))
|
||||||
|
{
|
||||||
|
cts.Cancel();
|
||||||
|
_logger.LogInformation("OS scan task {TaskId} cancelled", taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<OsDevice?> ScanSingleHostAsync(string ip, CancellationToken ct)
|
||||||
|
{
|
||||||
|
// 先 Ping 检测是否在线
|
||||||
|
if (!await IsHostOnlineAsync(ip, ct))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// 检测 Windows 端口
|
||||||
|
var isWindows = await IsWindowsHostAsync(ip, ct);
|
||||||
|
|
||||||
|
if (isWindows)
|
||||||
|
{
|
||||||
|
return new OsDevice
|
||||||
|
{
|
||||||
|
IpAddress = ip,
|
||||||
|
OsType = OsType.Windows,
|
||||||
|
IsOnline = true,
|
||||||
|
LastOnlineAt = DateTime.UtcNow,
|
||||||
|
DiscoveredAt = DateTime.UtcNow,
|
||||||
|
LastUpdatedAt = DateTime.UtcNow,
|
||||||
|
Description = "通过端口扫描发现"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测 Linux (SSH 端口)
|
||||||
|
var isLinux = await IsPortOpenAsync(ip, 22, 2000, ct);
|
||||||
|
if (isLinux)
|
||||||
|
{
|
||||||
|
return new OsDevice
|
||||||
|
{
|
||||||
|
IpAddress = ip,
|
||||||
|
OsType = OsType.Linux,
|
||||||
|
IsOnline = true,
|
||||||
|
LastOnlineAt = DateTime.UtcNow,
|
||||||
|
DiscoveredAt = DateTime.UtcNow,
|
||||||
|
LastUpdatedAt = DateTime.UtcNow,
|
||||||
|
Description = "通过 SSH 端口发现"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IsHostOnlineAsync(string ip, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var ping = new Ping();
|
||||||
|
var reply = await ping.SendPingAsync(ip, 1000);
|
||||||
|
return reply.Status == IPStatus.Success;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IsWindowsHostAsync(string ip, CancellationToken ct)
|
||||||
|
{
|
||||||
|
// 检测 Windows 常用端口: 135(RPC), 445(SMB), 3389(RDP), 5985(WinRM)
|
||||||
|
var windowsPorts = new[] { 135, 445, 3389, 5985 };
|
||||||
|
|
||||||
|
foreach (var port in windowsPorts)
|
||||||
|
{
|
||||||
|
if (await IsPortOpenAsync(ip, port, 1000, ct))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IsPortOpenAsync(string ip, int port, int timeoutMs, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var client = new TcpClient();
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
cts.CancelAfter(timeoutMs);
|
||||||
|
await client.ConnectAsync(ip, port, cts.Token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过 WMI 获取远程 Windows 系统信息
|
||||||
|
/// </summary>
|
||||||
|
public async Task<OsDevice?> GetOsInfoAsync(string ipAddress, string username, string password)
|
||||||
|
{
|
||||||
|
return await Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var options = new ConnectionOptions
|
||||||
|
{
|
||||||
|
Username = username,
|
||||||
|
Password = password,
|
||||||
|
Impersonation = ImpersonationLevel.Impersonate,
|
||||||
|
Authentication = AuthenticationLevel.PacketPrivacy
|
||||||
|
};
|
||||||
|
|
||||||
|
var scope = new ManagementScope($"\\\\{ipAddress}\\root\\cimv2", options);
|
||||||
|
scope.Connect();
|
||||||
|
|
||||||
|
var device = new OsDevice
|
||||||
|
{
|
||||||
|
IpAddress = ipAddress,
|
||||||
|
OsType = OsType.Windows,
|
||||||
|
IsOnline = true,
|
||||||
|
LastOnlineAt = DateTime.UtcNow,
|
||||||
|
LastUpdatedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取 UUID
|
||||||
|
var uuidQuery = new ObjectQuery("SELECT UUID FROM Win32_ComputerSystemProduct");
|
||||||
|
using (var uuidSearcher = new ManagementObjectSearcher(scope, uuidQuery))
|
||||||
|
{
|
||||||
|
foreach (var obj in uuidSearcher.Get())
|
||||||
|
{
|
||||||
|
device.SystemUuid = obj["UUID"]?.ToString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取操作系统信息
|
||||||
|
var osQuery = new ObjectQuery("SELECT Caption, Version, OSArchitecture, LastBootUpTime FROM Win32_OperatingSystem");
|
||||||
|
using (var osSearcher = new ManagementObjectSearcher(scope, osQuery))
|
||||||
|
{
|
||||||
|
foreach (var obj in osSearcher.Get())
|
||||||
|
{
|
||||||
|
device.OsVersion = $"{obj["Caption"]} ({obj["Version"]})";
|
||||||
|
device.Architecture = obj["OSArchitecture"]?.ToString();
|
||||||
|
|
||||||
|
var lastBootStr = obj["LastBootUpTime"]?.ToString();
|
||||||
|
if (!string.IsNullOrEmpty(lastBootStr))
|
||||||
|
{
|
||||||
|
device.LastBootTime = ManagementDateTimeConverter.ToDateTime(lastBootStr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取计算机名
|
||||||
|
var csQuery = new ObjectQuery("SELECT Name, UserName FROM Win32_ComputerSystem");
|
||||||
|
using (var csSearcher = new ManagementObjectSearcher(scope, csQuery))
|
||||||
|
{
|
||||||
|
foreach (var obj in csSearcher.Get())
|
||||||
|
{
|
||||||
|
device.Hostname = obj["Name"]?.ToString();
|
||||||
|
device.LoggedInUser = obj["UserName"]?.ToString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 MAC 地址
|
||||||
|
var netQuery = new ObjectQuery("SELECT MACAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True");
|
||||||
|
using (var netSearcher = new ManagementObjectSearcher(scope, netQuery))
|
||||||
|
{
|
||||||
|
foreach (var obj in netSearcher.Get())
|
||||||
|
{
|
||||||
|
var mac = obj["MACAddress"]?.ToString();
|
||||||
|
if (!string.IsNullOrEmpty(mac))
|
||||||
|
{
|
||||||
|
device.MacAddress = mac;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
device.Description = "通过 WMI 获取详细信息";
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to get OS info for {Ip} via WMI", ipAddress);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取远程 Windows 系统的 UUID
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string?> GetSystemUuidAsync(string ipAddress, string username, string password)
|
||||||
|
{
|
||||||
|
return await Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var options = new ConnectionOptions
|
||||||
|
{
|
||||||
|
Username = username,
|
||||||
|
Password = password,
|
||||||
|
Impersonation = ImpersonationLevel.Impersonate,
|
||||||
|
Authentication = AuthenticationLevel.PacketPrivacy
|
||||||
|
};
|
||||||
|
|
||||||
|
var scope = new ManagementScope($"\\\\{ipAddress}\\root\\cimv2", options);
|
||||||
|
scope.Connect();
|
||||||
|
|
||||||
|
var query = new ObjectQuery("SELECT UUID FROM Win32_ComputerSystemProduct");
|
||||||
|
using var searcher = new ManagementObjectSearcher(scope, query);
|
||||||
|
|
||||||
|
foreach (var obj in searcher.Get())
|
||||||
|
{
|
||||||
|
return obj["UUID"]?.ToString();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to get UUID for {Ip}", ipAddress);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据 UUID 自动绑定 AMT 设备和操作系统设备
|
||||||
|
/// </summary>
|
||||||
|
public async Task BindAmtDevicesAsync()
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
|
||||||
|
// 获取所有有 UUID 的操作系统设备
|
||||||
|
var osDevices = await context.OsDevices
|
||||||
|
.Where(o => o.SystemUuid != null && o.AmtDeviceId == null)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// 获取所有有 UUID 的 AMT 设备
|
||||||
|
var amtDevices = await context.AmtDevices
|
||||||
|
.Where(a => a.SystemUuid != null)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var amtUuidMap = amtDevices.ToDictionary(a => a.SystemUuid!, a => a);
|
||||||
|
|
||||||
|
foreach (var osDevice in osDevices)
|
||||||
|
{
|
||||||
|
if (osDevice.SystemUuid != null && amtUuidMap.TryGetValue(osDevice.SystemUuid, out var amtDevice))
|
||||||
|
{
|
||||||
|
osDevice.AmtDeviceId = amtDevice.Id;
|
||||||
|
_logger.LogInformation("Bound OS device {OsIp} to AMT device {AmtIp} via UUID {Uuid}",
|
||||||
|
osDevice.IpAddress, amtDevice.IpAddress, osDevice.SystemUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveOsDeviceAsync(OsDevice device)
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
|
||||||
|
var existing = await context.OsDevices
|
||||||
|
.FirstOrDefaultAsync(d => d.IpAddress == device.IpAddress);
|
||||||
|
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.OsType = device.OsType;
|
||||||
|
existing.IsOnline = device.IsOnline;
|
||||||
|
existing.LastOnlineAt = device.LastOnlineAt;
|
||||||
|
existing.LastUpdatedAt = DateTime.UtcNow;
|
||||||
|
if (!string.IsNullOrEmpty(device.SystemUuid))
|
||||||
|
existing.SystemUuid = device.SystemUuid;
|
||||||
|
if (!string.IsNullOrEmpty(device.Hostname))
|
||||||
|
existing.Hostname = device.Hostname;
|
||||||
|
if (!string.IsNullOrEmpty(device.OsVersion))
|
||||||
|
existing.OsVersion = device.OsVersion;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.OsDevices.Add(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> CalculateIpRange(string networkSegment, string subnetMask)
|
||||||
|
{
|
||||||
|
var ipList = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var networkLong = IpToLong(networkSegment);
|
||||||
|
var cidr = SubnetMaskToCidr(subnetMask);
|
||||||
|
var hostBits = 32 - cidr;
|
||||||
|
var totalHosts = (int)Math.Pow(2, hostBits);
|
||||||
|
|
||||||
|
for (int i = 1; i < totalHosts - 1; i++)
|
||||||
|
{
|
||||||
|
var ipLong = networkLong + i;
|
||||||
|
ipList.Add(LongToIp(ipLong));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error calculating IP range");
|
||||||
|
}
|
||||||
|
return ipList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long IpToLong(string ipAddress)
|
||||||
|
{
|
||||||
|
var parts = ipAddress.Split('.');
|
||||||
|
long result = 0;
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
result = result << 8 | long.Parse(parts[i]);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string LongToIp(long ip) =>
|
||||||
|
$"{(ip >> 24) & 0xFF}.{(ip >> 16) & 0xFF}.{(ip >> 8) & 0xFF}.{ip & 0xFF}";
|
||||||
|
|
||||||
|
private int SubnetMaskToCidr(string subnetMask)
|
||||||
|
{
|
||||||
|
if (subnetMask.StartsWith("/"))
|
||||||
|
return int.Parse(subnetMask.Substring(1));
|
||||||
|
|
||||||
|
var parts = subnetMask.Split('.');
|
||||||
|
int cidr = 0;
|
||||||
|
foreach (var part in parts)
|
||||||
|
cidr += Convert.ToString(int.Parse(part), 2).Count(c => c == '1');
|
||||||
|
return cidr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OsScanProgress
|
||||||
|
{
|
||||||
|
public string TaskId { get; set; } = string.Empty;
|
||||||
|
public int ScannedCount { get; set; }
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
public int FoundDevices { get; set; }
|
||||||
|
public double ProgressPercentage { get; set; }
|
||||||
|
public string? CurrentIp { get; set; }
|
||||||
|
public OsDevice? LatestDevice { get; set; }
|
||||||
|
}
|
||||||
11
backend-csharp/AmtScanner.Api/add_os_devices_menu.sql
Normal file
11
backend-csharp/AmtScanner.Api/add_os_devices_menu.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
-- 添加操作系统设备管理菜单(放在桌面管理目录下,ParentId=20)
|
||||||
|
INSERT INTO `Menus` (`Id`, `Name`, `Title`, `Icon`, `Path`, `Component`, `ParentId`, `Sort`, `IsHide`, `KeepAlive`, `IsIframe`, `IsSystem`, `IsHideTab`, `CreatedAt`)
|
||||||
|
VALUES (22, 'OsDevices', '操作系统', 'Monitor', 'os-devices', '/desktop-manage/os-devices', 20, 2, 0, 0, 0, 0, 0, NOW());
|
||||||
|
|
||||||
|
-- 为超级管理员角色添加菜单权限
|
||||||
|
INSERT INTO `RoleMenus` (`RoleId`, `MenuId`)
|
||||||
|
SELECT r.Id, 22 FROM `Roles` r WHERE r.RoleCode = 'R_SUPER';
|
||||||
|
|
||||||
|
-- 为管理员角色添加菜单权限
|
||||||
|
INSERT INTO `RoleMenus` (`RoleId`, `MenuId`)
|
||||||
|
SELECT r.Id, 22 FROM `Roles` r WHERE r.RoleCode = 'R_ADMIN';
|
||||||
Loading…
x
Reference in New Issue
Block a user