v0.20.0: 机器码管理工具 - 全新功能发布

✨ 新增功能
• 完整的跨平台机器码管理工具(Windows/macOS/Linux)
• 智能格式检测和验证(UUID/32位十六进制)
• 机器码生成、修改、备份和恢复功能
• 完整的操作历史记录追踪
• 图形化文件选择对话框
• 一键恢复原始机器码功能

🔧 技术实现
• Rust 后端服务,支持注册表和文件系统操作
• React 前端界面,完整的用户体验
• 跨平台兼容性和权限管理
• 历史记录持久化存储

🚀 用户体验
• 直观的图形化操作界面
• 智能权限检测和提示
• 清晰的错误处理和用户反馈
• 完善的恢复功能和备份机制

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
coso
2025-12-26 01:10:34 +08:00
co-authored by Claude Sonnet 4
parent f02e119957
commit c380d5eee7
23 changed files with 3449 additions and 7 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "proxycast",
"private": true,
"version": "0.19.0",
"version": "0.20.0",
"type": "module",
"repository": {
"type": "git",
+12 -1
View File
@@ -3668,7 +3668,7 @@ dependencies = [
[[package]]
name = "proxycast"
version = "0.19.0"
version = "0.20.0"
dependencies = [
"anyhow",
"arboard",
@@ -3725,6 +3725,7 @@ dependencies = [
"uuid",
"winapi",
"windows 0.56.0",
"winreg 0.52.0",
"zip",
]
@@ -6770,6 +6771,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "winreg"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "winreg"
version = "0.55.0"
+3 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "proxycast"
version = "0.19.0"
version = "0.20.0"
description = "AI API Proxy Desktop App"
authors = ["you"]
edition = "2021"
@@ -66,7 +66,7 @@ arboard = "3"
# Platform specific dependencies for browser interceptor
# Windows specific dependencies for browser interceptor
# Windows specific dependencies for browser interceptor and machine ID management
[target.'cfg(windows)'.dependencies]
windows = { version = "0.56", features = [
"Win32_Foundation",
@@ -89,6 +89,7 @@ winapi = { version = "0.3", features = [
"psapi",
"tlhelp32",
] }
winreg = "0.52"
# macOS specific dependencies for browser interceptor
[target.'cfg(target_os = "macos")'.dependencies]
+299
View File
@@ -0,0 +1,299 @@
use crate::models::machine_id::*;
use crate::services::machine_id_service::MachineIdService;
use std::sync::Arc;
use tauri::State;
use tokio::sync::RwLock;
/// 机器码管理服务状态
pub type MachineIdState = Arc<RwLock<MachineIdService>>;
/// 获取当前机器码信息
#[tauri::command]
pub async fn get_current_machine_id(
service: State<'_, MachineIdState>,
) -> Result<MachineIdInfo, String> {
let service = service.read().await;
service.get_current_machine_id().await
}
/// 设置新的机器码
#[tauri::command]
pub async fn set_machine_id(
new_id: String,
service: State<'_, MachineIdState>,
) -> Result<MachineIdResult, String> {
let service = service.read().await;
service.set_machine_id(&new_id).await
}
/// 生成随机机器码
#[tauri::command]
pub async fn generate_random_machine_id(
service: State<'_, MachineIdState>,
) -> Result<String, String> {
let service = service.read().await;
Ok(service.generate_random_machine_id())
}
/// 验证机器码格式
#[tauri::command]
pub async fn validate_machine_id(
machine_id: String,
service: State<'_, MachineIdState>,
) -> Result<MachineIdValidation, String> {
let service = service.read().await;
service.validate_machine_id(&machine_id)
}
/// 检查管理员权限
#[tauri::command]
pub async fn check_admin_privileges(
service: State<'_, MachineIdState>,
) -> Result<AdminStatus, String> {
let service = service.read().await;
service.check_admin_privileges().await
}
/// 获取操作系统类型
#[tauri::command]
pub async fn get_os_type() -> Result<String, String> {
Ok(MachineIdService::get_os_type())
}
/// 备份机器码到文件
#[tauri::command]
pub async fn backup_machine_id_to_file(
file_path: String,
service: State<'_, MachineIdState>,
) -> Result<bool, String> {
let service = service.read().await;
service.backup_machine_id(&file_path).await
}
/// 从文件恢复机器码
#[tauri::command]
pub async fn restore_machine_id_from_file(
file_path: String,
service: State<'_, MachineIdState>,
) -> Result<MachineIdResult, String> {
let service = service.read().await;
service.restore_machine_id(&file_path).await
}
/// 格式化机器码
#[tauri::command]
pub async fn format_machine_id(
machine_id: String,
format_type: String, // "uuid" 或 "hex32"
) -> Result<String, String> {
let format = match format_type.to_lowercase().as_str() {
"uuid" => MachineIdFormat::Uuid,
"hex32" => MachineIdFormat::Hex32,
_ => return Err("Unsupported format type. Use 'uuid' or 'hex32'".to_string()),
};
format.format_machine_id(&machine_id)
}
/// 检测机器码格式
#[tauri::command]
pub async fn detect_machine_id_format(machine_id: String) -> Result<String, String> {
let format = MachineIdFormat::detect(&machine_id);
Ok(format.to_string())
}
/// 转换机器码格式
#[tauri::command]
pub async fn convert_machine_id_format(
machine_id: String,
target_format: String,
) -> Result<String, String> {
// 首先清理输入
let cleaned = machine_id.replace("-", "").replace(" ", "").to_lowercase();
// 验证输入是否为有效的十六进制
if cleaned.len() != 32 || !cleaned.chars().all(|c| c.is_ascii_hexdigit()) {
return Err("Invalid machine ID: must be 32 hex characters".to_string());
}
match target_format.to_lowercase().as_str() {
"uuid" => {
let format = MachineIdFormat::Uuid;
format.format_machine_id(&cleaned)
}
"hex32" => {
let format = MachineIdFormat::Hex32;
format.format_machine_id(&cleaned)
}
_ => Err("Unsupported target format. Use 'uuid' or 'hex32'".to_string()),
}
}
/// 获取机器码历史记录
#[tauri::command]
pub async fn get_machine_id_history(
service: State<'_, MachineIdState>,
) -> Result<Vec<MachineIdHistory>, String> {
let service = service.read().await;
service.get_history()
}
/// 清除机器码覆盖(仅限 macOS)
#[tauri::command]
pub async fn clear_machine_id_override() -> Result<MachineIdResult, String> {
#[cfg(target_os = "macos")]
{
use dirs;
use std::fs;
let override_file = dirs::data_dir()
.ok_or("Failed to get app data directory")?
.join("proxycast")
.join("machine-id-override");
match fs::remove_file(override_file) {
Ok(_) => Ok(MachineIdResult {
success: true,
message: "Machine ID override removed successfully".to_string(),
requires_restart: false,
requires_admin: false,
new_machine_id: None,
}),
Err(e) => Ok(MachineIdResult {
success: false,
message: format!("Failed to remove override: {}", e),
requires_restart: false,
requires_admin: false,
new_machine_id: None,
}),
}
}
#[cfg(not(target_os = "macos"))]
{
Ok(MachineIdResult {
success: false,
message: "Machine ID override is only supported on macOS".to_string(),
requires_restart: false,
requires_admin: false,
new_machine_id: None,
})
}
}
/// 复制机器码到剪贴板
#[tauri::command]
pub async fn copy_machine_id_to_clipboard(machine_id: String) -> Result<bool, String> {
use arboard::Clipboard;
let mut clipboard =
Clipboard::new().map_err(|e| format!("Failed to access clipboard: {}", e))?;
clipboard
.set_text(machine_id)
.map_err(|e| format!("Failed to copy to clipboard: {}", e))?;
Ok(true)
}
/// 从剪贴板粘贴机器码
#[tauri::command]
pub async fn paste_machine_id_from_clipboard() -> Result<String, String> {
use arboard::Clipboard;
let mut clipboard =
Clipboard::new().map_err(|e| format!("Failed to access clipboard: {}", e))?;
let text = clipboard
.get_text()
.map_err(|e| format!("Failed to read from clipboard: {}", e))?;
// 基本验证
let cleaned = text.replace("-", "").replace(" ", "").trim().to_lowercase();
if cleaned.len() == 32 && cleaned.chars().all(|c| c.is_ascii_hexdigit()) {
Ok(text.trim().to_string())
} else {
Err("Clipboard does not contain a valid machine ID".to_string())
}
}
/// 获取系统信息(用于调试和支持)
#[tauri::command]
pub async fn get_system_info() -> Result<SystemInfo, String> {
let os = MachineIdService::get_os_type();
let arch = std::env::consts::ARCH.to_string();
let family = std::env::consts::FAMILY.to_string();
Ok(SystemInfo {
os,
arch,
family,
machine_id_support: get_machine_id_platform_support(),
requires_admin: match MachineIdService::get_os_type().as_str() {
"windows" | "linux" => true,
"macos" => false,
_ => true,
},
})
}
/// 系统信息结构
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct SystemInfo {
pub os: String,
pub arch: String,
pub family: String,
pub machine_id_support: PlatformSupport,
pub requires_admin: bool,
}
/// 平台支持信息
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct PlatformSupport {
pub can_read: bool,
pub can_write: bool,
pub format: String,
pub method: String,
pub limitations: Vec<String>,
}
/// 获取当前平台的机器码支持信息
fn get_machine_id_platform_support() -> PlatformSupport {
match MachineIdService::get_os_type().as_str() {
"windows" => PlatformSupport {
can_read: true,
can_write: true,
format: "UUID".to_string(),
method: "Windows Registry (HKLM\\SOFTWARE\\Microsoft\\Cryptography\\MachineGuid)"
.to_string(),
limitations: vec![
"Requires administrator privileges".to_string(),
"May require restart for some applications".to_string(),
],
},
"macos" => PlatformSupport {
can_read: true,
can_write: true,
format: "UUID".to_string(),
method: "Application-level override (ioreg IOPlatformUUID + override file)".to_string(),
limitations: vec![
"Original system UUID cannot be modified".to_string(),
"Only affects applications that use the override".to_string(),
],
},
"linux" => PlatformSupport {
can_read: true,
can_write: true,
format: "32-bit Hex".to_string(),
method: "File system (/etc/machine-id)".to_string(),
limitations: vec![
"Requires root privileges".to_string(),
"May affect system services".to_string(),
"Some services may require restart".to_string(),
],
},
_ => PlatformSupport {
can_read: false,
can_write: false,
format: "Unknown".to_string(),
method: "Not supported".to_string(),
limitations: vec!["Platform not supported".to_string()],
},
}
}
+1
View File
@@ -3,6 +3,7 @@ pub mod browser_interceptor_cmd;
pub mod config_cmd;
pub mod flow_monitor_cmd;
pub mod injection_cmd;
pub mod machine_id_cmd;
pub mod mcp_cmd;
pub mod network_cmd;
pub mod oauth_cmd;
+24
View File
@@ -34,6 +34,7 @@ use commands::flow_monitor_cmd::{
FlowMonitorState, FlowQueryServiceState, FlowReplayerState, QuickFilterManagerState,
SessionManagerState,
};
use commands::machine_id_cmd::MachineIdState;
use commands::plugin_cmd::PluginManagerState;
use commands::provider_pool_cmd::{CredentialSyncServiceState, ProviderPoolServiceState};
use commands::resilience_cmd::ResilienceConfigState;
@@ -1530,6 +1531,11 @@ pub fn run() {
let token_cache_service = TokenCacheService::new();
let token_cache_service_state = TokenCacheServiceState(Arc::new(token_cache_service));
// Initialize MachineIdService
let machine_id_service = services::machine_id_service::MachineIdService::new()
.expect("Failed to initialize MachineIdService");
let machine_id_service_state: MachineIdState = Arc::new(RwLock::new(machine_id_service));
// Initialize RouterConfigState
let router_config_state = RouterConfigState::default();
@@ -1720,6 +1726,7 @@ pub fn run() {
.manage(provider_pool_service_state)
.manage(credential_sync_service_state)
.manage(token_cache_service_state)
.manage(machine_id_service_state)
.manage(router_config_state)
.manage(resilience_config_state)
.manage(telemetry_state)
@@ -2315,6 +2322,23 @@ pub fn run() {
commands::browser_interceptor_cmd::show_status_notification,
// Auto fix commands
commands::auto_fix_cmd::auto_fix_configuration,
// Machine ID commands
commands::machine_id_cmd::get_current_machine_id,
commands::machine_id_cmd::set_machine_id,
commands::machine_id_cmd::generate_random_machine_id,
commands::machine_id_cmd::validate_machine_id,
commands::machine_id_cmd::check_admin_privileges,
commands::machine_id_cmd::get_os_type,
commands::machine_id_cmd::backup_machine_id_to_file,
commands::machine_id_cmd::restore_machine_id_from_file,
commands::machine_id_cmd::format_machine_id,
commands::machine_id_cmd::detect_machine_id_format,
commands::machine_id_cmd::convert_machine_id_format,
commands::machine_id_cmd::get_machine_id_history,
commands::machine_id_cmd::clear_machine_id_override,
commands::machine_id_cmd::copy_machine_id_to_clipboard,
commands::machine_id_cmd::paste_machine_id_from_clipboard,
commands::machine_id_cmd::get_system_info,
// Network commands
commands::network_cmd::get_network_info,
])
+208
View File
@@ -0,0 +1,208 @@
use serde::{Deserialize, Serialize};
/// 机器码信息结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachineIdInfo {
/// 当前机器码
pub current_id: String,
/// 原始机器码(如果有备份)
pub original_id: Option<String>,
/// 操作系统平台
pub platform: String,
/// 是否可以修改
pub can_modify: bool,
/// 是否需要管理员权限
pub requires_admin: bool,
/// 是否存在备份
pub backup_exists: bool,
/// 机器码格式类型
pub format_type: MachineIdFormat,
}
/// 机器码操作结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachineIdResult {
/// 操作是否成功
pub success: bool,
/// 结果消息
pub message: String,
/// 是否需要重启
pub requires_restart: bool,
/// 是否需要管理员权限
pub requires_admin: bool,
/// 新的机器码(如果操作成功)
pub new_machine_id: Option<String>,
}
/// 管理员权限状态
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminStatus {
/// 是否具有管理员权限
pub is_admin: bool,
/// 操作系统平台
pub platform: String,
/// 权限提升方法说明
pub elevation_method: Option<String>,
/// 权限检查是否成功
pub check_success: bool,
}
/// 机器码格式类型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MachineIdFormat {
/// UUID 格式 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
Uuid,
/// 32位十六进制格式 (xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
#[serde(rename = "hex32")]
Hex32,
/// 其他格式
#[serde(rename = "unknown")]
Unknown,
}
/// 机器码备份信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachineIdBackup {
/// 备份的机器码
pub machine_id: String,
/// 备份时间戳
pub timestamp: i64,
/// 操作系统平台
pub platform: String,
/// 机器码格式
pub format: MachineIdFormat,
/// 备份描述
pub description: Option<String>,
}
/// 机器码历史记录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachineIdHistory {
/// 记录ID
pub id: String,
/// 机器码
pub machine_id: String,
/// 操作时间戳
pub timestamp: String,
/// 操作系统平台
pub platform: String,
/// 备份路径(可选)
pub backup_path: Option<String>,
}
/// 机器码操作类型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MachineIdOperation {
/// 获取当前机器码
Get,
/// 设置新机器码
Set,
/// 生成随机机器码
Generate,
/// 备份机器码
Backup,
/// 恢复机器码
Restore,
/// 重置为原始机器码
Reset,
}
/// 机器码验证结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachineIdValidation {
/// 是否有效
pub is_valid: bool,
/// 检测到的格式
pub detected_format: MachineIdFormat,
/// 验证错误信息
pub error_message: Option<String>,
/// 格式化后的机器码(如果有效)
pub formatted_id: Option<String>,
}
impl MachineIdFormat {
/// 从字符串检测机器码格式
pub fn detect(machine_id: &str) -> Self {
let cleaned = machine_id.replace("-", "").replace(" ", "").to_lowercase();
// 检查UUID格式:8-4-4-4-12个十六进制字符
if machine_id.contains("-") && machine_id.len() == 36 {
let parts: Vec<&str> = machine_id.split('-').collect();
if parts.len() == 5
&& parts[0].len() == 8
&& parts[1].len() == 4
&& parts[2].len() == 4
&& parts[3].len() == 4
&& parts[4].len() == 12
&& cleaned.chars().all(|c| c.is_ascii_hexdigit())
{
return MachineIdFormat::Uuid;
}
}
// 检查32位十六进制格式
if cleaned.len() == 32 && cleaned.chars().all(|c| c.is_ascii_hexdigit()) {
return MachineIdFormat::Hex32;
}
MachineIdFormat::Unknown
}
/// 格式化机器码为标准格式
pub fn format_machine_id(&self, machine_id: &str) -> Result<String, String> {
let cleaned = machine_id.replace("-", "").replace(" ", "").to_lowercase();
match self {
MachineIdFormat::Uuid => {
if cleaned.len() != 32 {
return Err("UUID format requires 32 hex characters".to_string());
}
if !cleaned.chars().all(|c| c.is_ascii_hexdigit()) {
return Err("UUID format requires hex characters only".to_string());
}
Ok(format!(
"{}-{}-{}-{}-{}",
&cleaned[0..8],
&cleaned[8..12],
&cleaned[12..16],
&cleaned[16..20],
&cleaned[20..32]
))
}
MachineIdFormat::Hex32 => {
if cleaned.len() != 32 {
return Err("Hex32 format requires 32 hex characters".to_string());
}
if !cleaned.chars().all(|c| c.is_ascii_hexdigit()) {
return Err("Hex32 format requires hex characters only".to_string());
}
Ok(cleaned)
}
MachineIdFormat::Unknown => Err("Cannot format unknown machine ID format".to_string()),
}
}
}
impl std::fmt::Display for MachineIdFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MachineIdFormat::Uuid => write!(f, "uuid"),
MachineIdFormat::Hex32 => write!(f, "hex32"),
MachineIdFormat::Unknown => write!(f, "unknown"),
}
}
}
impl std::fmt::Display for MachineIdOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MachineIdOperation::Get => write!(f, "Get"),
MachineIdOperation::Set => write!(f, "Set"),
MachineIdOperation::Generate => write!(f, "Generate"),
MachineIdOperation::Backup => write!(f, "Backup"),
MachineIdOperation::Restore => write!(f, "Restore"),
MachineIdOperation::Reset => write!(f, "Reset"),
}
}
}
+1
View File
@@ -1,6 +1,7 @@
pub mod anthropic;
pub mod app_type;
pub mod codewhisperer;
pub mod machine_id;
pub mod mcp_model;
pub mod openai;
pub mod prompt_model;
@@ -0,0 +1,727 @@
use crate::models::machine_id::*;
use dirs;
use serde_json;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tracing;
use uuid::Uuid;
#[cfg(target_os = "windows")]
use std::ptr;
#[cfg(target_os = "windows")]
use winapi::um::winnt::KEY_READ;
#[cfg(target_os = "windows")]
use winreg::{enums::*, RegKey};
pub struct MachineIdService {
backup_dir: PathBuf,
history_file: PathBuf,
}
impl MachineIdService {
pub fn new() -> Result<Self, String> {
let app_data_dir = dirs::data_dir()
.ok_or("Failed to get app data directory")?
.join("proxycast");
let backup_dir = app_data_dir.join("machine_id_backups");
let history_file = app_data_dir.join("machine_id_history.json");
// 确保应用数据目录存在
if let Err(e) = fs::create_dir_all(&app_data_dir) {
return Err(format!("Failed to create app data directory: {}", e));
}
// 确保备份目录存在
if let Err(e) = fs::create_dir_all(&backup_dir) {
return Err(format!("Failed to create backup directory: {}", e));
}
Ok(MachineIdService {
backup_dir,
history_file,
})
}
/// 获取当前操作系统类型
pub fn get_os_type() -> String {
std::env::consts::OS.to_string()
}
/// 获取当前机器码信息
pub async fn get_current_machine_id(&self) -> Result<MachineIdInfo, String> {
let os = Self::get_os_type();
let requires_admin = self.check_requires_admin().await?;
let backup_exists = self.check_backup_exists();
match os.as_str() {
"windows" => {
self.get_windows_machine_id(requires_admin, backup_exists)
.await
}
"macos" => {
self.get_macos_machine_id(requires_admin, backup_exists)
.await
}
"linux" => {
self.get_linux_machine_id(requires_admin, backup_exists)
.await
}
_ => Err(format!("Unsupported operating system: {}", os)),
}
}
/// 设置新的机器码
pub async fn set_machine_id(&self, new_id: &str) -> Result<MachineIdResult, String> {
let validation = self.validate_machine_id(new_id)?;
if !validation.is_valid {
return Ok(MachineIdResult {
success: false,
message: validation
.error_message
.unwrap_or("Invalid machine ID format".to_string()),
requires_restart: false,
requires_admin: false,
new_machine_id: None,
});
}
let formatted_id = validation.formatted_id.unwrap();
let os = Self::get_os_type();
// 在设置前先备份当前机器码
if let Err(e) = self.create_auto_backup().await {
tracing::warn!("Failed to create auto backup: {}", e);
}
let result = match os.as_str() {
"windows" => self.set_windows_machine_id(&formatted_id).await,
"macos" => self.set_macos_machine_id(&formatted_id).await,
"linux" => self.set_linux_machine_id(&formatted_id).await,
_ => Ok(MachineIdResult {
success: false,
message: format!("Unsupported operating system: {}", os),
requires_restart: false,
requires_admin: false,
new_machine_id: None,
}),
};
// 添加到历史记录
if let Ok(ref res) = result {
if res.success {
if let Err(e) = self.add_history_record(formatted_id.clone(), None) {
tracing::warn!("Failed to add history record: {}", e);
}
}
}
result
}
/// 生成随机机器码
pub fn generate_random_machine_id(&self) -> String {
let uuid = Uuid::new_v4();
uuid.to_string()
}
/// 检查管理员权限
pub async fn check_admin_privileges(&self) -> Result<AdminStatus, String> {
let os = Self::get_os_type();
match os.as_str() {
"windows" => self.check_windows_admin().await,
"macos" => self.check_macos_admin().await,
"linux" => self.check_linux_admin().await,
_ => Ok(AdminStatus {
is_admin: false,
platform: os,
elevation_method: None,
check_success: false,
}),
}
}
/// 验证机器码格式
pub fn validate_machine_id(&self, machine_id: &str) -> Result<MachineIdValidation, String> {
let detected_format = MachineIdFormat::detect(machine_id);
match detected_format.format_machine_id(machine_id) {
Ok(formatted) => Ok(MachineIdValidation {
is_valid: true,
detected_format,
error_message: None,
formatted_id: Some(formatted),
}),
Err(error) => Ok(MachineIdValidation {
is_valid: false,
detected_format,
error_message: Some(error),
formatted_id: None,
}),
}
}
/// 备份机器码到文件
pub async fn backup_machine_id(&self, file_path: &str) -> Result<bool, String> {
let current_info = self.get_current_machine_id().await?;
let backup = MachineIdBackup {
machine_id: current_info.current_id.clone(),
timestamp: chrono::Utc::now().timestamp(),
platform: current_info.platform,
format: current_info.format_type,
description: Some("Manual backup".to_string()),
};
let backup_json = serde_json::to_string_pretty(&backup)
.map_err(|e| format!("Failed to serialize backup: {}", e))?;
let write_result = fs::write(file_path, backup_json)
.map_err(|e| format!("Failed to write backup file: {}", e));
// 添加到历史记录
if write_result.is_ok() {
if let Err(e) =
self.add_history_record(current_info.current_id, Some(file_path.to_string()))
{
tracing::warn!("Failed to add backup history record: {}", e);
}
}
write_result.map(|_| true)
}
/// 从文件恢复机器码
pub async fn restore_machine_id(&self, file_path: &str) -> Result<MachineIdResult, String> {
let backup_content = fs::read_to_string(file_path)
.map_err(|e| format!("Failed to read backup file: {}", e))?;
let backup: MachineIdBackup = serde_json::from_str(&backup_content)
.map_err(|e| format!("Failed to parse backup file: {}", e))?;
self.set_machine_id(&backup.machine_id).await
}
// === Windows 特定实现 ===
#[cfg(target_os = "windows")]
async fn get_windows_machine_id(
&self,
requires_admin: bool,
backup_exists: bool,
) -> Result<MachineIdInfo, String> {
let machine_id = self.read_windows_registry_machine_id()?;
Ok(MachineIdInfo {
current_id: machine_id,
original_id: self.get_original_backup(),
platform: "Windows".to_string(),
can_modify: true,
requires_admin,
backup_exists,
format_type: MachineIdFormat::Uuid,
})
}
#[cfg(not(target_os = "windows"))]
async fn get_windows_machine_id(
&self,
requires_admin: bool,
backup_exists: bool,
) -> Result<MachineIdInfo, String> {
Err("Windows machine ID not supported on this platform".to_string())
}
#[cfg(target_os = "windows")]
fn read_windows_registry_machine_id(&self) -> Result<String, String> {
use winreg::{enums::*, RegKey};
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
let key = hklm.open_subkey_with_flags("SOFTWARE\\Microsoft\\Cryptography", KEY_READ)
.map_err(|e| {
tracing::error!("Failed to open Windows registry key: {}", e);
format!("Failed to access Windows machine ID registry key: {}. This may indicate a system configuration issue.", e)
})?;
let machine_guid: String = key.get_value("MachineGuid")
.map_err(|e| {
tracing::error!("Failed to read MachineGuid from registry: {}", e);
format!("Failed to read Windows MachineGuid: {}. The registry value may be missing or corrupted.", e)
})?;
// 验证读取到的GUID格式
let cleaned_guid = machine_guid.trim();
if cleaned_guid.is_empty() {
return Err(
"Windows MachineGuid is empty. This indicates a system configuration problem."
.to_string(),
);
}
// 基本格式验证
let format = MachineIdFormat::detect(cleaned_guid);
if matches!(format, MachineIdFormat::Unknown) {
tracing::warn!(
"Windows MachineGuid has unexpected format: {}",
cleaned_guid
);
// 不返回错误,因为某些Windows系统可能有非标准格式
}
Ok(cleaned_guid.to_string())
}
#[cfg(target_os = "windows")]
async fn set_windows_machine_id(&self, new_id: &str) -> Result<MachineIdResult, String> {
// Windows 需要管理员权限来修改注册表
let admin_status = self.check_windows_admin().await?;
if !admin_status.is_admin {
return Ok(MachineIdResult {
success: false,
message: "Administrator privileges required to modify Windows machine ID"
.to_string(),
requires_restart: false,
requires_admin: true,
new_machine_id: None,
});
}
match self.write_windows_registry_machine_id(new_id) {
Ok(_) => Ok(MachineIdResult {
success: true,
message: "Machine ID updated successfully. Restart may be required for some applications.".to_string(),
requires_restart: true,
requires_admin: false,
new_machine_id: Some(new_id.to_string()),
}),
Err(e) => Ok(MachineIdResult {
success: false,
message: e,
requires_restart: false,
requires_admin: true,
new_machine_id: None,
}),
}
}
#[cfg(target_os = "windows")]
fn write_windows_registry_machine_id(&self, new_id: &str) -> Result<(), String> {
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
let (key, _) = hklm
.create_subkey("SOFTWARE\\Microsoft\\Cryptography")
.map_err(|e| format!("Failed to create/open registry key: {}", e))?;
key.set_value("MachineGuid", &new_id)
.map_err(|e| format!("Failed to set MachineGuid: {}", e))?;
Ok(())
}
#[cfg(not(target_os = "windows"))]
fn write_windows_registry_machine_id(&self, _new_id: &str) -> Result<(), String> {
Err("Windows registry modification not supported on this platform".to_string())
}
#[cfg(not(target_os = "windows"))]
fn read_windows_registry_machine_id(&self) -> Result<String, String> {
Err("Windows registry reading not supported on this platform".to_string())
}
#[cfg(target_os = "windows")]
async fn check_windows_admin(&self) -> Result<AdminStatus, String> {
// 尝试多种方法检查管理员权限
// 方法1:尝试打开需要管理员权限的注册表项
let registry_check = {
use winreg::{enums::*, RegKey};
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
hklm.open_subkey_with_flags("SOFTWARE\\Microsoft\\Cryptography", KEY_WRITE)
.is_ok()
};
// 方法2:运行 net session 命令作为后备
let net_session_check = Command::new("net")
.args(&["session"])
.output()
.map(|result| result.status.success())
.unwrap_or(false);
// 方法3:使用 Windows API 检查 (需要额外实现)
// 这里可以添加更复杂的 Windows API 调用
let is_admin = registry_check || net_session_check;
Ok(AdminStatus {
is_admin,
platform: "Windows".to_string(),
elevation_method: Some("Right-click and select 'Run as administrator'".to_string()),
check_success: true,
})
}
#[cfg(not(target_os = "windows"))]
async fn check_windows_admin(&self) -> Result<AdminStatus, String> {
Err("Windows admin check not supported on this platform".to_string())
}
#[cfg(not(target_os = "windows"))]
async fn set_windows_machine_id(&self, _new_id: &str) -> Result<MachineIdResult, String> {
Ok(MachineIdResult {
success: false,
message: "Windows machine ID modification not supported on this platform".to_string(),
requires_restart: false,
requires_admin: false,
new_machine_id: None,
})
}
// === macOS 特定实现 ===
#[cfg(target_os = "macos")]
async fn get_macos_machine_id(
&self,
requires_admin: bool,
backup_exists: bool,
) -> Result<MachineIdInfo, String> {
let machine_id = self.read_macos_machine_id().await?;
Ok(MachineIdInfo {
current_id: machine_id,
original_id: self.get_original_backup(),
platform: "macOS".to_string(),
can_modify: true, // macOS 使用应用层覆盖
requires_admin,
backup_exists,
format_type: MachineIdFormat::Uuid,
})
}
#[cfg(not(target_os = "macos"))]
async fn get_macos_machine_id(
&self,
requires_admin: bool,
backup_exists: bool,
) -> Result<MachineIdInfo, String> {
Err("macOS machine ID not supported on this platform".to_string())
}
#[cfg(target_os = "macos")]
async fn read_macos_machine_id(&self) -> Result<String, String> {
// 首先检查是否有应用层覆盖
if let Ok(override_id) = self.read_macos_override() {
return Ok(override_id);
}
// 读取系统原始 UUID
let output = Command::new("ioreg")
.args(&["-rd1", "-c", "IOPlatformExpertDevice"])
.output()
.map_err(|e| format!("Failed to execute ioreg: {}", e))?;
let output_str = String::from_utf8_lossy(&output.stdout);
for line in output_str.lines() {
if line.contains("IOPlatformUUID") {
if let Some(uuid_part) = line.split('"').nth(3) {
return Ok(uuid_part.to_string());
}
}
}
Err("Failed to find IOPlatformUUID in ioreg output".to_string())
}
#[cfg(target_os = "macos")]
async fn set_macos_machine_id(&self, new_id: &str) -> Result<MachineIdResult, String> {
// macOS 使用应用层覆盖文件
match self.write_macos_override(new_id) {
Ok(_) => Ok(MachineIdResult {
success: true,
message: "Machine ID override created successfully. Applications using this override will see the new ID.".to_string(),
requires_restart: false,
requires_admin: false,
new_machine_id: Some(new_id.to_string()),
}),
Err(e) => Ok(MachineIdResult {
success: false,
message: e,
requires_restart: false,
requires_admin: false,
new_machine_id: None,
}),
}
}
#[cfg(target_os = "macos")]
fn read_macos_override(&self) -> Result<String, String> {
let override_file = dirs::data_dir()
.ok_or("Failed to get app data directory")?
.join("proxycast")
.join("machine-id-override");
fs::read_to_string(override_file).map_err(|e| format!("No override file found: {}", e))
}
#[cfg(target_os = "macos")]
fn write_macos_override(&self, new_id: &str) -> Result<(), String> {
let override_dir = dirs::data_dir()
.ok_or("Failed to get app data directory")?
.join("proxycast");
fs::create_dir_all(&override_dir)
.map_err(|e| format!("Failed to create override directory: {}", e))?;
let override_file = override_dir.join("machine-id-override");
fs::write(override_file, new_id)
.map_err(|e| format!("Failed to write override file: {}", e))?;
Ok(())
}
#[cfg(target_os = "macos")]
async fn check_macos_admin(&self) -> Result<AdminStatus, String> {
// macOS 不需要管理员权限(使用应用层覆盖)
Ok(AdminStatus {
is_admin: true, // 应用层覆盖不需要管理员权限
platform: "macOS".to_string(),
elevation_method: None,
check_success: true,
})
}
// === Linux 特定实现 ===
#[cfg(target_os = "linux")]
async fn get_linux_machine_id(
&self,
requires_admin: bool,
backup_exists: bool,
) -> Result<MachineIdInfo, String> {
let machine_id = self.read_linux_machine_id()?;
Ok(MachineIdInfo {
current_id: machine_id,
original_id: self.get_original_backup(),
platform: "Linux".to_string(),
can_modify: true,
requires_admin,
backup_exists,
format_type: MachineIdFormat::Hex32,
})
}
#[cfg(not(target_os = "linux"))]
async fn get_linux_machine_id(
&self,
requires_admin: bool,
backup_exists: bool,
) -> Result<MachineIdInfo, String> {
Err("Linux machine ID not supported on this platform".to_string())
}
#[cfg(target_os = "linux")]
fn read_linux_machine_id(&self) -> Result<String, String> {
// 尝试读取 /etc/machine-id
if let Ok(content) = fs::read_to_string("/etc/machine-id") {
return Ok(content.trim().to_string());
}
// 尝试读取 /var/lib/dbus/machine-id
if let Ok(content) = fs::read_to_string("/var/lib/dbus/machine-id") {
return Ok(content.trim().to_string());
}
Err(
"Failed to read Linux machine ID from /etc/machine-id or /var/lib/dbus/machine-id"
.to_string(),
)
}
#[cfg(target_os = "linux")]
async fn set_linux_machine_id(&self, new_id: &str) -> Result<MachineIdResult, String> {
let admin_status = self.check_linux_admin().await?;
if !admin_status.is_admin {
return Ok(MachineIdResult {
success: false,
message: "Root privileges required to modify Linux machine ID".to_string(),
requires_restart: false,
requires_admin: true,
new_machine_id: None,
});
}
// 转换为 32 位十六进制格式(去除连字符)
let hex_id = new_id.replace("-", "").to_lowercase();
// 尝试写入 /etc/machine-id
match fs::write("/etc/machine-id", &hex_id) {
Ok(_) => {
// 同时更新 /var/lib/dbus/machine-id(如果存在)
let _ = fs::write("/var/lib/dbus/machine-id", &hex_id);
Ok(MachineIdResult {
success: true,
message: "Machine ID updated successfully. Some services may require restart."
.to_string(),
requires_restart: true,
requires_admin: false,
new_machine_id: Some(hex_id),
})
}
Err(e) => Ok(MachineIdResult {
success: false,
message: format!("Failed to write machine ID: {}", e),
requires_restart: false,
requires_admin: true,
new_machine_id: None,
}),
}
}
#[cfg(target_os = "linux")]
async fn check_linux_admin(&self) -> Result<AdminStatus, String> {
let output = Command::new("id")
.args(&["-u"])
.output()
.map_err(|e| format!("Failed to check user ID: {}", e))?;
let uid_str = String::from_utf8_lossy(&output.stdout);
let uid: u32 = uid_str
.trim()
.parse()
.map_err(|e| format!("Failed to parse user ID: {}", e))?;
Ok(AdminStatus {
is_admin: uid == 0,
platform: "Linux".to_string(),
elevation_method: Some("sudo or run as root".to_string()),
check_success: true,
})
}
#[cfg(not(target_os = "linux"))]
async fn check_linux_admin(&self) -> Result<AdminStatus, String> {
Err("Linux admin check not supported on this platform".to_string())
}
#[cfg(not(target_os = "linux"))]
async fn set_linux_machine_id(&self, _new_id: &str) -> Result<MachineIdResult, String> {
Ok(MachineIdResult {
success: false,
message: "Linux machine ID modification not supported on this platform".to_string(),
requires_restart: false,
requires_admin: false,
new_machine_id: None,
})
}
// === 通用辅助方法 ===
async fn check_requires_admin(&self) -> Result<bool, String> {
let admin_status = self.check_admin_privileges().await?;
Ok(!admin_status.is_admin && admin_status.check_success)
}
fn check_backup_exists(&self) -> bool {
let backup_file = self.backup_dir.join("original_machine_id.json");
backup_file.exists()
}
fn get_original_backup(&self) -> Option<String> {
let backup_file = self.backup_dir.join("original_machine_id.json");
if let Ok(content) = fs::read_to_string(backup_file) {
if let Ok(backup) = serde_json::from_str::<MachineIdBackup>(&content) {
return Some(backup.machine_id);
}
}
None
}
async fn create_auto_backup(&self) -> Result<(), String> {
// 如果已有备份则跳过
if self.check_backup_exists() {
return Ok(());
}
let current_info = self.get_current_machine_id().await?;
let backup_file = self.backup_dir.join("original_machine_id.json");
let backup = MachineIdBackup {
machine_id: current_info.current_id,
timestamp: chrono::Utc::now().timestamp(),
platform: current_info.platform,
format: current_info.format_type,
description: Some("Auto backup before first modification".to_string()),
};
let backup_json = serde_json::to_string_pretty(&backup)
.map_err(|e| format!("Failed to serialize backup: {}", e))?;
fs::write(backup_file, backup_json)
.map_err(|e| format!("Failed to create auto backup: {}", e))?;
Ok(())
}
// === 历史记录管理 ===
/// 加载历史记录
fn load_history(&self) -> Result<Vec<MachineIdHistory>, String> {
if !self.history_file.exists() {
return Ok(vec![]);
}
let content = fs::read_to_string(&self.history_file)
.map_err(|e| format!("Failed to read history file: {}", e))?;
let history: Vec<MachineIdHistory> =
serde_json::from_str(&content).unwrap_or_else(|_| vec![]);
Ok(history)
}
/// 保存历史记录
fn save_history(&self, history: &[MachineIdHistory]) -> Result<(), String> {
let history_json = serde_json::to_string_pretty(history)
.map_err(|e| format!("Failed to serialize history: {}", e))?;
fs::write(&self.history_file, history_json)
.map_err(|e| format!("Failed to save history: {}", e))?;
Ok(())
}
/// 添加历史记录
pub fn add_history_record(
&self,
machine_id: String,
backup_path: Option<String>,
) -> Result<(), String> {
let mut history = self.load_history().unwrap_or_else(|_| vec![]);
let record = MachineIdHistory {
id: uuid::Uuid::new_v4().to_string(),
machine_id,
timestamp: chrono::Utc::now().to_rfc3339(),
platform: Self::get_os_type(),
backup_path,
};
history.push(record);
// 保留最近100条记录
if history.len() > 100 {
history.drain(0..history.len() - 100);
}
self.save_history(&history)?;
Ok(())
}
/// 获取历史记录
pub fn get_history(&self) -> Result<Vec<MachineIdHistory>, String> {
self.load_history()
}
}
+1
View File
@@ -1,6 +1,7 @@
pub mod backup_service;
pub mod kiro_event_service;
pub mod live_sync;
pub mod machine_id_service;
pub mod mcp_service;
pub mod mcp_sync;
pub mod prompt_service;
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ProxyCast",
"version": "0.19.0",
"version": "0.20.0",
"identifier": "com.proxycast.app",
"build": {
"beforeDevCommand": "npm run dev",
+6
View File
@@ -7,6 +7,8 @@ import { ConfigManagementPage } from "./components/config/ConfigManagementPage";
import { FlowMonitorPage } from "./pages";
import { ToolsPage } from "./components/tools/ToolsPage";
import { BrowserInterceptorTool } from "./components/tools/browser-interceptor/BrowserInterceptorTool";
import { MachineIdTool } from "./components/tools/machine-id/MachineIdTool";
import { Toaster } from "./components/ui/sonner";
import { flowEventManager } from "./lib/flowEventManager";
type Page =
@@ -16,6 +18,7 @@ type Page =
| "flow-monitor"
| "tools"
| "browser-interceptor"
| "machine-id"
| "settings";
function App() {
@@ -41,6 +44,8 @@ function App() {
return <ToolsPage onNavigate={setCurrentPage} />;
case "browser-interceptor":
return <BrowserInterceptorTool onNavigate={setCurrentPage} />;
case "machine-id":
return <MachineIdTool onNavigate={setCurrentPage} />;
case "settings":
return <SettingsPage />;
default:
@@ -52,6 +57,7 @@ function App() {
<div className="flex h-screen bg-background">
<Sidebar currentPage={currentPage} onNavigate={setCurrentPage} />
<main className="flex-1 overflow-auto p-6">{renderPage()}</main>
<Toaster />
</div>
);
}
+1
View File
@@ -15,6 +15,7 @@ type Page =
| "flow-monitor"
| "tools"
| "browser-interceptor"
| "machine-id"
| "settings";
interface SidebarProps {
+14 -2
View File
@@ -1,5 +1,5 @@
import React, { useState } from "react";
import { Globe, Plus, Settings, Activity } from "lucide-react";
import { Globe, Plus, Settings, Activity, Cpu } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
Card,
@@ -19,6 +19,7 @@ interface ToolsPageProps {
| "flow-monitor"
| "tools"
| "browser-interceptor"
| "machine-id"
| "settings",
) => void;
}
@@ -87,6 +88,10 @@ export function ToolsPage({ onNavigate }: ToolsPageProps) {
onNavigate("browser-interceptor");
};
const handleMachineIdClick = () => {
onNavigate("machine-id");
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
@@ -96,7 +101,7 @@ export function ToolsPage({ onNavigate }: ToolsPageProps) {
ProxyCast 提供的实用工具集合
</p>
</div>
<Badge variant="outline">1 个工具</Badge>
<Badge variant="outline">2 个工具</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@@ -108,6 +113,13 @@ export function ToolsPage({ onNavigate }: ToolsPageProps) {
onClick={handleBrowserInterceptorClick}
/>
<ToolCard
title="机器码管理工具"
description="查看、修改和管理系统机器码,支持跨平台操作和备份恢复"
icon={<Cpu className="w-6 h-6 text-primary" />}
onClick={handleMachineIdClick}
/>
{/* 未来可以添加更多工具 */}
<ToolCard
title="网络监控工具"
@@ -0,0 +1,225 @@
import { useState, useEffect, useCallback } from "react";
import { History, Clock, FileText, AlertTriangle } from "lucide-react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { MachineIdHistory } from "@/lib/api/machineId";
import { machineIdApi } from "@/lib/api/machineId";
import { useToast } from "@/hooks/use-toast";
interface MachineIdHistoryPanelProps {
_onRefresh?: () => void;
}
export function MachineIdHistoryPanel({
_onRefresh,
}: MachineIdHistoryPanelProps) {
const { toast } = useToast();
const [history, setHistory] = useState<MachineIdHistory[]>([]);
const [loading, setLoading] = useState(true);
const loadHistory = useCallback(async () => {
try {
setLoading(true);
const historyData = await machineIdApi.getMachineIdHistory();
setHistory(historyData);
} catch (error) {
console.error("加载历史记录失败:", error);
toast({
variant: "destructive",
title: "加载失败",
description: "无法加载机器码历史记录",
});
} finally {
setLoading(false);
}
}, [toast]);
useEffect(() => {
loadHistory();
}, [loadHistory]);
const formatTimestamp = (timestamp: string) => {
try {
return new Date(timestamp).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
} catch {
return timestamp;
}
};
const getPlatformIcon = (platform: string) => {
switch (platform.toLowerCase()) {
case "windows":
return "🪟";
case "macos":
return "🍎";
case "linux":
return "🐧";
default:
return "💻";
}
};
if (loading) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<History className="w-5 h-5 text-blue-500" />
<span>操作历史</span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center py-8">
<div className="text-center">
<History className="w-8 h-8 mx-auto mb-2 animate-spin text-blue-500" />
<p className="text-muted-foreground">加载历史记录中...</p>
</div>
</div>
</CardContent>
</Card>
);
}
return (
<div className="space-y-6">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center space-x-2">
<History className="w-5 h-5 text-blue-500" />
<span>操作历史</span>
</CardTitle>
<CardDescription>查看机器码的历史修改记录</CardDescription>
</div>
<Button
variant="outline"
onClick={loadHistory}
className="space-x-2"
>
<History className="w-4 h-4" />
<span>刷新</span>
</Button>
</div>
</CardHeader>
<CardContent>
{history.length === 0 ? (
<div className="text-center py-12">
<div className="w-24 h-24 mx-auto mb-4 bg-muted rounded-full flex items-center justify-center">
<History className="w-12 h-12 text-muted-foreground" />
</div>
<h3 className="text-lg font-semibold mb-2">暂无历史记录</h3>
<p className="text-muted-foreground mb-4">
当前还没有机器码操作历史记录
</p>
<div className="text-sm text-muted-foreground bg-muted/50 p-4 rounded-lg">
<p>💡 提示:历史记录功能正在开发中</p>
<p>完成后将记录所有机器码修改操作,包括:</p>
<ul className="list-disc list-inside mt-2 space-y-1">
<li>机器码修改时间</li>
<li>修改前后的值</li>
<li>操作平台信息</li>
<li>备份文件路径</li>
</ul>
</div>
</div>
) : (
<div className="space-y-4">
{history.map((record, index) => (
<Card
key={record.id || index}
className="border-l-4 border-l-blue-500"
>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-900/20 rounded-full flex items-center justify-center">
<span className="text-lg">
{getPlatformIcon(record.platform)}
</span>
</div>
<div>
<h4 className="font-semibold">机器码操作</h4>
<div className="flex items-center space-x-2 mt-1">
<Badge variant="outline" className="text-xs">
{record.platform}
</Badge>
<div className="flex items-center space-x-1 text-xs text-muted-foreground">
<Clock className="w-3 h-3" />
<span>{formatTimestamp(record.timestamp)}</span>
</div>
</div>
</div>
</div>
</div>
</CardHeader>
<CardContent className="pt-0 space-y-3">
<div className="grid gap-3">
<div>
<p className="text-sm text-muted-foreground mb-1">
机器码
</p>
<div className="font-mono text-sm bg-muted/50 p-2 rounded border">
{record.machine_id}
</div>
</div>
{record.backup_path && (
<div>
<p className="text-sm text-muted-foreground mb-1">
备份路径
</p>
<div className="flex items-center space-x-2 text-sm bg-muted/50 p-2 rounded border">
<FileText className="w-4 h-4 text-muted-foreground" />
<span className="font-mono">
{record.backup_path}
</span>
</div>
</div>
)}
</div>
</CardContent>
</Card>
))}
</div>
)}
</CardContent>
</Card>
{/* 说明信息 */}
<Card className="border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950/20">
<CardHeader>
<CardTitle className="flex items-center space-x-2 text-amber-800 dark:text-amber-300">
<AlertTriangle className="w-5 h-5" />
<span>关于历史记录</span>
</CardTitle>
</CardHeader>
<CardContent className="text-amber-700 dark:text-amber-400 space-y-2">
<p>历史记录功能目前处于开发阶段,暂时返回空记录。</p>
<p className="text-sm">完整实现后将包含:</p>
<ul className="list-disc list-inside text-sm space-y-1 ml-4">
<li>所有机器码修改操作的时间记录</li>
<li>修改前后的机器码值对比</li>
<li>操作系统和平台信息</li>
<li>相关备份文件的路径信息</li>
<li>操作结果和状态信息</li>
</ul>
</CardContent>
</Card>
</div>
);
}
@@ -0,0 +1,257 @@
import { useState } from "react";
import {
Copy,
Shield,
ShieldCheck,
AlertTriangle,
Monitor,
HardDrive,
} from "lucide-react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { useToast } from "@/hooks/use-toast";
import { MachineIdInfo, AdminStatus } from "@/lib/api/machineId";
import { machineIdApi, machineIdUtils } from "@/lib/api/machineId";
interface MachineIdInfoPanelProps {
machineIdInfo: MachineIdInfo | null;
adminStatus: AdminStatus | null;
onRefresh: () => void;
}
export function MachineIdInfoPanel({
machineIdInfo,
adminStatus,
onRefresh,
}: MachineIdInfoPanelProps) {
const { toast } = useToast();
const [copying, setCopying] = useState(false);
const handleCopyMachineId = async () => {
if (!machineIdInfo) return;
try {
setCopying(true);
await machineIdApi.copyMachineIdToClipboard(machineIdInfo.current_id);
toast({
title: "复制成功",
description: "机器码已复制到剪贴板",
});
} catch (error) {
console.error("复制失败:", error);
toast({
variant: "destructive",
title: "复制失败",
description: "无法复制机器码到剪贴板",
});
} finally {
setCopying(false);
}
};
if (!machineIdInfo || !adminStatus) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<AlertTriangle className="w-5 h-5 text-amber-500" />
<span>无法获取机器码信息</span>
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
无法加载机器码信息,请检查系统状态或权限设置。
</p>
<Button onClick={onRefresh} className="mt-4">
重新加载
</Button>
</CardContent>
</Card>
);
}
const getStatusBadge = () => {
if (!machineIdInfo.can_modify) {
return (
<Badge variant="secondary" className="space-x-1">
<Shield className="w-3 h-3" />
<span>只读</span>
</Badge>
);
}
if (machineIdInfo.requires_admin && !adminStatus.is_admin) {
return (
<Badge variant="destructive" className="space-x-1">
<AlertTriangle className="w-3 h-3" />
<span>需要管理员权限</span>
</Badge>
);
}
return (
<Badge variant="default" className="space-x-1">
<ShieldCheck className="w-3 h-3" />
<span>可修改</span>
</Badge>
);
};
const getFormatBadge = () => {
const color =
machineIdInfo.format_type === "uuid"
? "default"
: machineIdInfo.format_type === "hex32"
? "secondary"
: "outline";
return (
<Badge variant={color}>
{machineIdUtils.getFormatDisplayName(machineIdInfo.format_type)}
</Badge>
);
};
return (
<div className="space-y-6">
{/* 主要信息卡片 */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center space-x-2">
<HardDrive className="w-5 h-5 text-blue-500" />
<span>当前机器码</span>
</CardTitle>
<CardDescription className="mt-2">
系统唯一标识符,用于设备识别和授权验证
</CardDescription>
</div>
{getStatusBadge()}
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between p-4 bg-muted/50 rounded-lg">
<div className="space-y-1">
<p className="text-sm text-muted-foreground">机器码</p>
<p className="font-mono text-lg font-semibold">
{machineIdInfo.current_id}
</p>
</div>
<div className="flex items-center space-x-2">
{getFormatBadge()}
<Button
variant="outline"
size="sm"
onClick={handleCopyMachineId}
disabled={copying}
className="space-x-1"
>
<Copy className="w-4 h-4" />
<span>{copying ? "复制中..." : "复制"}</span>
</Button>
</div>
</div>
{machineIdInfo.original_id && (
<>
<Separator />
<div className="p-4 bg-amber-50 dark:bg-amber-950/20 rounded-lg border border-amber-200 dark:border-amber-800">
<div className="flex items-center space-x-2 mb-2">
<AlertTriangle className="w-4 h-4 text-amber-600" />
<p className="text-sm font-medium text-amber-800 dark:text-amber-300">
检测到机器码覆盖
</p>
</div>
<p className="text-sm text-amber-700 dark:text-amber-400 mb-2">
原始机器码:
</p>
<p className="font-mono text-sm text-amber-900 dark:text-amber-200 bg-amber-100 dark:bg-amber-900/30 p-2 rounded">
{machineIdInfo.original_id}
</p>
</div>
</>
)}
</CardContent>
</Card>
{/* 系统信息卡片 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Monitor className="w-5 h-5 text-green-500" />
<span>系统状态</span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-1">
<p className="text-sm text-muted-foreground">操作系统</p>
<p className="font-semibold">
{machineIdUtils.getPlatformDisplayName(machineIdInfo.platform)}
</p>
</div>
<div className="space-y-1">
<p className="text-sm text-muted-foreground">管理员权限</p>
<div className="flex items-center space-x-2">
{adminStatus.is_admin ? (
<Badge variant="default" className="space-x-1">
<ShieldCheck className="w-3 h-3" />
<span>已获取</span>
</Badge>
) : (
<Badge variant="secondary" className="space-x-1">
<Shield className="w-3 h-3" />
<span>未获取</span>
</Badge>
)}
</div>
</div>
<div className="space-y-1">
<p className="text-sm text-muted-foreground">备份状态</p>
<div className="flex items-center space-x-2">
{machineIdInfo.backup_exists ? (
<Badge variant="default">已备份</Badge>
) : (
<Badge variant="outline">未备份</Badge>
)}
</div>
</div>
</div>
</CardContent>
</Card>
{/* 权限提醒 */}
{machineIdInfo.requires_admin && !adminStatus.is_admin && (
<Card className="border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950/20">
<CardHeader>
<CardTitle className="flex items-center space-x-2 text-amber-800 dark:text-amber-300">
<AlertTriangle className="w-5 h-5" />
<span>权限提醒</span>
</CardTitle>
</CardHeader>
<CardContent className="text-amber-700 dark:text-amber-400">
<p className="mb-2">
在当前平台({machineIdInfo.platform})上修改机器码需要管理员权限。
</p>
{adminStatus.elevation_method && (
<p className="text-sm">
提升权限方法:{adminStatus.elevation_method}
</p>
)}
</CardContent>
</Card>
)}
</div>
);
}
@@ -0,0 +1,787 @@
import { useState } from "react";
import {
Save,
Shuffle,
ClipboardPaste,
Settings,
AlertTriangle,
CheckCircle,
FileUp,
FileDown,
Trash2,
FolderOpen,
RotateCcw,
} from "lucide-react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { useToast } from "@/hooks/use-toast";
import {
MachineIdInfo,
AdminStatus,
MachineIdValidation,
} from "@/lib/api/machineId";
import { machineIdApi, machineIdUtils } from "@/lib/api/machineId";
import { open, save } from "@tauri-apps/plugin-dialog";
interface MachineIdManagePanelProps {
machineIdInfo: MachineIdInfo | null;
adminStatus: AdminStatus | null;
onRefresh: () => void;
}
export function MachineIdManagePanel({
machineIdInfo,
adminStatus,
onRefresh,
}: MachineIdManagePanelProps) {
const { toast } = useToast();
// 机器码修改
const [newMachineId, setNewMachineId] = useState("");
const [validationResult, setValidationResult] =
useState<MachineIdValidation | null>(null);
const [_isValidating, setIsValidating] = useState(false);
const [isApplying, setIsApplying] = useState(false);
// 格式转换
const [formatInput, setFormatInput] = useState("");
const [formatTarget, setFormatTarget] = useState<"uuid" | "hex32">("uuid");
const [formatResult, setFormatResult] = useState("");
// 备份恢复
const [backupPath, setBackupPath] = useState("");
const [restorePath, setRestorePath] = useState("");
const [isBackingUp, setIsBackingUp] = useState(false);
const [isRestoring, setIsRestoring] = useState(false);
const canModify =
machineIdInfo?.can_modify &&
(!machineIdInfo.requires_admin || adminStatus?.is_admin);
const handleValidateMachineId = async () => {
if (!newMachineId.trim()) {
setValidationResult(null);
return;
}
try {
setIsValidating(true);
const result = await machineIdApi.validateMachineId(newMachineId.trim());
setValidationResult(result);
} catch (error) {
console.error("验证失败:", error);
toast({
variant: "destructive",
title: "验证失败",
description: "无法验证机器码格式",
});
} finally {
setIsValidating(false);
}
};
const handleApplyMachineId = async () => {
if (!validationResult?.is_valid) {
toast({
variant: "destructive",
title: "无法应用",
description: "机器码格式不正确",
});
return;
}
try {
setIsApplying(true);
const result = await machineIdApi.setMachineId(newMachineId.trim());
if (result.success) {
toast({
title: "机器码修改成功",
description: result.message,
});
setNewMachineId("");
setValidationResult(null);
onRefresh();
if (result.requires_restart) {
toast({
title: "重启提醒",
description: "部分应用程序可能需要重启才能识别新的机器码",
duration: 8000,
});
}
} else {
toast({
variant: "destructive",
title: "机器码修改失败",
description: result.message,
});
}
} catch (error) {
console.error("应用失败:", error);
toast({
variant: "destructive",
title: "应用失败",
description: "无法设置新的机器码",
});
} finally {
setIsApplying(false);
}
};
const handleGenerateRandom = async () => {
try {
const randomId = await machineIdApi.generateRandomMachineId();
setNewMachineId(randomId);
// 自动验证生成的机器码
const result = await machineIdApi.validateMachineId(randomId);
setValidationResult(result);
toast({
title: "随机机器码生成成功",
description: "已生成新的随机机器码",
});
} catch (error) {
console.error("生成失败:", error);
toast({
variant: "destructive",
title: "生成失败",
description: "无法生成随机机器码",
});
}
};
const handlePasteFromClipboard = async () => {
try {
const clipboardText = await machineIdApi.pasteMachineIdFromClipboard();
setNewMachineId(clipboardText);
// 自动验证粘贴的机器码
const result = await machineIdApi.validateMachineId(clipboardText);
setValidationResult(result);
toast({
title: "从剪贴板粘贴成功",
description: "已从剪贴板粘贴机器码",
});
} catch (error) {
console.error("粘贴失败:", error);
toast({
variant: "destructive",
title: "粘贴失败",
description: "剪贴板中没有有效的机器码",
});
}
};
const handleFormatConvert = async () => {
if (!formatInput.trim()) return;
try {
const result = await machineIdApi.convertMachineIdFormat(
formatInput.trim(),
formatTarget,
);
setFormatResult(result);
toast({
title: "格式转换成功",
description: `已转换为${formatTarget === "uuid" ? "UUID" : "32位十六进制"}格式`,
});
} catch (error) {
console.error("转换失败:", error);
toast({
variant: "destructive",
title: "转换失败",
description: "无法转换机器码格式,请检查输入是否正确",
});
}
};
const handleBackup = async () => {
if (!backupPath.trim()) return;
try {
setIsBackingUp(true);
const success = await machineIdApi.backupMachineIdToFile(
backupPath.trim(),
);
if (success) {
toast({
title: "备份成功",
description: "机器码已备份到指定文件",
});
setBackupPath("");
onRefresh();
} else {
toast({
variant: "destructive",
title: "备份失败",
description: "无法创建备份文件",
});
}
} catch (error) {
console.error("备份失败:", error);
toast({
variant: "destructive",
title: "备份失败",
description: "备份操作失败",
});
} finally {
setIsBackingUp(false);
}
};
const handleSelectBackupFile = async () => {
try {
const selected = await save({
title: "保存机器码备份文件",
defaultPath: `machine_id_backup_${new Date().toISOString().slice(0, 10)}.json`,
filters: [
{
name: "JSON文件",
extensions: ["json"],
},
],
});
if (selected && typeof selected === "string") {
setBackupPath(selected);
toast({
title: "保存位置已选择",
description: "备份文件保存路径已设置",
});
}
} catch (error) {
console.error("文件保存对话框失败:", error);
toast({
variant: "destructive",
title: "文件选择失败",
description: "无法打开文件保存对话框",
});
}
};
const handleSelectRestoreFile = async () => {
try {
const selected = await open({
title: "选择机器码备份文件",
filters: [
{
name: "JSON文件",
extensions: ["json"],
},
],
multiple: false,
});
if (selected && typeof selected === "string") {
setRestorePath(selected);
toast({
title: "文件已选择",
description: "备份文件路径已设置",
});
}
} catch (error) {
console.error("文件选择失败:", error);
toast({
variant: "destructive",
title: "文件选择失败",
description: "无法打开文件选择对话框",
});
}
};
const handleRestore = async () => {
if (!restorePath.trim()) return;
try {
setIsRestoring(true);
const result = await machineIdApi.restoreMachineIdFromFile(
restorePath.trim(),
);
if (result.success) {
toast({
title: "恢复成功",
description: result.message,
});
setRestorePath("");
onRefresh();
if (result.requires_restart) {
toast({
title: "重启提醒",
description: "部分应用程序可能需要重启才能识别恢复的机器码",
duration: 8000,
});
}
} else {
toast({
variant: "destructive",
title: "恢复失败",
description: result.message,
});
}
} catch (error) {
console.error("恢复失败:", error);
toast({
variant: "destructive",
title: "恢复失败",
description: "恢复操作失败",
});
} finally {
setIsRestoring(false);
}
};
const handleRestoreOriginal = async () => {
if (!machineIdInfo?.original_id) return;
try {
setIsRestoring(true);
const result = await machineIdApi.setMachineId(machineIdInfo.original_id);
if (result.success) {
toast({
title: "恢复成功",
description: "已恢复到原始机器码",
});
onRefresh();
if (result.requires_restart) {
toast({
title: "重启提醒",
description: "部分应用程序可能需要重启才能识别恢复的机器码",
duration: 8000,
});
}
} else {
toast({
variant: "destructive",
title: "恢复失败",
description: result.message,
});
}
} catch (error) {
console.error("恢复原始机器码失败:", error);
toast({
variant: "destructive",
title: "恢复失败",
description: "无法恢复到原始机器码",
});
} finally {
setIsRestoring(false);
}
};
const handleClearOverride = async () => {
try {
const result = await machineIdApi.clearMachineIdOverride();
if (result.success) {
toast({
title: "清除成功",
description: result.message,
});
onRefresh();
} else {
toast({
variant: "destructive",
title: "清除失败",
description: result.message,
});
}
} catch (error) {
console.error("清除失败:", error);
toast({
variant: "destructive",
title: "清除失败",
description: "清除覆盖失败",
});
}
};
if (!machineIdInfo || !adminStatus) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<AlertTriangle className="w-5 h-5 text-amber-500" />
<span>无法加载管理功能</span>
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
无法加载机器码管理功能,请检查系统状态。
</p>
</CardContent>
</Card>
);
}
return (
<div className="space-y-6">
{/* 权限状态提醒 */}
{!canModify && (
<Card className="border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950/20">
<CardHeader>
<CardTitle className="flex items-center space-x-2 text-amber-800 dark:text-amber-300">
<AlertTriangle className="w-5 h-5" />
<span>权限不足</span>
</CardTitle>
</CardHeader>
<CardContent className="text-amber-700 dark:text-amber-400">
<p>
{!machineIdInfo.can_modify
? "当前平台不支持机器码修改"
: "需要管理员权限才能修改机器码"}
</p>
</CardContent>
</Card>
)}
{/* 机器码修改 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Settings className="w-5 h-5 text-blue-500" />
<span>修改机器码</span>
</CardTitle>
<CardDescription>
输入新的机器码来替换当前的系统标识符
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="new-machine-id">新机器码</Label>
<div className="flex space-x-2">
<Input
id="new-machine-id"
placeholder="输入新的机器码(UUID或32位十六进制)"
value={newMachineId}
onChange={(e) => {
setNewMachineId(e.target.value);
setValidationResult(null);
}}
onBlur={handleValidateMachineId}
disabled={!canModify}
className="font-mono"
/>
<Button
variant="outline"
onClick={handleGenerateRandom}
disabled={!canModify}
className="space-x-1 shrink-0"
>
<Shuffle className="w-4 h-4" />
<span>随机生成</span>
</Button>
<Button
variant="outline"
onClick={handlePasteFromClipboard}
disabled={!canModify}
className="space-x-1 shrink-0"
>
<ClipboardPaste className="w-4 h-4" />
<span>粘贴</span>
</Button>
</div>
</div>
{validationResult && (
<div
className={`p-3 rounded-lg border ${
validationResult.is_valid
? "border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/20"
: "border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950/20"
}`}
>
<div className="flex items-center space-x-2">
{validationResult.is_valid ? (
<CheckCircle className="w-4 h-4 text-green-600" />
) : (
<AlertTriangle className="w-4 h-4 text-red-600" />
)}
<span
className={`text-sm font-medium ${
validationResult.is_valid
? "text-green-800 dark:text-green-300"
: "text-red-800 dark:text-red-300"
}`}
>
{validationResult.is_valid ? "格式验证通过" : "格式验证失败"}
</span>
</div>
{validationResult.is_valid && validationResult.formatted_id && (
<div className="mt-2">
<p className="text-sm text-muted-foreground">格式化后:</p>
<p className="font-mono text-sm bg-background p-2 rounded border mt-1">
{validationResult.formatted_id}
</p>
</div>
)}
{!validationResult.is_valid && validationResult.error_message && (
<p className="text-sm text-red-700 dark:text-red-400 mt-1">
{validationResult.error_message}
</p>
)}
<div className="mt-2">
<Badge
variant={
validationResult.detected_format === "uuid"
? "default"
: validationResult.detected_format === "hex32"
? "secondary"
: "outline"
}
>
检测格式:
{machineIdUtils.getFormatDisplayName(
validationResult.detected_format,
)}
</Badge>
</div>
</div>
)}
<Button
onClick={handleApplyMachineId}
disabled={!canModify || !validationResult?.is_valid || isApplying}
className="w-full space-x-2"
>
<Save className="w-4 h-4" />
<span>{isApplying ? "应用中..." : "应用机器码"}</span>
</Button>
</CardContent>
</Card>
{/* 格式转换工具 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Settings className="w-5 h-5 text-green-500" />
<span>格式转换</span>
</CardTitle>
<CardDescription>
在UUID格式和32位十六进制格式之间转换
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="format-input">源机器码</Label>
<Input
id="format-input"
placeholder="输入要转换的机器码"
value={formatInput}
onChange={(e) => setFormatInput(e.target.value)}
className="font-mono"
/>
</div>
<div className="space-y-2">
<Label>目标格式</Label>
<RadioGroup
value={formatTarget}
onValueChange={(value: string) =>
setFormatTarget(value as "uuid" | "hex32")
}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="uuid" id="uuid" />
<Label htmlFor="uuid">
UUID 格式 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="hex32" id="hex32" />
<Label htmlFor="hex32">
32位十六进制 (xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
</Label>
</div>
</RadioGroup>
</div>
<Button
onClick={handleFormatConvert}
disabled={!formatInput.trim()}
className="w-full space-x-2"
>
<Settings className="w-4 h-4" />
<span>转换格式</span>
</Button>
{formatResult && (
<div className="p-3 bg-muted/50 rounded-lg">
<p className="text-sm text-muted-foreground mb-1">转换结果:</p>
<p className="font-mono bg-background p-2 rounded border">
{formatResult}
</p>
</div>
)}
</CardContent>
</Card>
{/* 备份和恢复 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<FileDown className="w-5 h-5 text-purple-500" />
<span>备份与恢复</span>
</CardTitle>
<CardDescription>备份当前机器码或从备份文件恢复</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* 备份 */}
<div className="space-y-3">
<h4 className="font-medium">备份机器码</h4>
<div className="flex space-x-2">
<Input
placeholder="选择保存位置..."
value={backupPath}
onChange={(e) => setBackupPath(e.target.value)}
disabled={!canModify}
className="flex-1"
/>
<Button
variant="outline"
onClick={handleSelectBackupFile}
disabled={!canModify}
className="space-x-1 shrink-0"
>
<FolderOpen className="w-4 h-4" />
<span>选择位置</span>
</Button>
<Button
variant="outline"
onClick={handleBackup}
disabled={!canModify || !backupPath.trim() || isBackingUp}
className="space-x-1 shrink-0"
>
<FileDown className="w-4 h-4" />
<span>{isBackingUp ? "备份中..." : "备份"}</span>
</Button>
</div>
</div>
<Separator />
{/* 恢复 */}
<div className="space-y-3">
<h4 className="font-medium">恢复机器码</h4>
{/* 恢复到原始机器码 */}
{machineIdInfo?.original_id && (
<div className="p-3 bg-blue-50 dark:bg-blue-950/20 rounded-lg border border-blue-200 dark:border-blue-800">
<div className="flex items-center justify-between">
<div>
<h5 className="font-medium text-blue-800 dark:text-blue-300">
恢复到原始机器码
</h5>
<p className="text-sm text-blue-700 dark:text-blue-400 mt-1">
将机器码恢复到首次备份的原始值
</p>
<p className="text-xs text-blue-600 dark:text-blue-500 font-mono mt-2">
{machineIdInfo.original_id}
</p>
</div>
<Button
variant="outline"
onClick={handleRestoreOriginal}
disabled={!canModify || isRestoring}
className="space-x-1 shrink-0 border-blue-300 text-blue-700 hover:bg-blue-100"
>
<RotateCcw className="w-4 h-4" />
<span>{isRestoring ? "恢复中..." : "恢复原始"}</span>
</Button>
</div>
</div>
)}
{/* 从文件恢复 */}
<div className="space-y-2">
<h5 className="text-sm font-medium text-muted-foreground">
从备份文件恢复
</h5>
<div className="flex space-x-2">
<Input
placeholder="选择备份文件..."
value={restorePath}
onChange={(e) => setRestorePath(e.target.value)}
disabled={!canModify}
className="flex-1"
/>
<Button
variant="outline"
onClick={handleSelectRestoreFile}
disabled={!canModify}
className="space-x-1 shrink-0"
>
<FolderOpen className="w-4 h-4" />
<span>选择文件</span>
</Button>
<Button
variant="outline"
onClick={handleRestore}
disabled={!canModify || !restorePath.trim() || isRestoring}
className="space-x-1 shrink-0"
>
<FileUp className="w-4 h-4" />
<span>{isRestoring ? "恢复中..." : "恢复"}</span>
</Button>
</div>
</div>
</div>
</CardContent>
</Card>
{/* macOS 特殊操作 */}
{machineIdInfo.platform.toLowerCase() === "macos" &&
machineIdInfo.original_id && (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Trash2 className="w-5 h-5 text-red-500" />
<span>macOS 特殊操作</span>
</CardTitle>
<CardDescription>
清除机器码覆盖,恢复原始系统机器码
</CardDescription>
</CardHeader>
<CardContent>
<div className="p-4 bg-amber-50 dark:bg-amber-950/20 rounded-lg border border-amber-200 dark:border-amber-800">
<div className="flex items-center space-x-2 mb-3">
<AlertTriangle className="w-4 h-4 text-amber-600" />
<p className="text-sm font-medium text-amber-800 dark:text-amber-300">
注意:此操作将删除当前的机器码覆盖
</p>
</div>
<p className="text-sm text-amber-700 dark:text-amber-400 mb-3">
这将使系统恢复到原始机器码:{machineIdInfo.original_id}
</p>
<Button
variant="destructive"
onClick={handleClearOverride}
className="space-x-2"
>
<Trash2 className="w-4 h-4" />
<span>清除机器码覆盖</span>
</Button>
</div>
</CardContent>
</Card>
)}
</div>
);
}
@@ -0,0 +1,346 @@
import {
Cpu,
Shield,
Server,
AlertTriangle,
CheckCircle,
Info,
} from "lucide-react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { SystemInfo, AdminStatus } from "@/lib/api/machineId";
import { machineIdUtils } from "@/lib/api/machineId";
interface MachineIdSystemPanelProps {
systemInfo: SystemInfo | null;
adminStatus: AdminStatus | null;
onRefresh: () => void;
}
export function MachineIdSystemPanel({
systemInfo,
adminStatus,
}: MachineIdSystemPanelProps) {
if (!systemInfo || !adminStatus) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<AlertTriangle className="w-5 h-5 text-amber-500" />
<span>无法获取系统信息</span>
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
无法加载系统信息,请检查系统状态。
</p>
</CardContent>
</Card>
);
}
const getPlatformIcon = (os: string) => {
switch (os.toLowerCase()) {
case "windows":
return "🪟";
case "macos":
return "🍎";
case "linux":
return "🐧";
default:
return "💻";
}
};
const getArchIcon = (arch: string) => {
switch (arch.toLowerCase()) {
case "x86_64":
case "amd64":
return "🖥️";
case "aarch64":
case "arm64":
return "📱";
case "x86":
case "i386":
return "🖧";
default:
return "🔧";
}
};
const getSupportBadge = (canRead: boolean, canWrite: boolean) => {
if (canRead && canWrite) {
return (
<Badge variant="default" className="space-x-1">
<CheckCircle className="w-3 h-3" />
<span>完全支持</span>
</Badge>
);
} else if (canRead) {
return (
<Badge variant="secondary" className="space-x-1">
<Shield className="w-3 h-3" />
<span>只读支持</span>
</Badge>
);
} else {
return (
<Badge variant="outline" className="space-x-1">
<AlertTriangle className="w-3 h-3" />
<span>不支持</span>
</Badge>
);
}
};
return (
<div className="space-y-6">
{/* 系统基本信息 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Server className="w-5 h-5 text-blue-500" />
<span>系统信息</span>
</CardTitle>
<CardDescription>当前系统的基本信息和配置</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div className="flex items-center space-x-3">
<span className="text-2xl">
{getPlatformIcon(systemInfo.os)}
</span>
<div>
<p className="text-sm text-muted-foreground">操作系统</p>
<p className="font-semibold">
{machineIdUtils.getPlatformDisplayName(systemInfo.os)}
</p>
<p className="text-sm text-muted-foreground">
系列: {systemInfo.family}
</p>
</div>
</div>
<div className="flex items-center space-x-3">
<span className="text-2xl">{getArchIcon(systemInfo.arch)}</span>
<div>
<p className="text-sm text-muted-foreground">系统架构</p>
<p className="font-semibold">{systemInfo.arch}</p>
</div>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center space-x-3">
<Shield className="w-6 h-6 text-green-500" />
<div>
<p className="text-sm text-muted-foreground">权限状态</p>
<div className="flex items-center space-x-2">
{adminStatus.is_admin ? (
<Badge variant="default" className="space-x-1">
<CheckCircle className="w-3 h-3" />
<span>管理员权限</span>
</Badge>
) : (
<Badge variant="secondary" className="space-x-1">
<Shield className="w-3 h-3" />
<span>普通用户</span>
</Badge>
)}
</div>
{adminStatus.elevation_method && (
<p className="text-xs text-muted-foreground mt-1">
提升方法:{adminStatus.elevation_method}
</p>
)}
</div>
</div>
<div className="flex items-center space-x-3">
<Cpu className="w-6 h-6 text-purple-500" />
<div>
<p className="text-sm text-muted-foreground">机器码支持</p>
{getSupportBadge(
systemInfo.machine_id_support.can_read,
systemInfo.machine_id_support.can_write,
)}
{systemInfo.requires_admin && (
<p className="text-xs text-amber-600 dark:text-amber-400 mt-1">
⚠️ 修改需要管理员权限
</p>
)}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
{/* 平台支持详情 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Cpu className="w-5 h-5 text-purple-500" />
<span>平台支持详情</span>
</CardTitle>
<CardDescription>当前平台对机器码操作的支持情况</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-3">
<div>
<p className="text-sm text-muted-foreground">支持格式</p>
<p className="font-semibold">
{systemInfo.machine_id_support.format}
</p>
</div>
<div>
<p className="text-sm text-muted-foreground">实现方法</p>
<p className="text-sm bg-muted/50 p-2 rounded">
{systemInfo.machine_id_support.method}
</p>
</div>
</div>
<div className="space-y-3">
<div>
<p className="text-sm text-muted-foreground">操作权限</p>
<div className="space-y-2">
<div className="flex items-center space-x-2">
{systemInfo.machine_id_support.can_read ? (
<CheckCircle className="w-4 h-4 text-green-500" />
) : (
<AlertTriangle className="w-4 h-4 text-red-500" />
)}
<span className="text-sm">
读取机器码{" "}
{systemInfo.machine_id_support.can_read ? "✓" : "✗"}
</span>
</div>
<div className="flex items-center space-x-2">
{systemInfo.machine_id_support.can_write ? (
<CheckCircle className="w-4 h-4 text-green-500" />
) : (
<AlertTriangle className="w-4 h-4 text-red-500" />
)}
<span className="text-sm">
修改机器码{" "}
{systemInfo.machine_id_support.can_write ? "✓" : "✗"}
</span>
</div>
</div>
</div>
</div>
</div>
{systemInfo.machine_id_support.limitations.length > 0 && (
<>
<Separator />
<div>
<div className="flex items-center space-x-2 mb-3">
<Info className="w-4 h-4 text-amber-500" />
<p className="text-sm font-medium">平台限制</p>
</div>
<div className="space-y-2">
{systemInfo.machine_id_support.limitations.map(
(limitation, index) => (
<div
key={index}
className="flex items-start space-x-2 text-sm text-muted-foreground"
>
<AlertTriangle className="w-4 h-4 text-amber-500 mt-0.5 shrink-0" />
<span>{limitation}</span>
</div>
),
)}
</div>
</div>
</>
)}
</CardContent>
</Card>
{/* 平台特定信息 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Info className="w-5 h-5 text-indigo-500" />
<span>平台特定说明</span>
</CardTitle>
</CardHeader>
<CardContent>
{systemInfo.os.toLowerCase() === "windows" && (
<div className="space-y-3">
<h4 className="font-medium">Windows 平台说明</h4>
<ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside">
<li>
机器码存储在注册表中:
<code className="bg-muted px-1 rounded">
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MachineGuid
</code>
</li>
<li>修改机器码需要管理员权限</li>
<li>某些应用程序可能需要重启才能识别新的机器码</li>
<li>支持标准 UUID 格式</li>
</ul>
</div>
)}
{systemInfo.os.toLowerCase() === "macos" && (
<div className="space-y-3">
<h4 className="font-medium">macOS 平台说明</h4>
<ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside">
<li>使用应用层覆盖机制,不修改系统原始 UUID</li>
<li>
原始机器码通过{" "}
<code className="bg-muted px-1 rounded">ioreg</code> 命令获取
</li>
<li>覆盖文件存储在用户数据目录</li>
<li>不需要管理员权限,但只影响使用覆盖的应用</li>
<li>支持清除覆盖恢复原始状态</li>
</ul>
</div>
)}
{systemInfo.os.toLowerCase() === "linux" && (
<div className="space-y-3">
<h4 className="font-medium">Linux 平台说明</h4>
<ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside">
<li>
机器码存储在{" "}
<code className="bg-muted px-1 rounded">/etc/machine-id</code>{" "}
文件中
</li>
<li>修改需要 root 权限</li>
<li>使用 32 位十六进制格式</li>
<li>某些系统服务可能需要重启</li>
<li>修改可能影响系统服务的正常运行</li>
</ul>
</div>
)}
{!["windows", "macos", "linux"].includes(
systemInfo.os.toLowerCase(),
) && (
<div className="text-center py-6">
<AlertTriangle className="w-12 h-12 mx-auto mb-3 text-muted-foreground" />
<h4 className="font-medium mb-2">不支持的平台</h4>
<p className="text-sm text-muted-foreground">
当前平台({systemInfo.os})暂不支持机器码管理功能
</p>
</div>
)}
</CardContent>
</Card>
</div>
);
}
@@ -0,0 +1,154 @@
import { useState, useEffect } from "react";
import { ArrowLeft, Cpu, Settings, History, Info } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { MachineIdInfoPanel } from "./MachineIdInfoPanel";
import { MachineIdManagePanel } from "./MachineIdManagePanel";
import { MachineIdHistoryPanel } from "./MachineIdHistoryPanel";
import { MachineIdSystemPanel } from "./MachineIdSystemPanel";
import { MachineIdInfo, SystemInfo, AdminStatus } from "@/lib/api/machineId";
import { machineIdApi } from "@/lib/api/machineId";
interface MachineIdToolProps {
onNavigate: (
page:
| "provider-pool"
| "config-management"
| "api-server"
| "flow-monitor"
| "tools"
| "browser-interceptor"
| "machine-id"
| "settings",
) => void;
}
export function MachineIdTool({ onNavigate }: MachineIdToolProps) {
const [machineIdInfo, setMachineIdInfo] = useState<MachineIdInfo | null>(
null,
);
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
const [adminStatus, setAdminStatus] = useState<AdminStatus | null>(null);
const [loading, setLoading] = useState(true);
const [refreshKey, setRefreshKey] = useState(0);
useEffect(() => {
loadAllInfo();
}, [refreshKey]);
const loadAllInfo = async () => {
try {
setLoading(true);
const [machineId, system, admin] = await Promise.all([
machineIdApi.getCurrentMachineId(),
machineIdApi.getSystemInfo(),
machineIdApi.checkAdminPrivileges(),
]);
setMachineIdInfo(machineId);
setSystemInfo(system);
setAdminStatus(admin);
} catch (error) {
console.error("加载机器码信息失败:", error);
} finally {
setLoading(false);
}
};
const handleRefresh = () => {
setRefreshKey((prev) => prev + 1);
};
if (loading) {
return (
<div className="flex items-center justify-center h-96">
<div className="text-center">
<Cpu className="w-16 h-16 mx-auto mb-4 animate-spin text-blue-500" />
<p className="text-gray-600">加载中...</p>
</div>
</div>
);
}
return (
<div className="space-y-6">
{/* 页面头部 */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<Button
variant="ghost"
onClick={() => onNavigate("tools")}
className="p-2"
>
<ArrowLeft className="w-4 h-4" />
</Button>
<div className="flex items-center space-x-3">
<Cpu className="w-8 h-8 text-blue-500" />
<div>
<h1 className="text-3xl font-bold">机器码管理工具</h1>
<p className="text-gray-600 text-sm mt-1">
查看、修改和管理系统机器码,支持跨平台操作
</p>
</div>
</div>
</div>
{/* 刷新按钮 */}
<Button variant="outline" onClick={handleRefresh} className="space-x-2">
<Settings className="w-4 h-4" />
<span>刷新信息</span>
</Button>
</div>
{/* 主要功能标签页 */}
<Tabs defaultValue="info" className="space-y-6">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="info" className="space-x-2">
<Info className="w-4 h-4" />
<span>机器码信息</span>
</TabsTrigger>
<TabsTrigger value="manage" className="space-x-2">
<Settings className="w-4 h-4" />
<span>管理操作</span>
</TabsTrigger>
<TabsTrigger value="history" className="space-x-2">
<History className="w-4 h-4" />
<span>操作历史</span>
</TabsTrigger>
<TabsTrigger value="system" className="space-x-2">
<Cpu className="w-4 h-4" />
<span>系统信息</span>
</TabsTrigger>
</TabsList>
<TabsContent value="info" className="space-y-6">
<MachineIdInfoPanel
machineIdInfo={machineIdInfo}
adminStatus={adminStatus}
onRefresh={handleRefresh}
/>
</TabsContent>
<TabsContent value="manage" className="space-y-6">
<MachineIdManagePanel
machineIdInfo={machineIdInfo}
adminStatus={adminStatus}
onRefresh={handleRefresh}
/>
</TabsContent>
<TabsContent value="history" className="space-y-6">
<MachineIdHistoryPanel _onRefresh={handleRefresh} />
</TabsContent>
<TabsContent value="system" className="space-y-6">
<MachineIdSystemPanel
systemInfo={systemInfo}
adminStatus={adminStatus}
onRefresh={handleRefresh}
/>
</TabsContent>
</Tabs>
</div>
);
}
+64
View File
@@ -0,0 +1,64 @@
import * as React from "react";
import { cn } from "@/lib/utils";
interface RadioGroupProps {
value?: string;
onValueChange?: (value: string) => void;
className?: string;
children?: React.ReactNode;
}
interface RadioGroupItemProps {
value: string;
id?: string;
className?: string;
children?: React.ReactNode;
}
const RadioGroupContext = React.createContext<{
value?: string;
onValueChange?: (value: string) => void;
}>({});
const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
({ className, value, onValueChange, children, ...props }, ref) => {
return (
<RadioGroupContext.Provider value={{ value, onValueChange }}>
<div
ref={ref}
className={cn("grid gap-2", className)}
role="radiogroup"
{...props}
>
{children}
</div>
</RadioGroupContext.Provider>
);
},
);
RadioGroup.displayName = "RadioGroup";
const RadioGroupItem = React.forwardRef<HTMLInputElement, RadioGroupItemProps>(
({ className, value, id, ...props }, ref) => {
const context = React.useContext(RadioGroupContext);
return (
<input
ref={ref}
type="radio"
id={id}
value={value}
checked={context.value === value}
onChange={() => context.onValueChange?.(value)}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
/>
);
},
);
RadioGroupItem.displayName = "RadioGroupItem";
export { RadioGroup, RadioGroupItem };
+31
View File
@@ -0,0 +1,31 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Separator = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & {
orientation?: "horizontal" | "vertical";
decorative?: boolean;
}
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref,
) => (
<div
ref={ref}
role={decorative ? "none" : "separator"}
aria-orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className,
)}
{...props}
/>
),
);
Separator.displayName = "Separator";
export { Separator };
+31
View File
@@ -0,0 +1,31 @@
import { toast as sonnerToast } from "sonner";
export interface ToastProps {
title: string;
description?: string;
variant?: "default" | "destructive";
duration?: number;
}
export function useToast() {
const toast = ({
title,
description,
variant = "default",
duration,
}: ToastProps) => {
if (variant === "destructive") {
sonnerToast.error(title, {
description,
duration,
});
} else {
sonnerToast.success(title, {
description,
duration,
});
}
};
return { toast };
}
+255
View File
@@ -0,0 +1,255 @@
import { invoke } from "@tauri-apps/api/core";
// Machine ID types matching Rust backend
export type MachineIdFormat = "uuid" | "hex32" | "unknown";
export interface MachineIdInfo {
current_id: string;
original_id?: string;
platform: string;
can_modify: boolean;
requires_admin: boolean;
backup_exists: boolean;
format_type: MachineIdFormat;
}
export interface MachineIdResult {
success: boolean;
message: string;
requires_restart: boolean;
requires_admin: boolean;
new_machine_id?: string;
}
export interface AdminStatus {
is_admin: boolean;
platform: string;
elevation_method?: string;
check_success: boolean;
}
export interface MachineIdValidation {
is_valid: boolean;
error_message?: string;
formatted_id?: string;
detected_format: MachineIdFormat;
}
export interface MachineIdHistory {
id: string;
machine_id: string;
timestamp: string;
platform: string;
backup_path?: string;
}
export interface SystemInfo {
os: string;
arch: string;
family: string;
machine_id_support: PlatformSupport;
requires_admin: boolean;
}
export interface PlatformSupport {
can_read: boolean;
can_write: boolean;
format: string;
method: string;
limitations: string[];
}
export const machineIdApi = {
/**
* 获取当前机器码信息
*/
async getCurrentMachineId(): Promise<MachineIdInfo> {
return invoke("get_current_machine_id");
},
/**
* 设置新的机器码
*/
async setMachineId(newId: string): Promise<MachineIdResult> {
return invoke("set_machine_id", { newId });
},
/**
* 生成随机机器码
*/
async generateRandomMachineId(): Promise<string> {
return invoke("generate_random_machine_id");
},
/**
* 验证机器码格式
*/
async validateMachineId(machineId: string): Promise<MachineIdValidation> {
return invoke("validate_machine_id", { machineId });
},
/**
* 检查管理员权限
*/
async checkAdminPrivileges(): Promise<AdminStatus> {
return invoke("check_admin_privileges");
},
/**
* 获取操作系统类型
*/
async getOsType(): Promise<string> {
return invoke("get_os_type");
},
/**
* 备份机器码到文件
*/
async backupMachineIdToFile(filePath: string): Promise<boolean> {
return invoke("backup_machine_id_to_file", { filePath });
},
/**
* 从文件恢复机器码
*/
async restoreMachineIdFromFile(filePath: string): Promise<MachineIdResult> {
return invoke("restore_machine_id_from_file", { filePath });
},
/**
* 格式化机器码
* @param machineId 机器码
* @param formatType 格式类型:"uuid" 或 "hex32"
*/
async formatMachineId(
machineId: string,
formatType: "uuid" | "hex32",
): Promise<string> {
return invoke("format_machine_id", { machineId, formatType });
},
/**
* 检测机器码格式
*/
async detectMachineIdFormat(machineId: string): Promise<string> {
return invoke("detect_machine_id_format", { machineId });
},
/**
* 转换机器码格式
* @param machineId 机器码
* @param targetFormat 目标格式:"uuid" 或 "hex32"
*/
async convertMachineIdFormat(
machineId: string,
targetFormat: "uuid" | "hex32",
): Promise<string> {
return invoke("convert_machine_id_format", { machineId, targetFormat });
},
/**
* 获取机器码历史记录
*/
async getMachineIdHistory(): Promise<MachineIdHistory[]> {
return invoke("get_machine_id_history");
},
/**
* 清除机器码覆盖(仅限 macOS)
*/
async clearMachineIdOverride(): Promise<MachineIdResult> {
return invoke("clear_machine_id_override");
},
/**
* 复制机器码到剪贴板
*/
async copyMachineIdToClipboard(machineId: string): Promise<boolean> {
return invoke("copy_machine_id_to_clipboard", { machineId });
},
/**
* 从剪贴板粘贴机器码
*/
async pasteMachineIdFromClipboard(): Promise<string> {
return invoke("paste_machine_id_from_clipboard");
},
/**
* 获取系统信息
*/
async getSystemInfo(): Promise<SystemInfo> {
return invoke("get_system_info");
},
};
// Helper functions for common operations
export const machineIdUtils = {
/**
* 检查机器码是否为有效的UUID格式
*/
isValidUuid(machineId: string): boolean {
const uuidRegex =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return uuidRegex.test(machineId);
},
/**
* 检查机器码是否为有效的32位十六进制格式
*/
isValidHex32(machineId: string): boolean {
const hex32Regex = /^[0-9a-f]{32}$/i;
return hex32Regex.test(machineId);
},
/**
* 清理机器码字符串(移除连字符和空格)
*/
cleanMachineId(machineId: string): string {
return machineId.replace(/[-\s]/g, "").toLowerCase();
},
/**
* 格式化机器码为UUID格式
*/
formatAsUuid(machineId: string): string {
const cleaned = this.cleanMachineId(machineId);
if (cleaned.length !== 32) {
throw new Error("Invalid machine ID length");
}
return `${cleaned.slice(0, 8)}-${cleaned.slice(8, 12)}-${cleaned.slice(12, 16)}-${cleaned.slice(16, 20)}-${cleaned.slice(20)}`;
},
/**
* 获取机器码的显示名称
*/
getFormatDisplayName(format: MachineIdFormat): string {
switch (format) {
case "uuid":
return "UUID格式";
case "hex32":
return "32位十六进制";
case "unknown":
return "未知格式";
default:
return "未知格式";
}
},
/**
* 获取平台的显示名称
*/
getPlatformDisplayName(platform: string): string {
switch (platform.toLowerCase()) {
case "windows":
return "Windows";
case "macos":
return "macOS";
case "linux":
return "Linux";
default:
return platform;
}
},
};