feat: 初始化项目基础架构和核心功能
- 添加项目基础结构:Cargo.toml、.gitignore、设备UID和密钥文件 - 实现前端Vue3项目结构:路由、登录页面、设备管理页面 - 添加核心协议定义(crates/protocol):设备状态、资产、USB事件等 - 实现客户端监控模块:系统状态收集、资产收集 - 实现服务端基础API和插件系统 - 添加数据库迁移脚本:设备管理、资产跟踪、告警系统等 - 实现前端设备状态展示和基本交互 - 添加使用时长统计和水印功能插件
This commit is contained in:
143
crates/server/src/api/assets.rs
Normal file
143
crates/server/src/api/assets.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use axum::{extract::{State, Query}, Json};
|
||||
use serde::Deserialize;
|
||||
use sqlx::Row;
|
||||
use crate::AppState;
|
||||
use super::{ApiResponse, Pagination};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AssetListParams {
|
||||
pub device_uid: Option<String>,
|
||||
pub search: Option<String>,
|
||||
pub page: Option<u32>,
|
||||
pub page_size: Option<u32>,
|
||||
}
|
||||
|
||||
pub async fn list_hardware(
|
||||
State(state): State<AppState>,
|
||||
Query(params): Query<AssetListParams>,
|
||||
) -> Json<ApiResponse<serde_json::Value>> {
|
||||
let limit = params.page_size.unwrap_or(20).min(100);
|
||||
let offset = params.page.unwrap_or(1).saturating_sub(1) * limit;
|
||||
|
||||
// Normalize empty strings to None
|
||||
let device_uid = params.device_uid.as_deref().filter(|s| !s.is_empty()).map(String::from);
|
||||
let search = params.search.as_deref().filter(|s| !s.is_empty()).map(String::from);
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, device_uid, cpu_model, cpu_cores, memory_total_mb, disk_model, disk_total_mb,
|
||||
gpu_model, motherboard, serial_number, reported_at
|
||||
FROM hardware_assets WHERE 1=1
|
||||
AND (? IS NULL OR device_uid = ?)
|
||||
AND (? IS NULL OR cpu_model LIKE '%' || ? || '%' OR gpu_model LIKE '%' || ? || '%')
|
||||
ORDER BY reported_at DESC LIMIT ? OFFSET ?"
|
||||
)
|
||||
.bind(&device_uid).bind(&device_uid)
|
||||
.bind(&search).bind(&search).bind(&search)
|
||||
.bind(limit).bind(offset)
|
||||
.fetch_all(&state.db)
|
||||
.await;
|
||||
|
||||
match rows {
|
||||
Ok(records) => {
|
||||
let items: Vec<serde_json::Value> = records.iter().map(|r| serde_json::json!({
|
||||
"id": r.get::<i64, _>("id"),
|
||||
"device_uid": r.get::<String, _>("device_uid"),
|
||||
"cpu_model": r.get::<String, _>("cpu_model"),
|
||||
"cpu_cores": r.get::<i32, _>("cpu_cores"),
|
||||
"memory_total_mb": r.get::<i64, _>("memory_total_mb"),
|
||||
"disk_model": r.get::<String, _>("disk_model"),
|
||||
"disk_total_mb": r.get::<i64, _>("disk_total_mb"),
|
||||
"gpu_model": r.get::<Option<String>, _>("gpu_model"),
|
||||
"motherboard": r.get::<Option<String>, _>("motherboard"),
|
||||
"serial_number": r.get::<Option<String>, _>("serial_number"),
|
||||
"reported_at": r.get::<String, _>("reported_at"),
|
||||
})).collect();
|
||||
Json(ApiResponse::ok(serde_json::json!({
|
||||
"hardware": items,
|
||||
"page": params.page.unwrap_or(1),
|
||||
"page_size": limit,
|
||||
})))
|
||||
}
|
||||
Err(e) => Json(ApiResponse::internal_error("query hardware assets", e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_software(
|
||||
State(state): State<AppState>,
|
||||
Query(params): Query<AssetListParams>,
|
||||
) -> Json<ApiResponse<serde_json::Value>> {
|
||||
let limit = params.page_size.unwrap_or(20).min(100);
|
||||
let offset = params.page.unwrap_or(1).saturating_sub(1) * limit;
|
||||
|
||||
// Normalize empty strings to None
|
||||
let device_uid = params.device_uid.as_deref().filter(|s| !s.is_empty()).map(String::from);
|
||||
let search = params.search.as_deref().filter(|s| !s.is_empty()).map(String::from);
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, device_uid, name, version, publisher, install_date, install_path
|
||||
FROM software_assets WHERE 1=1
|
||||
AND (? IS NULL OR device_uid = ?)
|
||||
AND (? IS NULL OR name LIKE '%' || ? || '%' OR publisher LIKE '%' || ? || '%')
|
||||
ORDER BY name ASC LIMIT ? OFFSET ?"
|
||||
)
|
||||
.bind(&device_uid).bind(&device_uid)
|
||||
.bind(&search).bind(&search).bind(&search)
|
||||
.bind(limit).bind(offset)
|
||||
.fetch_all(&state.db)
|
||||
.await;
|
||||
|
||||
match rows {
|
||||
Ok(records) => {
|
||||
let items: Vec<serde_json::Value> = records.iter().map(|r| serde_json::json!({
|
||||
"id": r.get::<i64, _>("id"),
|
||||
"device_uid": r.get::<String, _>("device_uid"),
|
||||
"name": r.get::<String, _>("name"),
|
||||
"version": r.get::<Option<String>, _>("version"),
|
||||
"publisher": r.get::<Option<String>, _>("publisher"),
|
||||
"install_date": r.get::<Option<String>, _>("install_date"),
|
||||
"install_path": r.get::<Option<String>, _>("install_path"),
|
||||
})).collect();
|
||||
Json(ApiResponse::ok(serde_json::json!({
|
||||
"software": items,
|
||||
"page": params.page.unwrap_or(1),
|
||||
"page_size": limit,
|
||||
})))
|
||||
}
|
||||
Err(e) => Json(ApiResponse::internal_error("query software assets", e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_changes(
|
||||
State(state): State<AppState>,
|
||||
Query(page): Query<Pagination>,
|
||||
) -> Json<ApiResponse<serde_json::Value>> {
|
||||
let offset = page.offset();
|
||||
let limit = page.limit();
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, device_uid, change_type, change_detail, detected_at
|
||||
FROM asset_changes ORDER BY detected_at DESC LIMIT ? OFFSET ?"
|
||||
)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&state.db)
|
||||
.await;
|
||||
|
||||
match rows {
|
||||
Ok(records) => {
|
||||
let items: Vec<serde_json::Value> = records.iter().map(|r| serde_json::json!({
|
||||
"id": r.get::<i64, _>("id"),
|
||||
"device_uid": r.get::<String, _>("device_uid"),
|
||||
"change_type": r.get::<String, _>("change_type"),
|
||||
"change_detail": r.get::<String, _>("change_detail"),
|
||||
"detected_at": r.get::<String, _>("detected_at"),
|
||||
})).collect();
|
||||
Json(ApiResponse::ok(serde_json::json!({
|
||||
"changes": items,
|
||||
"page": page.page.unwrap_or(1),
|
||||
"page_size": limit,
|
||||
})))
|
||||
}
|
||||
Err(e) => Json(ApiResponse::internal_error("query asset changes", e)),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user