feat: 添加arp支持,仅支持管理员模式

This commit is contained in:
SikongJueluo 2025-07-18 12:28:17 +08:00
parent 69c7cbf4d8
commit 12cd35edff
No known key found for this signature in database
6 changed files with 109 additions and 47 deletions

View File

@ -15,11 +15,15 @@ clean:
rm -rf "dist" rm -rf "dist"
rm -rf "wwwroot" rm -rf "wwwroot"
update: update: update-node update-dotnet
npm install
dotnet restore ./server/server.csproj
git submodule update --init --remote --recursive git submodule update --init --remote --recursive
update-node:
npm install
update-dotnet:
dotnet restore ./server/server.csproj
# 生成Restful API到网页客户端 # 生成Restful API到网页客户端
gen-api: gen-api:
npm run gen-api npm run gen-api

23
components.d.ts vendored
View File

@ -9,11 +9,8 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
Alert: typeof import('./src/components/Alert/Alert.vue')['default'] Alert: typeof import('./src/components/Alert/Alert.vue')['default']
AlertDemo: typeof import('./src/components/AlertDemo.vue')['default']
BaseBoard: typeof import('./src/components/equipments/BaseBoard.vue')['default'] BaseBoard: typeof import('./src/components/equipments/BaseBoard.vue')['default']
BaseInputField: typeof import('./src/components/InputField/BaseInputField.vue')['default'] BaseInputField: typeof import('./src/components/InputField/BaseInputField.vue')['default']
Canvas: typeof import('./src/components/Canvas.vue')['default']
ChannelConfig: typeof import('./src/components/LogicAnalyzer/ChannelConfig.vue')['default']
CollapsibleSection: typeof import('./src/components/CollapsibleSection.vue')['default'] CollapsibleSection: typeof import('./src/components/CollapsibleSection.vue')['default']
ComponentSelector: typeof import('./src/components/LabCanvas/ComponentSelector.vue')['default'] ComponentSelector: typeof import('./src/components/LabCanvas/ComponentSelector.vue')['default']
DDR: typeof import('./src/components/equipments/DDR.vue')['default'] DDR: typeof import('./src/components/equipments/DDR.vue')['default']
@ -22,16 +19,10 @@ declare module 'vue' {
DiagramCanvas: typeof import('./src/components/LabCanvas/DiagramCanvas.vue')['default'] DiagramCanvas: typeof import('./src/components/LabCanvas/DiagramCanvas.vue')['default']
Dialog: typeof import('./src/components/Dialog.vue')['default'] Dialog: typeof import('./src/components/Dialog.vue')['default']
ETH: typeof import('./src/components/equipments/ETH.vue')['default'] ETH: typeof import('./src/components/equipments/ETH.vue')['default']
FunctionBar: typeof import('./src/components/FunctionBar.vue')['default']
HDMI: typeof import('./src/components/equipments/HDMI.vue')['default'] HDMI: typeof import('./src/components/equipments/HDMI.vue')['default']
IpInputField: typeof import('./src/components/InputField/IpInputField.vue')['default'] IpInputField: typeof import('./src/components/InputField/IpInputField.vue')['default']
ItemList: typeof import('./src/components/LabCanvas/ItemList.vue')['default'] ItemList: typeof import('./src/components/LabCanvas/ItemList.vue')['default']
LabCanvas: typeof import('./src/components/LabCanvasNew/LabCanvas.vue')['default']
LabCanvasNew: typeof import('./src/components/LabCanvas/LabCanvasNew.vue')['default']
LabComponentsDrawer: typeof import('./src/components/LabCanvasNew/LabComponentsDrawer.vue')['default']
LabComponentsDrawerNew: typeof import('./src/components/LabCanvas/LabComponentsDrawerNew.vue')['default']
LogicalWaveFormDisplay: typeof import('./src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue')['default'] LogicalWaveFormDisplay: typeof import('./src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue')['default']
LoginCard: typeof import('./src/components/LoginCard.vue')['default']
MarkdownRenderer: typeof import('./src/components/MarkdownRenderer.vue')['default'] MarkdownRenderer: typeof import('./src/components/MarkdownRenderer.vue')['default']
MechanicalButton: typeof import('./src/components/equipments/MechanicalButton.vue')['default'] MechanicalButton: typeof import('./src/components/equipments/MechanicalButton.vue')['default']
MotherBoard: typeof import('./src/components/equipments/MotherBoard.vue')['default'] MotherBoard: typeof import('./src/components/equipments/MotherBoard.vue')['default']
@ -43,29 +34,15 @@ declare module 'vue' {
PortInputField: typeof import('./src/components/InputField/PortInputField.vue')['default'] PortInputField: typeof import('./src/components/InputField/PortInputField.vue')['default']
PropertyEditor: typeof import('./src/components/PropertyEditor.vue')['default'] PropertyEditor: typeof import('./src/components/PropertyEditor.vue')['default']
PropertyPanel: typeof import('./src/components/PropertyPanel.vue')['default'] PropertyPanel: typeof import('./src/components/PropertyPanel.vue')['default']
RekaSplitterGroup: typeof import('reka-ui')['SplitterGroup']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
ScrollAreaCorner: typeof import('reka-ui')['ScrollAreaCorner']
ScrollAreaRoot: typeof import('reka-ui')['ScrollAreaRoot']
ScrollAreaScrollbar: typeof import('reka-ui')['ScrollAreaScrollbar']
ScrollAreaThumb: typeof import('reka-ui')['ScrollAreaThumb']
ScrollAreaViewport: typeof import('reka-ui')['ScrollAreaViewport']
SD: typeof import('./src/components/equipments/SD.vue')['default'] SD: typeof import('./src/components/equipments/SD.vue')['default']
SevenSegmentDisplay: typeof import('./src/components/equipments/SevenSegmentDisplay.vue')['default'] SevenSegmentDisplay: typeof import('./src/components/equipments/SevenSegmentDisplay.vue')['default']
SFP: typeof import('./src/components/equipments/SFP.vue')['default'] SFP: typeof import('./src/components/equipments/SFP.vue')['default']
Sidebar: typeof import('./src/components/Sidebar.vue')['default'] Sidebar: typeof import('./src/components/Sidebar.vue')['default']
SMA: typeof import('./src/components/equipments/SMA.vue')['default'] SMA: typeof import('./src/components/equipments/SMA.vue')['default']
SMT_LED: typeof import('./src/components/equipments/SMT_LED.vue')['default'] SMT_LED: typeof import('./src/components/equipments/SMT_LED.vue')['default']
SplitterGroup: typeof import('reka-ui')['SplitterGroup']
SplitterPanel: typeof import('reka-ui')['SplitterPanel']
SplitterResizeHandle: typeof import('reka-ui')['SplitterResizeHandle']
Switch: typeof import('./src/components/equipments/Switch.vue')['default'] Switch: typeof import('./src/components/equipments/Switch.vue')['default']
TabsContent: typeof import('reka-ui')['TabsContent']
TabsIndicator: typeof import('reka-ui')['TabsIndicator']
TabsList: typeof import('reka-ui')['TabsList']
TabsRoot: typeof import('reka-ui')['TabsRoot']
TabsTrigger: typeof import('reka-ui')['TabsTrigger']
ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default'] ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default']
ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default'] ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default']
TriggerSettings: typeof import('./src/components/LogicAnalyzer/TriggerSettings.vue')['default'] TriggerSettings: typeof import('./src/components/LogicAnalyzer/TriggerSettings.vue')['default']

View File

@ -23,6 +23,7 @@
sqls sqls
sql-studio sql-studio
zlib zlib
bash
# Backend # Backend
(dotnetCorePackages.combinePackages [ (dotnetCorePackages.combinePackages [
dotnetCorePackages.sdk_9_0 dotnetCorePackages.sdk_9_0

View File

@ -7,6 +7,8 @@ using System.Text.RegularExpressions;
/// </summary> /// </summary>
public static class Arp public static class Arp
{ {
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary> /// <summary>
/// 读取所有 ARP 记录 /// 读取所有 ARP 记录
/// </summary> /// </summary>
@ -41,6 +43,28 @@ public static class Arp
return entries; return entries;
} }
/// <summary>
/// 通过 Ping 动态更新指定 IP 的 ARP 记录
/// </summary>
/// <param name="ipAddress">要更新的 IP 地址</param>
/// <returns>是否成功发送 Ping</returns>
public static async Task<bool> UpdateArpEntryByPingAsync(string ipAddress)
{
if (string.IsNullOrWhiteSpace(ipAddress))
throw new ArgumentException("IP 地址不能为空", nameof(ipAddress));
try
{
using var ping = new System.Net.NetworkInformation.Ping();
var reply = await ping.SendPingAsync(ipAddress, 100);
return reply.Status == System.Net.NetworkInformation.IPStatus.Success;
}
catch
{
return false;
}
}
/// <summary> /// <summary>
/// 添加 ARP 记录 /// 添加 ARP 记录
/// </summary> /// </summary>
@ -48,7 +72,7 @@ public static class Arp
/// <param name="macAddress">MAC 地址</param> /// <param name="macAddress">MAC 地址</param>
/// <param name="interfaceName">网络接口名称(可选)</param> /// <param name="interfaceName">网络接口名称(可选)</param>
/// <returns>是否成功</returns> /// <returns>是否成功</returns>
public static async Task<bool> AddArpEntryAsync(string ipAddress, string macAddress, string interfaceName = null) public static async Task<bool> AddArpEntryAsync(string ipAddress, string macAddress, string? interfaceName = null)
{ {
if (string.IsNullOrWhiteSpace(ipAddress)) if (string.IsNullOrWhiteSpace(ipAddress))
throw new ArgumentException("IP 地址不能为空", nameof(ipAddress)); throw new ArgumentException("IP 地址不能为空", nameof(ipAddress));
@ -74,7 +98,7 @@ public static class Arp
/// <param name="ipAddress">要删除的 IP 地址</param> /// <param name="ipAddress">要删除的 IP 地址</param>
/// <param name="interfaceName">网络接口名称(可选)</param> /// <param name="interfaceName">网络接口名称(可选)</param>
/// <returns>是否成功</returns> /// <returns>是否成功</returns>
public static async Task<bool> DeleteArpEntryAsync(string ipAddress, string interfaceName = null) public static async Task<bool> DeleteArpEntryAsync(string ipAddress, string? interfaceName = null)
{ {
if (string.IsNullOrWhiteSpace(ipAddress)) if (string.IsNullOrWhiteSpace(ipAddress))
throw new ArgumentException("IP 地址不能为空", nameof(ipAddress)); throw new ArgumentException("IP 地址不能为空", nameof(ipAddress));
@ -149,15 +173,13 @@ public static class Arp
/// </summary> /// </summary>
private static string GetArpListCommand() private static string GetArpListCommand()
{ {
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) return "arp -a";
? "arp -a"
: "arp -a";
} }
/// <summary> /// <summary>
/// 获取 ARP 添加命令 /// 获取 ARP 添加命令
/// </summary> /// </summary>
private static string GetArpAddCommand(string ipAddress, string macAddress, string interfaceName) private static string GetArpAddCommand(string ipAddress, string macAddress, string? interfaceName)
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
@ -186,7 +208,7 @@ public static class Arp
/// <summary> /// <summary>
/// 获取 ARP 删除命令 /// 获取 ARP 删除命令
/// </summary> /// </summary>
private static string GetArpDeleteCommand(string ipAddress, string interfaceName) private static string GetArpDeleteCommand(string ipAddress, string? interfaceName)
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
@ -237,16 +259,9 @@ public static class Arp
/// 获取 ARP 查询命令 /// 获取 ARP 查询命令
/// </summary> /// </summary>
private static string GetArpQueryCommand(string ipAddress) private static string GetArpQueryCommand(string ipAddress)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
return $"arp -a {ipAddress}"; return $"arp -a {ipAddress}";
} }
else
{
return $"arp -n {ipAddress}";
}
}
/// <summary> /// <summary>
/// 执行系统命令 /// 执行系统命令
@ -284,6 +299,8 @@ public static class Arp
}; };
} }
logger.Debug($"Executing command: {processInfo.FileName} {processInfo.Arguments}");
using var process = new Process { StartInfo = processInfo }; using var process = new Process { StartInfo = processInfo };
process.Start(); process.Start();
@ -292,6 +309,11 @@ public static class Arp
await process.WaitForExitAsync(); await process.WaitForExitAsync();
logger.Debug($"Command output: {output}");
if (!string.IsNullOrWhiteSpace(error))
logger.Debug($"Command error: {error}");
logger.Debug($"Command exit code: {process.ExitCode}");
return new CommandResult return new CommandResult
{ {
IsSuccess = process.ExitCode == 0, IsSuccess = process.ExitCode == 0,
@ -302,6 +324,7 @@ public static class Arp
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.Error(ex, $"Command execution failed: {command}");
return new CommandResult return new CommandResult
{ {
IsSuccess = false, IsSuccess = false,
@ -358,8 +381,8 @@ public static class Arp
/// </summary> /// </summary>
private static ArpEntry? ParseUnixArpEntry(string line) private static ArpEntry? ParseUnixArpEntry(string line)
{ {
// Unix/Linux arp -a 输出格式: hostname (ip) at mac [ether] on interface // Unix/Linux arp -a 输出格式: hostname (ip) at mac [ether] PERM on interface
var pattern = @"(\S+)\s+\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([a-fA-F0-9:]{17})\s+\[(\w+)\]\s+on\s+(\S+)"; var pattern = @"(\S+)\s+\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([a-fA-F0-9:]{17})\s+\[(\w+)\]\s+(\w+)\s+on\s+(\S+)";
var match = Regex.Match(line, pattern); var match = Regex.Match(line, pattern);
if (match.Success) if (match.Success)
@ -369,8 +392,8 @@ public static class Arp
Hostname = match.Groups[1].Value, Hostname = match.Groups[1].Value,
IpAddress = match.Groups[2].Value, IpAddress = match.Groups[2].Value,
MacAddress = match.Groups[3].Value, MacAddress = match.Groups[3].Value,
Type = match.Groups[4].Value, Type = match.Groups[5].Value,
Interface = match.Groups[5].Value Interface = match.Groups[6].Value
}; };
} }
@ -390,6 +413,26 @@ public static class Arp
return null; return null;
} }
/// <summary>
/// 判断当前进程是否具有管理员(或 root权限
/// </summary>
/// <returns>如果有管理员权限返回 true否则返回 false</returns>
public static bool IsAdministrator()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Windows: 检查当前用户是否为管理员
using var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var principal = new System.Security.Principal.WindowsPrincipal(identity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
else
{
// Unix/Linux/macOS: 检查是否为 root 用户
return Environment.UserName == "root" || (Environment.GetEnvironmentVariable("USER") == "root");
}
}
} }
/// <summary> /// <summary>
@ -397,12 +440,30 @@ public static class Arp
/// </summary> /// </summary>
public class ArpEntry public class ArpEntry
{ {
/// <summary>
/// [TODO:description]
/// </summary>
public string Hostname { get; set; } = string.Empty; public string Hostname { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string IpAddress { get; set; } = string.Empty; public string IpAddress { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string MacAddress { get; set; } = string.Empty; public string MacAddress { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string Type { get; set; } = string.Empty; public string Type { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string Interface { get; set; } = string.Empty; public string Interface { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public override string ToString() public override string ToString()
{ {
return $"{IpAddress} -> {MacAddress} ({Interface})"; return $"{IpAddress} -> {MacAddress} ({Interface})";
@ -414,8 +475,20 @@ public class ArpEntry
/// </summary> /// </summary>
public class CommandResult public class CommandResult
{ {
/// <summary>
/// [TODO:description]
/// </summary>
public bool IsSuccess { get; set; } public bool IsSuccess { get; set; }
/// <summary>
/// [TODO:description]
/// </summary>
public string Output { get; set; } = string.Empty; public string Output { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string Error { get; set; } = string.Empty; public string Error { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public int ExitCode { get; set; } public int ExitCode { get; set; }
} }

View File

@ -3,6 +3,8 @@
/// </summary> /// </summary>
public static class MsgBus public static class MsgBus
{ {
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private static readonly UDPServer udpServer = new UDPServer(1234, 12); private static readonly UDPServer udpServer = new UDPServer(1234, 12);
/// <summary> /// <summary>
/// 获取UDP服务器 /// 获取UDP服务器
@ -19,8 +21,13 @@ public static class MsgBus
/// 通信总线初始化 /// 通信总线初始化
/// </summary> /// </summary>
/// <returns>无</returns> /// <returns>无</returns>
public static void Init() public async static void Init()
{ {
if (!Arp.IsAdministrator())
{
logger.Error($"非管理员运行ARP无法更新请用管理员权限运行");
// throw new Exception($"非管理员运行ARP无法更新请用管理员权限运行");
}
udpServer.Start(); udpServer.Start();
isRunning = true; isRunning = true;
} }

View File

@ -128,7 +128,7 @@ public class UDPServer
if (IsPortInUse(currentPort)) if (IsPortInUse(currentPort))
{ {
throw new ArgumentException( throw new ArgumentException(
$"Port {currentPort} is already in use.", $"端口{currentPort}已被占用无法启动UDP Server",
nameof(port) nameof(port)
); );
} }