feat: 初始化项目基础架构和核心功能
- 添加项目基础结构:Cargo.toml、.gitignore、设备UID和密钥文件 - 实现前端Vue3项目结构:路由、登录页面、设备管理页面 - 添加核心协议定义(crates/protocol):设备状态、资产、USB事件等 - 实现客户端监控模块:系统状态收集、资产收集 - 实现服务端基础API和插件系统 - 添加数据库迁移脚本:设备管理、资产跟踪、告警系统等 - 实现前端设备状态展示和基本交互 - 添加使用时长统计和水印功能插件
This commit is contained in:
192
web/src/views/UsbPolicy.vue
Normal file
192
web/src/views/UsbPolicy.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="usb-page">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="策略管理" name="policies">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="showPolicyDialog()">新建策略</el-button>
|
||||
</div>
|
||||
<el-table :data="policies" v-loading="loading" stripe size="small">
|
||||
<el-table-column prop="name" label="策略名称" width="180" />
|
||||
<el-table-column prop="policy_type" label="策略类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="policyTypeTag(row.policy_type)" size="small">{{ policyTypeLabel(row.policy_type) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="target_group" label="目标分组" width="120" />
|
||||
<el-table-column prop="enabled" label="启用" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-switch :model-value="row.enabled" @change="togglePolicy(row)" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="showPolicyDialog(row)">编辑</el-button>
|
||||
<el-button link type="danger" size="small" @click="deletePolicy(row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="事件日志" name="events">
|
||||
<div class="toolbar">
|
||||
<el-select v-model="eventFilter" placeholder="事件类型" clearable style="width: 150px" @change="fetchEvents">
|
||||
<el-option label="插入" value="Inserted" />
|
||||
<el-option label="拔出" value="Removed" />
|
||||
<el-option label="拦截" value="Blocked" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-table :data="events" v-loading="evLoading" stripe size="small">
|
||||
<el-table-column prop="device_name" label="USB设备" width="150" />
|
||||
<el-table-column label="事件类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.event_type === 'Inserted' ? 'success' : row.event_type === 'Blocked' ? 'danger' : 'info'" size="small">
|
||||
{{ eventTypeLabel(row.event_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="vendor_id" label="VID" width="100" />
|
||||
<el-table-column prop="product_id" label="PID" width="100" />
|
||||
<el-table-column prop="serial_number" label="序列号" width="160" />
|
||||
<el-table-column prop="device_uid" label="终端UID" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="event_time" label="时间" width="170" />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-dialog v-model="policyDialogVisible" :title="editingPolicy ? '编辑策略' : '新建策略'" width="500px">
|
||||
<el-form :model="policyForm" label-width="100px">
|
||||
<el-form-item label="策略名称">
|
||||
<el-input v-model="policyForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="策略类型">
|
||||
<el-select v-model="policyForm.policy_type" style="width: 100%">
|
||||
<el-option label="全部拦截" value="all_block" />
|
||||
<el-option label="白名单" value="whitelist" />
|
||||
<el-option label="黑名单" value="blacklist" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标分组">
|
||||
<el-input v-model="policyForm.target_group" placeholder="留空表示全部终端" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备规则">
|
||||
<el-input v-model="policyForm.rules" type="textarea" :rows="3" placeholder='[{"vendor_id":"1234","product_id":"5678"}]' />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="policyDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="savePolicy">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { api } from '@/lib/api'
|
||||
|
||||
const activeTab = ref('policies')
|
||||
|
||||
// Policies
|
||||
const policies = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const policyDialogVisible = ref(false)
|
||||
const editingPolicy = ref<any>(null)
|
||||
const policyForm = reactive({ name: '', policy_type: 'all_block', target_group: '', rules: '[]' })
|
||||
|
||||
async function fetchPolicies() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<any>('/api/usb/policies')
|
||||
policies.value = data.policies || []
|
||||
} catch { /* api.ts handles 401 */ } finally { loading.value = false }
|
||||
}
|
||||
|
||||
function showPolicyDialog(row?: any) {
|
||||
if (row) {
|
||||
editingPolicy.value = row
|
||||
policyForm.name = row.name
|
||||
policyForm.policy_type = row.policy_type
|
||||
policyForm.target_group = row.target_group || ''
|
||||
policyForm.rules = row.rules || '[]'
|
||||
} else {
|
||||
editingPolicy.value = null
|
||||
policyForm.name = ''
|
||||
policyForm.policy_type = 'all_block'
|
||||
policyForm.target_group = ''
|
||||
policyForm.rules = '[]'
|
||||
}
|
||||
policyDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function savePolicy() {
|
||||
try {
|
||||
if (editingPolicy.value) {
|
||||
await api.put(`/api/usb/policies/${editingPolicy.value.id}`, policyForm)
|
||||
ElMessage.success('策略已更新')
|
||||
} else {
|
||||
await api.post('/api/usb/policies', policyForm)
|
||||
ElMessage.success('策略已创建')
|
||||
}
|
||||
policyDialogVisible.value = false
|
||||
fetchPolicies()
|
||||
} catch (e: any) { ElMessage.error(e.message || '操作失败') }
|
||||
}
|
||||
|
||||
async function togglePolicy(row: any) {
|
||||
try {
|
||||
await api.put(`/api/usb/policies/${row.id}`, { enabled: !row.enabled ? 1 : 0 })
|
||||
fetchPolicies()
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function deletePolicy(id: number) {
|
||||
await ElMessageBox.confirm('确定删除该策略?', '确认', { type: 'warning' })
|
||||
try {
|
||||
await api.delete(`/api/usb/policies/${id}`)
|
||||
ElMessage.success('策略已删除')
|
||||
fetchPolicies()
|
||||
} catch (e: any) { ElMessage.error(e.message || '删除失败') }
|
||||
}
|
||||
|
||||
function policyTypeTag(type: string) {
|
||||
const map: Record<string, string> = { all_block: 'danger', whitelist: 'success', blacklist: 'warning' }
|
||||
return map[type] || 'info'
|
||||
}
|
||||
|
||||
function policyTypeLabel(type: string) {
|
||||
const map: Record<string, string> = { all_block: '全部拦截', whitelist: '白名单', blacklist: '黑名单' }
|
||||
return map[type] || type
|
||||
}
|
||||
|
||||
// Events
|
||||
const events = ref<any[]>([])
|
||||
const evLoading = ref(false)
|
||||
const eventFilter = ref('')
|
||||
|
||||
async function fetchEvents() {
|
||||
evLoading.value = true
|
||||
try {
|
||||
const params = new URLSearchParams()
|
||||
if (eventFilter.value) params.set('event_type', eventFilter.value)
|
||||
const data = await api.get<any>(`/api/usb/events?${params}`)
|
||||
events.value = data.events || []
|
||||
} catch { /* api.ts handles 401 */ } finally { evLoading.value = false }
|
||||
}
|
||||
|
||||
function eventTypeLabel(type: string) {
|
||||
const map: Record<string, string> = { Inserted: '插入', Removed: '拔出', Blocked: '拦截' }
|
||||
return map[type] || type
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchPolicies()
|
||||
fetchEvents()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.usb-page { padding: 20px; }
|
||||
.toolbar { display: flex; gap: 12px; margin-bottom: 16px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user