Merge remote-tracking branch 'origin/main'

This commit is contained in:
jimmy committed 2025-07-26 15:48:49 +08:00
commit ee5b83bb9d
31 files changed
+2222 -3898

No files matched your search

+7 -3
View File
@@ -14,9 +14,13 @@
[English](#english) | [中文](#chinese)
</div>
> **目前项目正在积极开发阶段, 在 `1.0` 版本前不保证项目可用性.**
>
> **The project is currently in active development, and there is no guarantee of project availability until the '1.0' release.**
<p style="color: red; font-weight: bold;">
🚧 当前项目正在积极开发阶段,在 <code>1.0</code> 版本前不保证项目可用性。
</p>
<p style="color: red; font-weight: bold;">
🚧 The project is currently in active development, and there is no guarantee of project availability until the <code>1.0</code> release.
</p>
---
@@ -151,7 +151,7 @@ namespace Common.Utils
totalSpeed.UploadSpeed += speed.UploadSpeed;
}
Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
// Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
return totalSpeed;
}
@@ -113,7 +113,7 @@ namespace Common.Utils
// 跳过环回接口
if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
{
Log.Info($"跳过环回接口: {ni.Name}");
//Log.Info($"跳过环回接口: {ni.Name}");
continue;
}
@@ -291,7 +291,7 @@ namespace Common.Utils
}
}
Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
// Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
return totalSpeed;
}
@@ -308,4 +308,4 @@ namespace Common.Utils
#endregion
}
}
}
@@ -57,6 +57,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATraceEvent_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F4d_003F09175cdc_003FTraceEvent_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWebSocketServer_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa7d96aff8e344c1d9aa8042f229d493cae00_003Fc6_003F2f09550a_003FWebSocketServer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWebView2_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8aca2f2c90e1454c825fc8b69c53a97e14268_003F06_003F22dc0e77_003FWebView2_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWindow_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd079e839ea2040f09d8282baf64f01edf61928_003Fab_003F96a4af57_003FWindow_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=f4a8c497_002D43fb_002D4de1_002D9ffa_002D84076d7db2c5/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Test1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;NUnit3x::B3F481C9-B351-449C-AE2B-CDEC29BD5DCC::net8.0::Common.Test.Tests.Test1&lt;/TestId&gt;&#xD;
+19 -7
View File
@@ -20,12 +20,16 @@ using Infrastructure.Models;
using Microsoft.Extensions.Logging;
using Serilog.Events;
using Shell;
using Shell.UserControls;
using Shell.Utils;
using Shell.Views;
using Utils.ETW.Etw;
using Utils.Firewall;
namespace NetVizor;
using System.Windows.Forms; // 引用 Windows Forms 命名空间
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
@@ -42,16 +46,20 @@ public partial class App : System.Windows.Application
// 启动服务(如 WebSocket、HTTP、端口监听等)
// StartMyServer();
// NetView
// var netView = new NetView();
// netView.Show();
Mouth();
// NetView 网络监看
var netView = new NetView();
netView.Show();
// Mouth();
// 网速监视
// var window = new NetView();
// window.Show();
// 设置应用程序在关闭最后一个窗口时不自动退出
this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
}
private async void Mouth()
@@ -59,7 +67,7 @@ public partial class App : System.Windows.Application
var firewallService = new FirewallService();
// 防火墙状态
// var statusAsync = await firewallService.GetStatusAsync();
var rulesAsync =await firewallService.GetRulesAsync();
var rulesAsync = await firewallService.GetRulesAsync();
Console.WriteLine(JsonHelper.ToJson(rulesAsync));
}
@@ -405,6 +413,10 @@ public partial class App : System.Windows.Application
#endregion
#region 获取防火墙信息
#endregion
#region 取消订阅
server.Post("/api/unsubscribe", async (context) =>
@@ -516,4 +528,4 @@ public class SubscriptionAppInfo : SubscriptionInfo
// 再有,“协议和端口”中的协议类型也有很多,我们也要展示。比如它甚至能指定用户,可以选择用户和组。
// 所以基于上,我们几乎是重写了防火墙的页面。
// 我现在需要你基于这个能获取到的信息(可能有些我没提到),使用html完成一个防火墙的看板,要点击某个规则可以编辑,可以新增。
// 新增的窗口和交互都需要完成,数据的展示也需要完成。你用假数据替代数据填充即可,要求界面要现代化,美观,优雅
// 新增的窗口和交互都需要完成,数据的展示也需要完成。你用假数据替代数据填充即可,要求界面要现代化,美观,优雅
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+12
View File
@@ -6,14 +6,22 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms> <!-- 加上这句,才能正确启用 Forms 支持 -->
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Folder Include="Assets\Icons\"/>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3240.44" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.6.25358.103"/>
</ItemGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms"/>
</ItemGroup>
<ItemGroup>
@@ -30,6 +38,10 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="WinDivert.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Remove="Assets\Icons\app.ico"/>
<Content Include="Assets\Icons\app.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -8,7 +8,7 @@ namespace Shell.UserControls
/// <summary>
/// Interaction logic for NetworkMonitorView.xaml
/// </summary>
public partial class NetworkMonitorView : UserControl
public partial class NetworkMonitorView : System.Windows.Controls.UserControl
{
private NetworkMonitorViewModel _viewModel;
@@ -38,4 +38,4 @@ namespace Shell.UserControls
_viewModel?.Dispose();
}
}
}
}
@@ -0,0 +1,116 @@
<Window x:Class="Shell.UserControls.TrayMenuWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Shell.UserControls"
mc:Ignorable="d"
Title="TrayMenu"
Height="200" Width="180"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
Topmost="True"
ShowInTaskbar="False"
ResizeMode="NoResize"
Deactivated="Window_Deactivated">
<Window.Resources>
<!-- 菜单项样式 -->
<Style x:Key="MenuItemStyle" TargetType="Border">
<Setter Property="Padding" Value="15,8" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#E3F2FD" />
</Trigger>
</Style.Triggers>
</Style>
<!-- 图标样式 -->
<Style x:Key="IconStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe MDL2 Assets" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Foreground" Value="#2196F3" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,10,0" />
</Style>
<!-- 文本样式 -->
<Style x:Key="TextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="#333333" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!-- 分隔线样式 -->
<Style x:Key="SeparatorStyle" TargetType="Rectangle">
<Setter Property="Height" Value="1" />
<Setter Property="Fill" Value="#E0E0E0" />
<Setter Property="Margin" Value="10,5" />
</Style>
</Window.Resources>
<Border Background="White"
CornerRadius="8"
BorderBrush="#E0E0E0"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect ShadowDepth="3"
Direction="315"
Color="#40000000"
Opacity="0.3"
BlurRadius="10" />
</Border.Effect>
<StackPanel Margin="5">
<!-- 打开主窗口 -->
<Border Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="ShowMainWindow_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE8A7;" Style="{StaticResource IconStyle}" />
<TextBlock Text="打开主窗口" Style="{StaticResource TextStyle}" />
</StackPanel>
</Border>
<!-- 分隔线 -->
<Rectangle Style="{StaticResource SeparatorStyle}" />
<!-- 最小化到托盘 -->
<Border Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="HideToTray_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE921;" Style="{StaticResource IconStyle}" />
<TextBlock Text="最小化到托盘" Style="{StaticResource TextStyle}" />
</StackPanel>
</Border>
<!-- 关于 -->
<Border Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="About_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE946;" Style="{StaticResource IconStyle}" />
<TextBlock Text="关于" Style="{StaticResource TextStyle}" />
</StackPanel>
</Border>
<!-- 分隔线 -->
<Rectangle Style="{StaticResource SeparatorStyle}" />
<!-- 退出程序 -->
<Border Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="ExitApplication_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE8BB;"
Style="{StaticResource IconStyle}"
Foreground="#F44336" />
<TextBlock Text="退出程序"
Style="{StaticResource TextStyle}"
Foreground="#F44336" />
</StackPanel>
</Border>
</StackPanel>
</Border>
</Window>
@@ -0,0 +1,256 @@
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Interop;
using System.Windows.Media;
namespace Shell.UserControls;
public partial class TrayMenuWindow : Window
{
public event Action OnShowMainWindow;
public event Action OnExitApplication;
public event Action OnHideToTray;
// 菜单的固定尺寸(与XAML中设置的一致)
private const double MENU_WIDTH = 180;
private const double MENU_HEIGHT = 200;
public TrayMenuWindow()
{
InitializeComponent();
this.Loaded += TrayMenuWindow_Loaded;
// 确保窗口使用Per-Monitor DPI Awareness
this.SourceInitialized += (s, e) =>
{
var hwnd = new WindowInteropHelper(this).Handle;
var source = HwndSource.FromHwnd(hwnd);
if (source != null)
{
source.AddHook(WndProc);
}
};
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// 处理DPI变化消息
const int WM_DPICHANGED = 0x02E0;
if (msg == WM_DPICHANGED)
{
// DPI发生变化时可以在这里处理
Console.WriteLine("DPI changed detected");
}
return IntPtr.Zero;
}
private void TrayMenuWindow_Loaded(object sender, RoutedEventArgs e)
{
// 添加淡入动画
// var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200));
// this.BeginAnimation(OpacityProperty, fadeIn);
// 添加缩放动画
// var scaleTransform = new System.Windows.Media.ScaleTransform(0.8, 0.8);
// this.RenderTransform = scaleTransform;
// this.RenderTransformOrigin = new System.Windows.Point(0.5, 0.5);
// var scaleXAnimation = new DoubleAnimation(0.8, 1, TimeSpan.FromMilliseconds(200));
// var scaleYAnimation = new DoubleAnimation(0.8, 1, TimeSpan.FromMilliseconds(200));
// scaleXAnimation.EasingFunction = new QuadraticEase();
// scaleYAnimation.EasingFunction = new QuadraticEase();
// scaleTransform.BeginAnimation(System.Windows.Media.ScaleTransform.ScaleXProperty, scaleXAnimation);
// scaleTransform.BeginAnimation(System.Windows.Media.ScaleTransform.ScaleYProperty, scaleYAnimation);
}
public void ShowAt(int x, int y)
{
// 使用主显示器工作区域的旧方法(保留向后兼容)
var workArea = SystemParameters.WorkArea;
var customWorkArea = new Shell.Utils.TrayIconManager.RECT
{
Left = (int)workArea.Left,
Top = (int)workArea.Top,
Right = (int)workArea.Right,
Bottom = (int)workArea.Bottom
};
ShowAtWithWorkArea(x, y, customWorkArea);
}
public void ShowAtWithWorkArea(int x, int y, Shell.Utils.TrayIconManager.RECT workArea)
{
Console.WriteLine("=== TrayMenu ShowAtWithWorkArea Debug ===");
Console.WriteLine($"输入鼠标位置: X={x}, Y={y}");
Console.WriteLine(
$"使用工作区域: Left={workArea.Left}, Top={workArea.Top}, Right={workArea.Right}, Bottom={workArea.Bottom}");
Console.WriteLine($"工作区域尺寸: Width={workArea.Right - workArea.Left}, Height={workArea.Bottom - workArea.Top}");
Console.WriteLine($"菜单尺寸: Width={MENU_WIDTH}, Height={MENU_HEIGHT}");
// 获取当前窗口的DPI缩放信息
var dpiScale = GetDpiScale();
Console.WriteLine($"窗口DPI缩放: X={dpiScale.DpiScaleX:F2}, Y={dpiScale.DpiScaleY:F2}");
// 计算菜单显示位置 - 优先显示在鼠标左上方
double left = x - MENU_WIDTH;
double top = y - MENU_HEIGHT;
Console.WriteLine($"初始计算位置 (左上方): Left={left}, Top={top}");
// 智能位置调整算法
var adjustedPosition = CalculateOptimalPosition(x, y, workArea, MENU_WIDTH, MENU_HEIGHT);
left = adjustedPosition.X;
top = adjustedPosition.Y;
Console.WriteLine($"智能调整后位置: Left={left}, Top={top}");
// 最终边界检查(确保完全在工作区域内)
left = Math.Max(workArea.Left, Math.Min(left, workArea.Right - MENU_WIDTH));
top = Math.Max(workArea.Top, Math.Min(top, workArea.Bottom - MENU_HEIGHT));
Console.WriteLine($"最终边界检查后位置: Left={left}, Top={top}");
// 设置窗口位置
this.Left = left;
this.Top = top;
// 显示窗口
this.Show();
this.Activate();
this.Focus();
// 验证实际设置的位置
Console.WriteLine($"窗口实际位置: Left={this.Left}, Top={this.Top}");
Console.WriteLine($"窗口实际尺寸: Width={this.ActualWidth}, Height={this.ActualHeight}");
Console.WriteLine("=== Debug End ===\n");
}
private (double X, double Y) CalculateOptimalPosition(int mouseX, int mouseY,
Shell.Utils.TrayIconManager.RECT workArea, double menuWidth, double menuHeight)
{
double left, top;
// 定义四个可能的位置:左上、右上、左下、右下
var positions = new[]
{
new
{
X = (double)(mouseX - menuWidth), Y = (double)(mouseY - menuHeight), Priority = 1, Name = "左上"
}, // 左上 (优先)
new { X = (double)mouseX, Y = (double)(mouseY - menuHeight), Priority = 2, Name = "右上" }, // 右上
new { X = (double)(mouseX - menuWidth), Y = (double)mouseY, Priority = 3, Name = "左下" }, // 左下
new { X = (double)mouseX, Y = (double)mouseY, Priority = 4, Name = "右下" } // 右下
};
// 检查每个位置是否完全在工作区域内
foreach (var pos in positions.OrderBy(p => p.Priority))
{
bool fitsHorizontally = pos.X >= workArea.Left && pos.X + menuWidth <= workArea.Right;
bool fitsVertically = pos.Y >= workArea.Top && pos.Y + menuHeight <= workArea.Bottom;
Console.WriteLine(
$"检查位置 {pos.Name}: X={pos.X:F1}, Y={pos.Y:F1}, 水平适合={fitsHorizontally}, 垂直适合={fitsVertically}");
if (fitsHorizontally && fitsVertically)
{
Console.WriteLine($"选择位置: {pos.Name}");
return (pos.X, pos.Y);
}
}
// 如果没有位置完全适合,使用智能调整
Console.WriteLine("没有完全适合的位置,使用智能调整");
// 计算最佳的水平位置
if (mouseX - menuWidth >= workArea.Left)
{
left = mouseX - menuWidth; // 左侧有足够空间
}
else if (mouseX + menuWidth <= workArea.Right)
{
left = mouseX; // 右侧有足够空间
}
else
{
// 两侧都不够,居中显示或贴边
left = Math.Max(workArea.Left, workArea.Right - menuWidth);
}
// 计算最佳的垂直位置
if (mouseY - menuHeight >= workArea.Top)
{
top = mouseY - menuHeight; // 上方有足够空间
}
else if (mouseY + menuHeight <= workArea.Bottom)
{
top = mouseY; // 下方有足够空间
}
else
{
// 上下都不够,贴顶部或底部
top = Math.Max(workArea.Top, workArea.Bottom - menuHeight);
}
Console.WriteLine($"智能调整结果: Left={left:F1}, Top={top:F1}");
return (left, top);
}
private System.Windows.DpiScale GetDpiScale()
{
try
{
var source = PresentationSource.FromVisual(this);
if (source?.CompositionTarget != null)
{
return VisualTreeHelper.GetDpi(this);
}
}
catch (Exception ex)
{
Console.WriteLine($"获取DPI缩放失败: {ex.Message}");
}
// 返回默认缩放
return new System.Windows.DpiScale(1.0, 1.0);
}
private void ShowMainWindow_Click(object sender, MouseButtonEventArgs e)
{
OnShowMainWindow?.Invoke();
this.Hide();
}
private void HideToTray_Click(object sender, MouseButtonEventArgs e)
{
OnHideToTray?.Invoke();
this.Hide();
}
private void About_Click(object sender, MouseButtonEventArgs e)
{
System.Windows.MessageBox.Show("这是一个带有系统托盘功能的WPF应用程序。", "关于",
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information);
this.Hide();
}
private void ExitApplication_Click(object sender, MouseButtonEventArgs e)
{
var result = System.Windows.MessageBox.Show("确定要退出程序吗?", "确认退出",
System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
if (result == System.Windows.MessageBoxResult.Yes)
{
OnExitApplication?.Invoke();
}
this.Hide();
}
private void Window_Deactivated(object sender, EventArgs e)
{
// 当窗口失去焦点时自动隐藏
this.Hide();
}
}
@@ -9,7 +9,7 @@ using Infrastructure.utils;
namespace Shell.UserControls;
public partial class WebPanel : UserControl
public partial class WebPanel : System.Windows.Controls.UserControl
{
public WebPanel()
{
@@ -87,7 +87,7 @@ public partial class WebPanel : UserControl
switch (msg?.Channel)
{
case "showMessage":
MessageBox.Show(msg.Payload?.Content ?? "无内容", msg.Payload?.Title ?? "提示");
System.Windows.MessageBox.Show(msg.Payload?.Content ?? "无内容", msg.Payload?.Title ?? "提示");
InitWebView1();
break;
case "GetNetInfo":
@@ -175,4 +175,4 @@ public class JsData
{
return $"Title:{Title}, Content:{Content}";
}
}
}
@@ -0,0 +1,299 @@
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using Shell.UserControls;
namespace Shell.Utils;
public class TrayIconManager : IDisposable
{
private NotifyIcon _notifyIcon;
private Window _mainWindow;
private TrayMenuWindow _trayMenu;
private DispatcherTimer _clickTimer;
private bool _isDoubleClick = false;
// Win32 API 用于获取鼠标位置和显示器信息
[DllImport("user32.dll")]
private static extern bool GetCursorPos(out POINT lpPoint);
[DllImport("user32.dll")]
private static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags);
[DllImport("user32.dll")]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
// 添加DPI相关API
[DllImport("user32.dll")]
private static extern uint GetDpiForWindow(IntPtr hwnd);
[DllImport("user32.dll")]
private static extern uint GetDpiForSystem();
[DllImport("shcore.dll")]
private static extern int GetDpiForMonitor(IntPtr hmonitor, DpiType dpiType, out uint dpiX, out uint dpiY);
public enum DpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
public uint cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}
private const uint MONITOR_DEFAULTTONEAREST = 2;
public TrayIconManager(Window mainWindow)
{
_mainWindow = mainWindow;
InitializeTrayIcon();
InitializeClickTimer();
}
private void InitializeTrayIcon()
{
_notifyIcon = new NotifyIcon
{
Icon = LoadIcon(),
Visible = true,
Text = "你的应用程序"
};
_notifyIcon.MouseClick += OnTrayIconClick;
_notifyIcon.MouseDoubleClick += OnTrayIconDoubleClick;
}
private void InitializeClickTimer()
{
_clickTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(300)
};
_clickTimer.Tick += OnClickTimerTick;
}
private Icon LoadIcon()
{
try
{
return new Icon("Assets/Icons/app.ico");
}
catch
{
return SystemIcons.Application;
}
}
private void OnTrayIconClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
try
{
ShowContextMenu();
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
}
else if (e.Button == MouseButtons.Left)
{
_isDoubleClick = false;
_clickTimer.Start();
}
}
private void OnTrayIconDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
_isDoubleClick = true;
_clickTimer.Stop();
ShowMainWindow();
}
}
private void OnClickTimerTick(object sender, EventArgs e)
{
_clickTimer.Stop();
if (!_isDoubleClick)
{
// 单击逻辑(可选)
}
}
private void ShowMainWindow()
{
if (_mainWindow != null)
{
_mainWindow.Show();
_mainWindow.WindowState = WindowState.Normal;
_mainWindow.Activate();
_mainWindow.Focus();
}
}
private void ShowContextMenu()
{
Console.WriteLine("=== TrayIconManager ShowContextMenu ===");
if (_trayMenu == null)
{
_trayMenu = new TrayMenuWindow();
_trayMenu.OnShowMainWindow += () => ShowMainWindow();
_trayMenu.OnExitApplication += () => System.Windows.Application.Current.Shutdown();
_trayMenu.OnHideToTray += () => _mainWindow?.Hide();
Console.WriteLine("创建新的TrayMenuWindow");
}
// 使用Win32 API获取鼠标位置
if (GetCursorPos(out POINT mousePos))
{
Console.WriteLine($"Win32 API获取鼠标物理位置: X={mousePos.X}, Y={mousePos.Y}");
// 获取鼠标所在显示器的工作区域和DPI信息
var (workArea, dpiScaleX, dpiScaleY) = GetMouseMonitorInfoWithDpi(mousePos);
Console.WriteLine(
$"显示器工作区域: Left={workArea.Left}, Top={workArea.Top}, Right={workArea.Right}, Bottom={workArea.Bottom}");
Console.WriteLine($"DPI缩放比例: ScaleX={dpiScaleX:F2}, ScaleY={dpiScaleY:F2}");
// 将物理坐标转换为WPF逻辑坐标
var logicalMouseX = mousePos.X / dpiScaleX;
var logicalMouseY = mousePos.Y / dpiScaleY;
// 将工作区域也转换为逻辑坐标
var logicalWorkArea = new RECT
{
Left = (int)(workArea.Left / dpiScaleX),
Top = (int)(workArea.Top / dpiScaleY),
Right = (int)(workArea.Right / dpiScaleX),
Bottom = (int)(workArea.Bottom / dpiScaleY)
};
Console.WriteLine($"逻辑鼠标位置: X={logicalMouseX:F1}, Y={logicalMouseY:F1}");
Console.WriteLine(
$"逻辑工作区域: Left={logicalWorkArea.Left}, Top={logicalWorkArea.Top}, Right={logicalWorkArea.Right}, Bottom={logicalWorkArea.Bottom}");
_trayMenu.ShowAtWithWorkArea((int)logicalMouseX, (int)logicalMouseY, logicalWorkArea);
}
else
{
Console.WriteLine("获取鼠标位置失败,使用默认位置");
// 如果获取鼠标位置失败,使用托盘图标的估计位置
var workArea = SystemParameters.WorkArea;
var defaultX = (int)workArea.Right - 50;
var defaultY = (int)workArea.Bottom - 50;
Console.WriteLine($"使用默认位置: X={defaultX}, Y={defaultY}");
_trayMenu.ShowAt(defaultX, defaultY);
}
Console.WriteLine("=== ShowContextMenu End ===\n");
}
private (RECT workArea, double dpiScaleX, double dpiScaleY) GetMouseMonitorInfoWithDpi(POINT mousePos)
{
// 获取鼠标所在的显示器
IntPtr hMonitor = MonitorFromPoint(mousePos, MONITOR_DEFAULTTONEAREST);
// 获取显示器信息
MONITORINFO monitorInfo = new MONITORINFO();
monitorInfo.cbSize = (uint)Marshal.SizeOf(monitorInfo);
RECT workArea;
double dpiScaleX = 1.0;
double dpiScaleY = 1.0;
if (GetMonitorInfo(hMonitor, ref monitorInfo))
{
workArea = monitorInfo.rcWork;
// 获取该显示器的DPI
try
{
if (GetDpiForMonitor(hMonitor, DpiType.Effective, out uint dpiX, out uint dpiY) == 0)
{
dpiScaleX = dpiX / 96.0; // 96 DPI是标准DPI
dpiScaleY = dpiY / 96.0;
Console.WriteLine($"显示器DPI: X={dpiX}, Y={dpiY}");
}
else
{
// 如果GetDpiForMonitor失败,尝试使用系统DPI
var systemDpi = GetDpiForSystem();
dpiScaleX = dpiScaleY = systemDpi / 96.0;
Console.WriteLine($"使用系统DPI: {systemDpi}");
}
}
catch (Exception ex)
{
Console.WriteLine($"获取DPI失败: {ex.Message},使用默认缩放");
// 作为后备方案,使用WPF的DPI信息
var source = PresentationSource.FromVisual(System.Windows.Application.Current.MainWindow);
if (source?.CompositionTarget != null)
{
var matrix = source.CompositionTarget.TransformFromDevice;
dpiScaleX = 1.0 / matrix.M11;
dpiScaleY = 1.0 / matrix.M22;
}
}
}
else
{
// 如果获取失败,返回主显示器的工作区域
var wpfWorkArea = SystemParameters.WorkArea;
workArea = new RECT
{
Left = (int)wpfWorkArea.Left,
Top = (int)wpfWorkArea.Top,
Right = (int)wpfWorkArea.Right,
Bottom = (int)wpfWorkArea.Bottom
};
// 使用WPF的DPI信息作为后备
var source = PresentationSource.FromVisual(System.Windows.Application.Current.MainWindow);
if (source?.CompositionTarget != null)
{
var matrix = source.CompositionTarget.TransformFromDevice;
dpiScaleX = 1.0 / matrix.M11;
dpiScaleY = 1.0 / matrix.M22;
}
}
return (workArea, dpiScaleX, dpiScaleY);
}
public void Dispose()
{
_notifyIcon?.Dispose();
_trayMenu?.Close();
_clickTimer?.Stop();
}
}
@@ -1,11 +1,41 @@
using System.ComponentModel;
using System.Windows;
using Shell.Utils;
namespace Shell.Views;
public partial class NetView : Window
{
private TrayIconManager _trayIconManager;
public NetView()
{
InitializeComponent();
InitializeTrayIcon();
}
}
private void InitializeTrayIcon()
{
_trayIconManager = new TrayIconManager(this);
}
protected override void OnClosing(CancelEventArgs e)
{
// 阻止窗口关闭,而是隐藏到托盘
e.Cancel = true;
this.Hide();
}
protected override void OnClosed(EventArgs e)
{
_trayIconManager?.Dispose();
base.OnClosed(e);
}
// 如果你想要真正关闭程序的方法
public void ForceClose()
{
_trayIconManager?.Dispose();
System.Windows.Application.Current.Shutdown();
}
}
@@ -15,7 +15,7 @@ public class WindowsFirewallApi : IFirewallApi
/// Windows防火墙策略对象,用于访问防火墙的全局设置和状态
/// </summary>
private readonly INetFwPolicy2? _fwPolicy;
/// <summary>
/// Windows防火墙规则集合对象,用于管理防火墙规则
/// </summary>
@@ -73,9 +73,9 @@ public class WindowsFirewallApi : IFirewallApi
// 获取规则统计信息
var allRules = GetAllRules();
status.TotalRules = allRules.Count; // 总规则数
status.EnabledRules = allRules.Count(r => r.Enabled); // 已启用规则数
status.InboundRules = allRules.Count(r => r.Direction == RuleDirection.Inbound); // 入站规则数
status.TotalRules = allRules.Count; // 总规则数
status.EnabledRules = allRules.Count(r => r.Enabled); // 已启用规则数
status.InboundRules = allRules.Count(r => r.Direction == RuleDirection.Inbound); // 入站规则数
status.OutboundRules = allRules.Count(r => r.Direction == RuleDirection.Outbound); // 出站规则数
return status;
@@ -164,12 +164,12 @@ public class WindowsFirewallApi : IFirewallApi
return new ProfileStatus
{
Profile = profile,
IsEnabled = _fwPolicy.FirewallEnabled[profileType], // 是否启用
BlockAllInboundTraffic = _fwPolicy.BlockAllInboundTraffic[profileType], // 是否阻止所有入站流量
NotifyOnListen = _fwPolicy.NotificationsDisabled[profileType] == false, // 是否显示通知
IsEnabled = _fwPolicy.FirewallEnabled[profileType], // 是否启用
BlockAllInboundTraffic = _fwPolicy.BlockAllInboundTraffic[profileType], // 是否阻止所有入站流量
NotifyOnListen = _fwPolicy.NotificationsDisabled[profileType] == false, // 是否显示通知
UnicastResponsesDisabled = _fwPolicy.UnicastResponsesToMulticastBroadcastDisabled[profileType], // 单播响应设置
DefaultInboundAction = (RuleAction)_fwPolicy.DefaultInboundAction[profileType], // 默认入站动作
DefaultOutboundAction = (RuleAction)_fwPolicy.DefaultOutboundAction[profileType] // 默认出站动作
DefaultInboundAction = (RuleAction)_fwPolicy.DefaultInboundAction[profileType], // 默认入站动作
DefaultOutboundAction = (RuleAction)_fwPolicy.DefaultOutboundAction[profileType] // 默认出站动作
};
}
@@ -186,7 +186,7 @@ public class WindowsFirewallApi : IFirewallApi
if (_fwPolicy == null) return false;
var profileType = ConvertToNetFwProfile(profile);
// 设置各项配置
_fwPolicy.FirewallEnabled[profileType] = status.IsEnabled;
_fwPolicy.BlockAllInboundTraffic[profileType] = status.BlockAllInboundTraffic;
@@ -194,7 +194,7 @@ public class WindowsFirewallApi : IFirewallApi
_fwPolicy.UnicastResponsesToMulticastBroadcastDisabled[profileType] = status.UnicastResponsesDisabled;
_fwPolicy.DefaultInboundAction[profileType] = (NET_FW_ACTION_)status.DefaultInboundAction;
_fwPolicy.DefaultOutboundAction[profileType] = (NET_FW_ACTION_)status.DefaultOutboundAction;
return true;
}
catch
@@ -219,7 +219,8 @@ public class WindowsFirewallApi : IFirewallApi
// 遍历Windows防火墙中的所有规则并转换为自定义格式
foreach (INetFwRule rule in _fwRules)
{
rules.Add(ConvertToFirewallRule(rule));
var convertToFirewallRule = ConvertToFirewallRule(rule);
rules.Add(convertToFirewallRule);
}
return rules;
@@ -358,22 +359,22 @@ public class WindowsFirewallApi : IFirewallApi
if (newRule == null) return false;
// 设置规则的各项属性
newRule.Name = request.Name; // 规则名称
newRule.Description = request.Description; // 规则描述
newRule.ApplicationName = request.ApplicationName; // 应用程序路径
newRule.Name = request.Name; // 规则名称
newRule.Description = request.Description; // 规则描述
newRule.ApplicationName = request.ApplicationName; // 应用程序路径
// 注意:INetFwRule 接口可能没有 ServiceName 属性,这里移除了对它的直接赋值
newRule.Protocol = (int)request.Protocol; // 协议类型
newRule.LocalPorts = request.LocalPorts; // 本地端口
newRule.RemotePorts = request.RemotePorts; // 远程端口
newRule.LocalAddresses = request.LocalAddresses; // 本地地址
newRule.RemoteAddresses = request.RemoteAddresses; // 远程地址
newRule.IcmpTypesAndCodes = request.IcmpTypesAndCodes; // ICMP类型和代码
newRule.Direction = (NET_FW_RULE_DIRECTION_)request.Direction; // 流量方向
newRule.Enabled = request.Enabled; // 是否启用
newRule.Profiles = ConvertToNetFwProfiles(request.Profiles); // 适用的配置文件
newRule.Action = (NET_FW_ACTION_)request.Action; // 规则动作
newRule.Grouping = request.Grouping; // 规则组
newRule.InterfaceTypes = request.InterfaceTypes; // 接口类型
newRule.Protocol = (int)request.Protocol; // 协议类型
newRule.LocalPorts = request.LocalPorts; // 本地端口
newRule.RemotePorts = request.RemotePorts; // 远程端口
newRule.LocalAddresses = request.LocalAddresses; // 本地地址
newRule.RemoteAddresses = request.RemoteAddresses; // 远程地址
newRule.IcmpTypesAndCodes = request.IcmpTypesAndCodes; // ICMP类型和代码
newRule.Direction = (NET_FW_RULE_DIRECTION_)request.Direction; // 流量方向
newRule.Enabled = request.Enabled; // 是否启用
newRule.Profiles = ConvertToNetFwProfiles(request.Profiles); // 适用的配置文件
newRule.Action = (NET_FW_ACTION_)request.Action; // 规则动作
newRule.Grouping = request.Grouping; // 规则组
newRule.InterfaceTypes = request.InterfaceTypes; // 接口类型
// 设置边缘遍历(用于某些特殊网络场景)
if (request.EdgeTraversal)
@@ -469,7 +470,7 @@ public class WindowsFirewallApi : IFirewallApi
{
// 先获取组中的所有规则
var rules = GetRulesByGroup(groupName);
// 逐一删除组中的每个规则
foreach (var rule in rules)
{
@@ -620,14 +621,14 @@ public class WindowsFirewallApi : IFirewallApi
try
{
var rules = GetAllRules();
// 配置JSON序列化选项,包括缩进和枚举转换
var json = JsonSerializer.Serialize(rules, new JsonSerializerOptions
{
WriteIndented = true,
Converters = { new JsonStringEnumConverter() }
});
System.IO.File.WriteAllText(filePath, json);
return true;
}
@@ -647,7 +648,7 @@ public class WindowsFirewallApi : IFirewallApi
try
{
var json = System.IO.File.ReadAllText(filePath);
// 反序列化JSON文件中的规则
var rules = JsonSerializer.Deserialize<List<FirewallRule>>(json, new JsonSerializerOptions
{
@@ -700,9 +701,9 @@ public class WindowsFirewallApi : IFirewallApi
var rules = GetAllRules();
var stats = new FirewallStatistics
{
TotalRules = rules.Count, // 总规则数
EnabledRules = rules.Count(r => r.Enabled), // 已启用规则数
DisabledRules = rules.Count(r => !r.Enabled) // 已禁用规则数
TotalRules = rules.Count, // 总规则数
EnabledRules = rules.Count(r => r.Enabled), // 已启用规则数
DisabledRules = rules.Count(r => !r.Enabled) // 已禁用规则数
};
// 按流量方向统计规则数量
@@ -795,7 +796,7 @@ public class WindowsFirewallApi : IFirewallApi
public bool BlockApplication(string applicationPath)
{
var fileName = System.IO.Path.GetFileName(applicationPath);
// 创建入站阻止规则
var blockInbound = CreateRule(new CreateRuleRequest
{
@@ -1043,25 +1044,25 @@ public class WindowsFirewallApi : IFirewallApi
{
return new FirewallRule
{
Name = rule.Name ?? string.Empty, // 规则名称
Description = rule.Description ?? string.Empty, // 规则描述
ApplicationName = rule.ApplicationName ?? string.Empty, // 应用程序路径
Name = rule.Name ?? string.Empty, // 规则名称
Description = rule.Description ?? string.Empty, // 规则描述
ApplicationName = rule.ApplicationName ?? string.Empty, // 应用程序路径
// ServiceName 在某些版本的 INetFwRule 中可能不存在,使用默认值
ServiceName = string.Empty, // 服务名称
Protocol = (ProtocolType)rule.Protocol, // 协议类型
LocalPorts = rule.LocalPorts ?? string.Empty, // 本地端口
RemotePorts = rule.RemotePorts ?? string.Empty, // 远程端口
LocalAddresses = rule.LocalAddresses ?? string.Empty, // 本地地址
RemoteAddresses = rule.RemoteAddresses ?? string.Empty, // 远程地址
IcmpTypesAndCodes = rule.IcmpTypesAndCodes ?? string.Empty, // ICMP类型和代码
Direction = (RuleDirection)rule.Direction, // 流量方向
Enabled = rule.Enabled, // 是否启用
Profiles = ConvertFromNetFwProfiles(rule.Profiles), // 适用的配置文件
EdgeTraversal = rule.EdgeTraversal, // 边缘遍历设置
Action = (RuleAction)rule.Action, // 规则动作
Grouping = rule.Grouping ?? string.Empty, // 规则组
InterfaceTypes = rule.InterfaceTypes ?? string.Empty, // 接口类型
Interfaces = ParseInterfaces(rule.Interfaces) // 接口列表
ServiceName = string.Empty, // 服务名称
Protocol = (ProtocolType)rule.Protocol, // 协议类型
LocalPorts = rule.LocalPorts ?? string.Empty, // 本地端口
RemotePorts = rule.RemotePorts ?? string.Empty, // 远程端口
LocalAddresses = rule.LocalAddresses ?? string.Empty, // 本地地址
RemoteAddresses = rule.RemoteAddresses ?? string.Empty, // 远程地址
IcmpTypesAndCodes = rule.IcmpTypesAndCodes ?? string.Empty, // ICMP类型和代码
Direction = (RuleDirection)rule.Direction, // 流量方向
Enabled = rule.Enabled, // 是否启用
Profiles = ConvertFromNetFwProfiles(rule.Profiles), // 适用的配置文件
EdgeTraversal = rule.EdgeTraversal, // 边缘遍历设置
Action = (RuleAction)rule.Action, // 规则动作
Grouping = rule.Grouping ?? string.Empty, // 规则组
InterfaceTypes = rule.InterfaceTypes ?? string.Empty, // 接口类型
Interfaces = ParseInterfaces(rule.Interfaces) // 接口列表
};
}
@@ -1137,4 +1138,4 @@ public class WindowsFirewallApi : IFirewallApi
}
#endregion
}
}
+3
View File
@@ -1,2 +1,5 @@
VITE_MODE='development'
VITE_APP_API_URL='http://localhost:8268/api'
我想让FirewallRuleForm.vue的滚动区域,只在页面主容器内进行,比如“基础内容”很多,那么就在“基础内容”的
子Tab中进行滚动,不能超出去,不然滚动时将会整体滚动,非常不优雅,而且你这次优化的样式我觉得还有提升空间,你可以参考MonitorView下的组件进行样式优化
+3 -1
View File
@@ -9,14 +9,16 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
CommandManager: typeof import('./src/components/CommandManager.vue')['default']
FirewallRuleForm: typeof import('./src/components/FirewallRuleForm.vue')['default']
IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
NetworkConnectionPanel2: typeof import('./src/components/NetworkConnectionPanel2.vue')['default']
NHighlight: typeof import('naive-ui')['NHighlight']
NInput: typeof import('naive-ui')['NInput']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NVirtualList: typeof import('naive-ui')['NVirtualList']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
TitleBar: typeof import('./src/components/TitleBar.vue')['default']
@@ -1,922 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防火墙管理中心</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--secondary: #64748b;
--success: #22c55e;
--danger: #ef4444;
--warning: #f59e0b;
--background: #0f172a;
--surface: #1e293b;
--surface-light: #334155;
--text: #f1f5f9;
--text-secondary: #94a3b8;
--border: #475569;
--shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--background);
color: var(--text);
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Header */
.header {
background: var(--surface);
padding: 20px 30px;
border-radius: 16px;
margin-bottom: 30px;
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
border: 1px solid var(--border);
}
.header h1 {
font-size: 28px;
font-weight: 600;
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
}
.stats {
display: flex;
gap: 30px;
margin-top: 20px;
}
.stat-item {
display: flex;
align-items: center;
gap: 10px;
}
.stat-icon {
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.stat-icon.active {
background: rgba(34, 197, 94, 0.2);
color: var(--success);
}
.stat-icon.total {
background: rgba(37, 99, 235, 0.2);
color: var(--primary);
}
.stat-icon.blocked {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
}
/* Controls */
.controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.search-box {
position: relative;
flex: 1;
max-width: 400px;
}
.search-box input {
width: 100%;
padding: 12px 20px 12px 45px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--text);
font-size: 14px;
transition: all 0.3s;
}
.search-box input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.search-box::before {
content: "🔍";
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%;
}
.filter-buttons {
display: flex;
gap: 10px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 10px 20px -10px rgba(37, 99, 235, 0.5);
}
.btn-secondary {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--surface-light);
}
.btn-secondary.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
/* Rules Table */
.rules-container {
background: var(--surface);
border-radius: 16px;
overflow: hidden;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
.rules-table {
width: 100%;
border-collapse: collapse;
}
.rules-table th {
background: var(--surface-light);
padding: 16px 20px;
text-align: left;
font-weight: 600;
font-size: 14px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid var(--border);
}
.rules-table td {
padding: 16px 20px;
border-bottom: 1px solid rgba(71, 85, 105, 0.3);
font-size: 14px;
}
.rules-table tr:hover {
background: rgba(37, 99, 235, 0.05);
cursor: pointer;
}
.rules-table tr:hover td {
color: var(--text);
}
.rule-name {
font-weight: 500;
display: flex;
align-items: center;
gap: 10px;
}
.rule-icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
background: rgba(37, 99, 235, 0.1);
}
.status-badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
display: inline-block;
}
.status-enabled {
background: rgba(34, 197, 94, 0.2);
color: var(--success);
}
.status-disabled {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
}
.direction-badge {
padding: 4px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
.direction-in {
background: rgba(37, 99, 235, 0.2);
color: #60a5fa;
}
.direction-out {
background: rgba(245, 158, 11, 0.2);
color: #fbbf24;
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(5px);
z-index: 1000;
animation: fadeIn 0.3s;
}
.modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: var(--surface);
border-radius: 20px;
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
border: 1px solid var(--border);
animation: slideUp 0.3s;
}
.modal-header {
padding: 30px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 {
font-size: 24px;
font-weight: 600;
}
.close-btn {
width: 36px;
height: 36px;
border-radius: 8px;
border: none;
background: var(--surface-light);
color: var(--text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
transition: all 0.3s;
}
.close-btn:hover {
background: var(--danger);
transform: scale(1.1);
}
.modal-body {
padding: 30px;
}
.form-section {
margin-bottom: 30px;
}
.form-section h3 {
font-size: 18px;
margin-bottom: 15px;
color: var(--text);
display: flex;
align-items: center;
gap: 10px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: var(--text-secondary);
font-weight: 500;
}
.form-control {
width: 100%;
padding: 12px 16px;
background: var(--background);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text);
font-size: 14px;
transition: all 0.3s;
}
.form-control:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
select.form-control {
cursor: pointer;
}
.radio-group, .checkbox-group {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.radio-item, .checkbox-item {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.radio-item input[type="radio"],
.checkbox-item input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
border-bottom: 1px solid var(--border);
}
.tab {
padding: 12px 24px;
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 14px;
font-weight: 500;
position: relative;
transition: all 0.3s;
}
.tab:hover {
color: var(--text);
}
.tab.active {
color: var(--primary);
}
.tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background: var(--primary);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.port-range {
display: flex;
gap: 10px;
align-items: center;
}
.service-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--border);
border-radius: 10px;
padding: 10px;
background: var(--background);
}
.service-item {
padding: 8px;
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
}
.service-item:hover {
background: var(--surface-light);
}
.modal-footer {
padding: 20px 30px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 15px;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from {
transform: translateY(50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--surface);
}
::-webkit-scrollbar-thumb {
background: var(--surface-light);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--border);
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>防火墙管理中心</h1>
<p style="color: var(--text-secondary);">实时监控和管理网络安全规则</p>
<div class="stats">
<div class="stat-item">
<div class="stat-icon active">✓</div>
<div>
<div style="font-size: 24px; font-weight: 600;">42</div>
<div style="font-size: 12px; color: var(--text-secondary);">活动规则</div>
</div>
</div>
<div class="stat-item">
<div class="stat-icon total">🛡️</div>
<div>
<div style="font-size: 24px; font-weight: 600;">68</div>
<div style="font-size: 12px; color: var(--text-secondary);">总规则数</div>
</div>
</div>
<div class="stat-item">
<div class="stat-icon blocked">🚫</div>
<div>
<div style="font-size: 24px; font-weight: 600;">1,247</div>
<div style="font-size: 12px; color: var(--text-secondary);">今日阻止</div>
</div>
</div>
</div>
</div>
<!-- Controls -->
<div class="controls">
<div class="search-box">
<input type="text" placeholder="搜索规则名称、程序、端口...">
</div>
<div class="filter-buttons">
<button class="btn btn-secondary active" onclick="filterRules('all')">全部</button>
<button class="btn btn-secondary" onclick="filterRules('inbound')">入站</button>
<button class="btn btn-secondary" onclick="filterRules('outbound')">出站</button>
<button class="btn btn-primary" onclick="openModal()">
<span>+</span> 新建规则
</button>
</div>
</div>
<!-- Rules Table -->
<div class="rules-container">
<table class="rules-table">
<thead>
<tr>
<th>名称</th>
<th>状态</th>
<th>方向</th>
<th>程序</th>
<th>协议/端口</th>
<th>作用域</th>
<th>操作</th>
</tr>
</thead>
<tbody id="rulesTableBody">
<!-- Rules will be populated here -->
</tbody>
</table>
</div>
</div>
<!-- Modal -->
<div class="modal" id="ruleModal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">新建防火墙规则</h2>
<button class="close-btn" onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<!-- Basic Settings -->
<div class="form-section">
<h3>🔧 基本设置</h3>
<div class="form-group">
<label>规则名称</label>
<input type="text" class="form-control" id="ruleName" placeholder="输入规则名称">
</div>
<div class="form-group">
<label>描述(可选)</label>
<input type="text" class="form-control" id="ruleDescription" placeholder="输入规则描述">
</div>
<div class="form-group">
<label>方向</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="direction" value="inbound" checked>
<span>入站</span>
</label>
<label class="radio-item">
<input type="radio" name="direction" value="outbound">
<span>出站</span>
</label>
</div>
</div>
<div class="form-group">
<label>操作</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="action" value="allow" checked>
<span>允许连接</span>
</label>
<label class="radio-item">
<input type="radio" name="action" value="block">
<span>阻止连接</span>
</label>
<label class="radio-item">
<input type="radio" name="action" value="secure">
<span>允许安全连接</span>
</label>
</div>
</div>
</div>
<!-- Program Settings -->
<div class="form-section">
<h3>📱 程序设置</h3>
<div class="tabs">
<button class="tab active" onclick="switchTab('program', 'allPrograms')">所有程序</button>
<button class="tab" onclick="switchTab('program', 'specificProgram')">指定程序</button>
<button class="tab" onclick="switchTab('program', 'service')">服务</button>
</div>
<div class="tab-content active" id="allPrograms">
<p style="color: var(--text-secondary);">此规则适用于所有程序</p>
</div>
<div class="tab-content" id="specificProgram">
<div class="form-group">
<label>程序路径</label>
<input type="text" class="form-control" placeholder="浏览或输入程序路径">
</div>
</div>
<div class="tab-content" id="service">
<div class="form-group">
<label>选择服务</label>
<div class="service-list">
<div class="service-item">
<input type="checkbox" id="service1">
<label for="service1">Windows Update</label>
</div>
<div class="service-item">
<input type="checkbox" id="service2">
<label for="service2">远程桌面服务</label>
</div>
<div class="service-item">
<input type="checkbox" id="service3">
<label for="service3">文件和打印机共享</label>
</div>
<div class="service-item">
<input type="checkbox" id="service4">
<label for="service4">SQL Server</label>
</div>
<div class="service-item">
<input type="checkbox" id="service5">
<label for="service5">IIS</label>
</div>
</div>
</div>
</div>
</div>
<!-- Protocol and Ports -->
<div class="form-section">
<h3>🔌 协议和端口</h3>
<div class="form-group">
<label>协议类型</label>
<select class="form-control" id="protocol" onchange="updatePortOptions()">
<option value="any">任意</option>
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
<option value="icmpv4">ICMPv4</option>
<option value="icmpv6">ICMPv6</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="form-group" id="portSettings" style="display: none;">
<label>端口</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="portType" value="all" checked>
<span>所有端口</span>
</label>
<label class="radio-item">
<input type="radio" name="portType" value="specific">
<span>特定端口</span>
</label>
</div>
<div class="port-range" style="margin-top: 10px; display: none;" id="portRange">
<input type="text" class="form-control" placeholder="端口或范围 (如: 80, 443, 1000-2000)" style="flex: 1;">
</div>
</div>
</div>
<!-- Scope -->
<div class="form-section">
<h3>🌐 作用域</h3>
<div class="form-group">
<label>本地IP地址</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="localIP" value="any" checked>
<span>任何IP地址</span>
</label>
<label class="radio-item">
<input type="radio" name="localIP" value="specific">
<span>指定IP地址</span>
</label>
</div>
</div>
<div class="form-group">
<label>远程IP地址</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="remoteIP" value="any" checked>
<span>任何IP地址</span>
</label>
<label class="radio-item">
<input type="radio" name="remoteIP" value="specific">
<span>指定IP地址</span>
</label>
</div>
</div>
</div>
<!-- Advanced -->
<div class="form-section">
<h3>⚙️ 高级设置</h3>
<div class="form-group">
<label>配置文件</label>
<div class="checkbox-group">
<label class="checkbox-item">
<input type="checkbox" checked>
<span>域</span>
</label>
<label class="checkbox-item">
<input type="checkbox" checked>
<span>专用</span>
</label>
<label class="checkbox-item">
<input type="checkbox" checked>
<span>公用</span>
</label>
</div>
</div>
<div class="form-group">
<label>用户和组</label>
<button class="btn btn-secondary">选择用户或组...</button>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal()">取消</button>
<button class="btn btn-primary" onclick="saveRule()">保存规则</button>
</div>
</div>
</div>
<script>
// Sample data
const rules = [
{
id: 1,
name: "Chrome 浏览器",
status: "enabled",
direction: "outbound",
program: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
protocol: "TCP",
port: "80, 443",
scope: "任意",
action: "允许"
},
{
id: 2,
name: "远程桌面 - 用户模式 (TCP-In)",
status: "enabled",
direction: "inbound",
program: "系统",
protocol: "TCP",
port: "3389",
scope: "任意",
action: "允许"
},
{
id: 3,
name: "文件和打印机共享 (SMB-In)",
status: "disabled",
direction: "inbound",
program: "系统",
protocol: "TCP",
port: "445",
scope: "本地子网",
action: "允许"
},
{
id: 4,
name: "Windows Update",
status: "enabled",
direction: "outbound",
program: "svchost.exe",
protocol: "TCP/UDP",
port: "80, 443",
scope: "任意",
action: "允许"
},
{
id: 5,
name: "SQL Server",
status: "enabled",
direction: "inbound",
program: "sqlservr.exe",
protocol: "TCP",
port: "1433",
scope: "指定IP",
action: "允许"
},
{
id: 6,
name: "阻止可疑端口",
status: "enabled",
direction: "inbound",
program: "所有程序",
protocol: "TCP",
port: "135, 139, 445",
scope: "任意",
action: "阻止"
}
];
// Initialize
function init() {
renderRules(rules);
}
// Render rules table
function renderRules(rulesToRender) {
const tbody = document.getElementById('rulesTableBody');
tbody.innerHTML = '';
rulesToRender.forEach(rule => {
const row = document.createElement('tr');
row.onclick = () => editRule(rule);
row.innerHTML = `
<td>
<div class="rule-name">
<div class="rule-icon">${rule.direction === 'inbound' ? '↓' : '↑'}</div>
<span>${rule.name}</span>
</div>
</td>
<td>
<span class="status-badge status-${rule.status}">
${rule.status === 'enabled' ? '已启用' : '已禁用'}
</span>
</td>
<td>
<span class="direction-badge direction-${rule.direction === 'inbound' ? 'in' : 'out'}">
${rule.direction === 'inbound' ? '入站' : '出站'}
</span>
</td>
<td>${rule.program.split('\\').pop()}</td>
<td>${rule.protocol}${rule.port !== '任意' ? ': ' + rule.port : ''}</td>
<td>${rule.scope}</td>
<td>${rule.action}</td>
`;
tbody.appendChild(row);
});
}
// Filter rules
function filterRules(type) {
const buttons = document.querySelectorAll('.filter-buttons .btn-secondary');
buttons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
let filtered = rules;
if (type === 'inbound') {
filtered = rules.filter(r => r.direction === 'inbound');
} else if (type === 'outbound') {
filtered = rules.filter(r => r.direction === 'outbound');
}
renderRules(filtered);
}
// Search functionality
document.querySelector('.search-box input').addEventListener('input', function(e
@@ -1,1497 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防火墙管理中心</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--secondary: #64748b;
--success: #22c55e;
--danger: #ef4444;
--warning: #f59e0b;
--background: #0f172a;
--surface: #1e293b;
--surface-light: #334155;
--text: #f1f5f9;
--text-secondary: #94a3b8;
--border: #475569;
--shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--background);
color: var(--text);
line-height: 1.6;
overflow-x: hidden;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Header */
.header {
background: var(--surface);
padding: 20px 30px;
border-radius: 16px;
margin-bottom: 30px;
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
border: 1px solid var(--border);
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary), var(--success), var(--warning));
}
.header h1 {
font-size: 28px;
font-weight: 600;
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
}
.stats {
display: flex;
gap: 30px;
margin-top: 20px;
flex-wrap: wrap;
}
.stat-item {
display: flex;
align-items: center;
gap: 15px;
padding: 15px;
background: rgba(37, 99, 235, 0.05);
border-radius: 12px;
border: 1px solid rgba(37, 99, 235, 0.1);
transition: all 0.3s;
}
.stat-item:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px -8px rgba(37, 99, 235, 0.3);
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
backdrop-filter: blur(10px);
}
.stat-icon.active {
background: rgba(34, 197, 94, 0.2);
color: var(--success);
box-shadow: 0 0 20px rgba(34, 197, 94, 0.3);
}
.stat-icon.total {
background: rgba(37, 99, 235, 0.2);
color: var(--primary);
box-shadow: 0 0 20px rgba(37, 99, 235, 0.3);
}
.stat-icon.blocked {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
box-shadow: 0 0 20px rgba(239, 68, 68, 0.3);
}
/* Controls */
.controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.search-box {
position: relative;
flex: 1;
max-width: 400px;
}
.search-box input {
width: 100%;
padding: 12px 20px 12px 45px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--text);
font-size: 14px;
transition: all 0.3s;
}
.search-box input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
transform: translateY(-1px);
}
.search-box::before {
content: "🔍";
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
}
.filter-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
color: white;
box-shadow: 0 4px 15px rgba(37, 99, 235, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(37, 99, 235, 0.4);
}
.btn-secondary {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--surface-light);
transform: translateY(-1px);
}
.btn-secondary.active {
background: var(--primary);
color: white;
border-color: var(--primary);
box-shadow: 0 4px 15px rgba(37, 99, 235, 0.3);
}
/* Rules Table */
.rules-container {
background: var(--surface);
border-radius: 16px;
overflow: hidden;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
.rules-table {
width: 100%;
border-collapse: collapse;
}
.rules-table th {
background: var(--surface-light);
padding: 16px 20px;
text-align: left;
font-weight: 600;
font-size: 14px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
}
.rules-table td {
padding: 16px 20px;
border-bottom: 1px solid rgba(71, 85, 105, 0.3);
font-size: 14px;
vertical-align: middle;
}
.rules-table tr {
transition: all 0.3s;
}
.rules-table tr:hover {
background: linear-gradient(90deg, rgba(37, 99, 235, 0.05), rgba(37, 99, 235, 0.1));
cursor: pointer;
transform: scale(1.01);
}
.rules-table tr:hover td {
color: var(--text);
}
.rule-name {
font-weight: 500;
display: flex;
align-items: center;
gap: 12px;
}
.rule-icon {
width: 36px;
height: 36px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.1), rgba(37, 99, 235, 0.2));
border: 1px solid rgba(37, 99, 235, 0.2);
}
.status-badge {
padding: 6px 14px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
display: inline-block;
animation: pulse 2s infinite;
}
.status-enabled {
background: rgba(34, 197, 94, 0.2);
color: var(--success);
border: 1px solid rgba(34, 197, 94, 0.3);
}
.status-disabled {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
border: 1px solid rgba(239, 68, 68, 0.3);
}
.direction-badge {
padding: 6px 14px;
border-radius: 8px;
font-size: 12px;
font-weight: 500;
border: 1px solid;
}
.direction-in {
background: rgba(37, 99, 235, 0.2);
color: #60a5fa;
border-color: rgba(37, 99, 235, 0.3);
}
.direction-out {
background: rgba(245, 158, 11, 0.2);
color: #fbbf24;
border-color: rgba(245, 158, 11, 0.3);
}
.action-cell {
display: flex;
gap: 8px;
}
.action-btn {
padding: 6px 12px;
border: none;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s;
}
.action-btn.edit {
background: rgba(37, 99, 235, 0.2);
color: var(--primary);
}
.action-btn.delete {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
}
.action-btn:hover {
transform: scale(1.1);
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
z-index: 1000;
animation: fadeIn 0.3s;
}
.modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: var(--surface);
border-radius: 20px;
width: 90%;
max-width: 900px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7);
border: 1px solid var(--border);
animation: slideUp 0.3s;
}
.modal-header {
padding: 30px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(135deg, var(--surface), var(--surface-light));
}
.modal-header h2 {
font-size: 24px;
font-weight: 600;
background: linear-gradient(135deg, #60a5fa, #3b82f6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.close-btn {
width: 40px;
height: 40px;
border-radius: 10px;
border: none;
background: var(--surface-light);
color: var(--text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
transition: all 0.3s;
}
.close-btn:hover {
background: var(--danger);
transform: scale(1.1) rotate(90deg);
}
.modal-body {
padding: 30px;
}
.form-section {
margin-bottom: 30px;
padding: 25px;
background: rgba(37, 99, 235, 0.03);
border-radius: 12px;
border: 1px solid rgba(37, 99, 235, 0.1);
}
.form-section h3 {
font-size: 18px;
margin-bottom: 20px;
color: var(--text);
display: flex;
align-items: center;
gap: 12px;
padding-bottom: 10px;
border-bottom: 2px solid rgba(37, 99, 235, 0.2);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: var(--text-secondary);
font-weight: 500;
}
.form-control {
width: 100%;
padding: 12px 16px;
background: var(--background);
border: 2px solid var(--border);
border-radius: 10px;
color: var(--text);
font-size: 14px;
transition: all 0.3s;
}
.form-control:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
transform: translateY(-1px);
}
select.form-control {
cursor: pointer;
}
.radio-group, .checkbox-group {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.radio-item, .checkbox-item {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
padding: 8px 12px;
border-radius: 8px;
transition: all 0.3s;
}
.radio-item:hover, .checkbox-item:hover {
background: rgba(37, 99, 235, 0.1);
}
.radio-item input[type="radio"],
.checkbox-item input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.tabs {
display: flex;
gap: 5px;
margin-bottom: 20px;
border-bottom: 2px solid var(--border);
padding-bottom: 0;
}
.tab {
padding: 12px 24px;
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 14px;
font-weight: 500;
position: relative;
transition: all 0.3s;
border-radius: 8px 8px 0 0;
}
.tab:hover {
color: var(--text);
background: rgba(37, 99, 235, 0.1);
}
.tab.active {
color: var(--primary);
background: rgba(37, 99, 235, 0.1);
}
.tab.active::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 3px;
background: var(--primary);
border-radius: 2px;
}
.tab-content {
display: none;
padding: 20px;
background: var(--background);
border-radius: 8px;
border: 1px solid var(--border);
}
.tab-content.active {
display: block;
}
.port-range {
display: flex;
gap: 10px;
align-items: center;
}
.service-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--border);
border-radius: 10px;
padding: 10px;
background: var(--background);
}
.service-item {
padding: 12px;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s;
border: 1px solid transparent;
}
.service-item:hover {
background: var(--surface-light);
border-color: var(--primary);
transform: translateX(5px);
}
.modal-footer {
padding: 20px 30px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 15px;
background: rgba(37, 99, 235, 0.02);
}
/* Notification */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 10px;
color: white;
font-weight: 500;
z-index: 2000;
animation: slideInRight 0.3s;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.notification.success {
background: var(--success);
}
.notification.error {
background: var(--danger);
}
.notification.info {
background: var(--primary);
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from {
transform: translateY(50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--surface);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: var(--surface-light);
border-radius: 4px;
transition: all 0.3s;
}
::-webkit-scrollbar-thumb:hover {
background: var(--border);
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.stats {
flex-direction: column;
gap: 15px;
}
.controls {
flex-direction: column;
align-items: stretch;
}
.search-box {
max-width: none;
}
.filter-buttons {
justify-content: center;
}
.rules-table {
font-size: 12px;
}
.rules-table th,
.rules-table td {
padding: 10px;
}
.modal-content {
width: 95%;
margin: 10px;
}
.modal-body {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>防火墙管理中心</h1>
<p style="color: var(--text-secondary);">实时监控和管理网络安全规则</p>
<div class="stats">
<div class="stat-item">
<div class="stat-icon active">✓</div>
<div>
<div style="font-size: 24px; font-weight: 600;" id="activeRules">42</div>
<div style="font-size: 12px; color: var(--text-secondary);">活动规则</div>
</div>
</div>
<div class="stat-item">
<div class="stat-icon total">🛡️</div>
<div>
<div style="font-size: 24px; font-weight: 600;" id="totalRules">68</div>
<div style="font-size: 12px; color: var(--text-secondary);">总规则数</div>
</div>
</div>
<div class="stat-item">
<div class="stat-icon blocked">🚫</div>
<div>
<div style="font-size: 24px; font-weight: 600;" id="blockedToday">1,247</div>
<div style="font-size: 12px; color: var(--text-secondary);">今日阻止</div>
</div>
</div>
</div>
</div>
<!-- Controls -->
<div class="controls">
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索规则名称、程序、端口...">
</div>
<div class="filter-buttons">
<button class="btn btn-secondary active" onclick="filterRules('all')">全部</button>
<button class="btn btn-secondary" onclick="filterRules('inbound')">入站</button>
<button class="btn btn-secondary" onclick="filterRules('outbound')">出站</button>
<button class="btn btn-primary" onclick="openModal()">
<span>+</span> 新建规则
</button>
</div>
</div>
<!-- Rules Table -->
<div class="rules-container">
<table class="rules-table">
<thead>
<tr>
<th>名称</th>
<th>状态</th>
<th>方向</th>
<th>程序</th>
<th>协议/端口</th>
<th>作用域</th>
<th>操作</th>
</tr>
</thead>
<tbody id="rulesTableBody">
<!-- Rules will be populated here -->
</tbody>
</table>
</div>
</div>
<!-- Modal -->
<div class="modal" id="ruleModal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">新建防火墙规则</h2>
<button class="close-btn" onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<form id="ruleForm">
<!-- Basic Settings -->
<div class="form-section">
<h3>🔧 基本设置</h3>
<div class="form-group">
<label>规则名称 *</label>
<input type="text" class="form-control" id="ruleName" placeholder="输入规则名称" required>
</div>
<div class="form-group">
<label>描述(可选)</label>
<input type="text" class="form-control" id="ruleDescription" placeholder="输入规则描述">
</div>
<div class="form-group">
<label>方向</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="direction" value="inbound" checked>
<span>入站</span>
</label>
<label class="radio-item">
<input type="radio" name="direction" value="outbound">
<span>出站</span>
</label>
</div>
</div>
<div class="form-group">
<label>操作</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="action" value="allow" checked>
<span>允许连接</span>
</label>
<label class="radio-item">
<input type="radio" name="action" value="block">
<span>阻止连接</span>
</label>
<label class="radio-item">
<input type="radio" name="action" value="secure">
<span>允许安全连接</span>
</label>
</div>
</div>
</div>
<!-- Program Settings -->
<div class="form-section">
<h3>📱 程序设置</h3>
<div class="tabs">
<button type="button" class="tab active" onclick="switchTab('program', 'allPrograms')">所有程序</button>
<button type="button" class="tab" onclick="switchTab('program', 'specificProgram')">指定程序</button>
<button type="button" class="tab" onclick="switchTab('program', 'service')">服务</button>
</div>
<div class="tab-content active" id="allPrograms">
<p style="color: var(--text-secondary);">此规则适用于所有程序</p>
</div>
<div class="tab-content" id="specificProgram">
<div class="form-group">
<label>程序路径</label>
<input type="text" class="form-control" id="programPath" placeholder="浏览或输入程序路径">
<button type="button" class="btn btn-secondary" style="margin-top: 10px;" onclick="browseProgramPath()">浏览...</button>
</div>
</div>
<div class="tab-content" id="service">
<div class="form-group">
<label>选择服务</label>
<div class="service-list">
<div class="service-item">
<input type="checkbox" id="service1" value="windows_update">
<label for="service1">Windows Update</label>
</div>
<div class="service-item">
<input type="checkbox" id="service2" value="remote_desktop">
<label for="service2">远程桌面服务</label>
</div>
<div class="service-item">
<input type="checkbox" id="service3" value="file_sharing">
<label for="service3">文件和打印机共享</label>
</div>
<div class="service-item">
<input type="checkbox" id="service4" value="sql_server">
<label for="service4">SQL Server</label>
</div>
<div class="service-item">
<input type="checkbox" id="service5" value="iis">
<label for="service5">IIS</label>
</div>
</div>
</div>
</div>
</div>
<!-- Protocol and Ports -->
<div class="form-section">
<h3>🔌 协议和端口</h3>
<div class="form-group">
<label>协议类型</label>
<select class="form-control" id="protocol" onchange="updatePortOptions()">
<option value="any">任意</option>
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
<option value="icmpv4">ICMPv4</option>
<option value="icmpv6">ICMPv6</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="form-group" id="portSettings" style="display: none;">
<label>端口</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="portType" value="all" checked onchange="togglePortRange()">
<span>所有端口</span>
</label>
<label class="radio-item">
<input type="radio" name="portType" value="specific" onchange="togglePortRange()">
<span>特定端口</span>
</label>
</div>
<div class="port-range" style="margin-top: 10px; display: none;" id="portRange">
<input type="text" class="form-control" id="portInput" placeholder="端口或范围 (如: 80, 443, 1000-2000)" style="flex: 1;">
</div>
</div>
</div>
<!-- Scope -->
<div class="form-section">
<h3>🌐 作用域</h3>
<div class="form-group">
<label>本地IP地址</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="localIP" value="any" checked onchange="toggleLocalIP()">
<span>任何IP地址</span>
</label>
<label class="radio-item">
<input type="radio" name="localIP" value="specific" onchange="toggleLocalIP()">
<span>指定IP地址</span>
</label>
</div>
<div id="localIPInput" style="margin-top: 10px; display: none;">
<input type="text" class="form-control" placeholder="输入IP地址或范围">
</div>
</div>
<div class="form-group">
<label>远程IP地址</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="remoteIP" value="any" checked onchange="toggleRemoteIP()">
<span>任何IP地址</span>
</label>
<label class="radio-item">
<input type="radio" name="remoteIP" value="specific" onchange="toggleRemoteIP()">
<span>指定IP地址</span>
</label>
</div>
<div id="remoteIPInput" style="margin-top: 10px; display: none;">
<input type="text" class="form-control" placeholder="输入IP地址或范围">
</div>
</div>
</div>
<!-- Advanced -->
<div class="form-section">
<h3>⚙️ 高级设置</h3>
<div class="form-group">
<label>配置文件</label>
<div class="checkbox-group">
<label class="checkbox-item">
<input type="checkbox" name="profile" value="domain" checked>
<span>域</span>
</label>
<label class="checkbox-item">
<input type="checkbox" name="profile" value="private" checked>
<span>专用</span>
</label>
<label class="checkbox-item">
<input type="checkbox" name="profile" value="public" checked>
<span>公用</span>
</label>
</div>
</div>
<div class="form-group">
<label>启用规则</label>
<div class="checkbox-group">
<label class="checkbox-item">
<input type="checkbox" id="enableRule" checked>
<span>创建后立即启用此规则</span>
</label>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal()">取消</button>
<button type="button" class="btn btn-primary" onclick="saveRule()">保存规则</button>
</div>
</div>
</div>
<script>
// Global variables
let rules = [
{
id: 1,
name: "Chrome 浏览器",
status: "enabled",
direction: "outbound",
program: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
protocol: "TCP",
port: "80, 443",
scope: "任意",
action: "允许"
},
{
id: 2,
name: "远程桌面 - 用户模式 (TCP-In)",
status: "enabled",
direction: "inbound",
program: "系统",
protocol: "TCP",
port: "3389",
scope: "任意",
action: "允许"
},
{
id: 3,
name: "文件和打印机共享 (SMB-In)",
status: "disabled",
direction: "inbound",
program: "系统",
protocol: "TCP",
port: "445",
scope: "本地子网",
action: "允许"
},
{
id: 4,
name: "Windows Update",
status: "enabled",
direction: "outbound",
program: "svchost.exe",
protocol: "TCP/UDP",
port: "80, 443",
scope: "任意",
action: "允许"
},
{
id: 5,
name: "SQL Server",
status: "enabled",
direction: "inbound",
program: "sqlservr.exe",
protocol: "TCP",
port: "1433",
scope: "指定IP",
action: "允许"
},
{
id: 6,
name: "阻止可疑端口",
status: "enabled",
direction: "inbound",
program: "所有程序",
protocol: "TCP",
port: "135, 139, 445",
scope: "任意",
action: "阻止"
},
{
id: 7,
name: "SSH 服务",
status: "enabled",
direction: "inbound",
program: "sshd.exe",
protocol: "TCP",
port: "22",
scope: "指定IP",
action: "允许"
},
{
id: 8,
name: "HTTP 服务器",
status: "enabled",
direction: "inbound",
program: "httpd.exe",
protocol: "TCP",
port: "80",
scope: "任意",
action: "允许"
}
];
let currentFilter = 'all';
let editingRuleId = null;
// Initialize application
function init() {
renderRules(rules);
updateStats();
setupEventListeners();
}
// Setup event listeners
function setupEventListeners() {
const searchInput = document.getElementById('searchInput');
searchInput.addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
const filteredRules = rules.filter(rule => {
const matchesSearch = rule.name.toLowerCase().includes(searchTerm) ||
rule.program.toLowerCase().includes(searchTerm) ||
rule.port.toLowerCase().includes(searchTerm) ||
rule.protocol.toLowerCase().includes(searchTerm);
const matchesFilter = currentFilter === 'all' || rule.direction === currentFilter;
return matchesSearch && matchesFilter;
});
renderRules(filteredRules);
});
// Close modal when clicking outside
document.getElementById('ruleModal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// Prevent form submission
document.getElementById('ruleForm').addEventListener('submit', function(e) {
e.preventDefault();
saveRule();
});
}
// Update statistics
function updateStats() {
const activeRules = rules.filter(rule => rule.status === 'enabled').length;
const totalRules = rules.length;
const blockedToday = Math.floor(Math.random() * 2000) + 1000; // Simulated data
document.getElementById('activeRules').textContent = activeRules;
document.getElementById('totalRules').textContent = totalRules;
document.getElementById('blockedToday').textContent = blockedToday.toLocaleString();
}
// Render rules table
function renderRules(rulesToRender) {
const tbody = document.getElementById('rulesTableBody');
tbody.innerHTML = '';
if (rulesToRender.length === 0) {
const row = document.createElement('tr');
row.innerHTML = `
<td colspan="7" style="text-align: center; padding: 40px; color: var(--text-secondary);">
没有找到匹配的规则
</td>
`;
tbody.appendChild(row);
return;
}
rulesToRender.forEach(rule => {
const row = document.createElement('tr');
row.innerHTML = `
<td>
<div class="rule-name">
<div class="rule-icon">${rule.direction === 'inbound' ? '↓' : '↑'}</div>
<span>${rule.name}</span>
</div>
</td>
<td>
<span class="status-badge status-${rule.status}">
${rule.status === 'enabled' ? '已启用' : '已禁用'}
</span>
</td>
<td>
<span class="direction-badge direction-${rule.direction === 'inbound' ? 'in' : 'out'}">
${rule.direction === 'inbound' ? '入站' : '出站'}
</span>
</td>
<td>${rule.program.split('\\').pop()}</td>
<td>${rule.protocol}${rule.port !== '任意' ? ': ' + rule.port : ''}</td>
<td>${rule.scope}</td>
<td>
<div class="action-cell">
<button class="action-btn edit" onclick="editRule(${rule.id})" title="编辑">
✏️
</button>
<button class="action-btn delete" onclick="deleteRule(${rule.id})" title="删除">
🗑️
</button>
</div>
</td>
`;
tbody.appendChild(row);
});
}
// Filter rules
function filterRules(type) {
currentFilter = type;
// Update button states
const buttons = document.querySelectorAll('.filter-buttons .btn-secondary');
buttons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
// Apply filter
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
let filtered = rules;
if (type !== 'all') {
filtered = rules.filter(r => r.direction === type);
}
if (searchTerm) {
filtered = filtered.filter(rule => {
return rule.name.toLowerCase().includes(searchTerm) ||
rule.program.toLowerCase().includes(searchTerm) ||
rule.port.toLowerCase().includes(searchTerm) ||
rule.protocol.toLowerCase().includes(searchTerm);
});
}
renderRules(filtered);
}
// Modal functions
function openModal(ruleId = null) {
editingRuleId = ruleId;
const modal = document.getElementById('ruleModal');
const modalTitle = document.getElementById('modalTitle');
if (ruleId) {
const rule = rules.find(r => r.id === ruleId);
modalTitle.textContent = '编辑防火墙规则';
populateForm(rule);
} else {
modalTitle.textContent = '新建防火墙规则';
resetForm();
}
modal.classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeModal() {
const modal = document.getElementById('ruleModal');
modal.classList.remove('active');
document.body.style.overflow = '';
editingRuleId = null;
resetForm();
}
function resetForm() {
document.getElementById('ruleForm').reset();
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
document.querySelector('.tab').classList.add('active');
document.querySelector('.tab-content').classList.add('active');
document.getElementById('portSettings').style.display = 'none';
document.getElementById('portRange').style.display = 'none';
}
function populateForm(rule) {
document.getElementById('ruleName').value = rule.name;
document.querySelector(`input[name="direction"][value="${rule.direction}"]`).checked = true;
document.querySelector(`input[name="action"][value="${rule.action.toLowerCase()}"]`).checked = true;
document.getElementById('enableRule').checked = rule.status === 'enabled';
// Set protocol and ports
if (rule.protocol !== '任意') {
const protocolSelect = document.getElementById('protocol');
if (rule.protocol.includes('/')) {
protocolSelect.value = 'custom';
} else {
protocolSelect.value = rule.protocol.toLowerCase();
}
updatePortOptions();
if (rule.port !== '任意') {
document.querySelector('input[name="portType"][value="specific"]').checked = true;
togglePortRange();
document.getElementById('portInput').value = rule.port;
}
}
}
// Tab switching
function switchTab(group, tabId) {
// Remove active class from all tabs in this group
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
// Add active class to clicked tab and its content
event.target.classList.add('active');
document.getElementById(tabId).classList.add('active');
}
// Protocol and port handling
function updatePortOptions() {
const protocol = document.getElementById('protocol').value;
const portSettings = document.getElementById('portSettings');
if (protocol === 'tcp' || protocol === 'udp' || protocol === 'custom') {
portSettings.style.display = 'block';
} else {
portSettings.style.display = 'none';
}
}
function togglePortRange() {
const portType = document.querySelector('input[name="portType"]:checked').value;
const portRange = document.getElementById('portRange');
if (portType === 'specific') {
portRange.style.display = 'flex';
} else {
portRange.style.display = 'none';
}
}
function toggleLocalIP() {
const ipType = document.querySelector('input[name="localIP"]:checked').value;
const ipInput = document.getElementById('localIPInput');
if (ipType === 'specific') {
ipInput.style.display = 'block';
} else {
ipInput.style.display = 'none';
}
}
function toggleRemoteIP() {
const ipType = document.querySelector('input[name="remoteIP"]:checked').value;
const ipInput = document.getElementById('remoteIPInput');
if (ipType === 'specific') {
ipInput.style.display = 'block';
} else {
ipInput.style.display = 'none';
}
}
// Rule management
function saveRule() {
const form = document.getElementById('ruleForm');
const formData = new FormData(form);
// Validate required fields
const ruleName = document.getElementById('ruleName').value.trim();
if (!ruleName) {
showNotification('请输入规则名称', 'error');
return;
}
// Create rule object
const rule = {
id: editingRuleId || Date.now(),
name: ruleName,
description: document.getElementById('ruleDescription').value,
status: document.getElementById('enableRule').checked ? 'enabled' : 'disabled',
direction: document.querySelector('input[name="direction"]:checked').value,
action: document.querySelector('input[name="action"]:checked').value,
program: getProgramValue(),
protocol: getProtocolValue(),
port: getPortValue(),
scope: getScopeValue()
};
if (editingRuleId) {
// Update existing rule
const index = rules.findIndex(r => r.id === editingRuleId);
if (index !== -1) {
rules[index] = rule;
showNotification('规则已更新', 'success');
}
} else {
// Add new rule
rules.push(rule);
showNotification('规则已创建', 'success');
}
renderRules(rules);
updateStats();
closeModal();
}
function editRule(ruleId) {
openModal(ruleId);
}
function deleteRule(ruleId) {
if (confirm('确定要删除这个规则吗?')) {
rules = rules.filter(r => r.id !== ruleId);
renderRules(rules);
updateStats();
showNotification('规则已删除', 'info');
}
}
// Helper functions for form data
function getProgramValue() {
const activeTab = document.querySelector('.tab.active').textContent;
if (activeTab === '所有程序') {
return '所有程序';
} else if (activeTab === '指定程序') {
return document.getElementById('programPath').value || '指定程序';
} else {
const selectedServices = Array.from(document.querySelectorAll('#service input[type="checkbox"]:checked'))
.map(cb => cb.nextElementSibling.textContent).join(', ');
return selectedServices || '服务';
}
}
function getProtocolValue() {
const protocol = document.getElementById('protocol').value;
return protocol === 'any' ? '任意' : protocol.toUpperCase();
}
function getPortValue() {
const protocol = document.getElementById('protocol').value;
if (protocol === 'any' || protocol === 'icmpv4' || protocol === 'icmpv6') {
return '任意';
}
const portType = document.querySelector('input[name="portType"]:checked');
if (!portType || portType.value === 'all') {
return '任意';
}
return document.getElementById('portInput').value || '任意';
}
function getScopeValue() {
const localIP = document.querySelector('input[name="localIP"]:checked').value;
const remoteIP = document.querySelector('input[name="remoteIP"]:checked').value;
if (localIP === 'any' && remoteIP === 'any') {
return '任意';
} else if (localIP === 'specific' || remoteIP === 'specific') {
return '指定IP';
} else {
return '本地子网';
}
}
function browseProgramPath() {
// Simulate file browser (in real implementation, this would open a file dialog)
const path = prompt('请输入程序路径:', 'C:\\Program Files\\');
if (path) {
document.getElementById('programPath').value = path;
}
}
// Notification system
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
// Initialize application when DOM is loaded
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
@@ -1,1205 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防火墙管理看板</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
}
.header p {
font-size: 1.1rem;
color: #666;
margin-bottom: 20px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: rgba(255, 255, 255, 0.9);
border-radius: 15px;
padding: 25px;
text-align: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.stat-number {
font-size: 2.5rem;
font-weight: 700;
color: #667eea;
margin-bottom: 10px;
}
.stat-label {
font-size: 0.9rem;
color: #666;
text-transform: uppercase;
letter-spacing: 1px;
}
.controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
flex-wrap: wrap;
gap: 15px;
}
.search-box {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.9);
border-radius: 25px;
padding: 10px 20px;
min-width: 300px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.search-box input {
border: none;
outline: none;
background: transparent;
flex: 1;
padding: 8px;
font-size: 1rem;
}
.search-box i {
color: #666;
margin-right: 10px;
}
.btn {
padding: 12px 25px;
border: none;
border-radius: 25px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.6);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.9);
color: #333;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.btn-secondary:hover {
background: white;
transform: translateY(-2px);
}
.table-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.table {
width: 100%;
border-collapse: collapse;
}
.table thead {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
.table th,
.table td {
padding: 15px 20px;
text-align: left;
border-bottom: 1px solid #eee;
}
.table th {
font-weight: 600;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.table tbody tr {
transition: background-color 0.3s ease;
}
.table tbody tr:hover {
background-color: #f8f9ff;
}
.status-badge {
padding: 5px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
}
.status-enabled {
background: #d4edda;
color: #155724;
}
.status-disabled {
background: #f8d7da;
color: #721c24;
}
.action-badge {
padding: 5px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
}
.action-allow {
background: #d1ecf1;
color: #0c5460;
}
.action-block {
background: #f5c6cb;
color: #721c24;
}
.direction-badge {
padding: 5px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
}
.direction-inbound {
background: #ffeaa7;
color: #d68910;
}
.direction-outbound {
background: #fab1a0;
color: #e17055;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
z-index: 1000;
animation: fadeIn 0.3s ease;
}
.modal.show {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
border-radius: 20px;
padding: 40px;
max-width: 800px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.3);
transform: scale(0.8);
animation: modalScale 0.3s ease forwards;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes modalScale {
to { transform: scale(1); }
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #eee;
}
.modal-title {
font-size: 1.8rem;
font-weight: 700;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 2rem;
cursor: pointer;
color: #999;
transition: color 0.3s ease;
}
.close-btn:hover {
color: #333;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 25px;
margin-bottom: 30px;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group.full-width {
grid-column: 1 / -1;
}
.form-label {
font-weight: 600;
margin-bottom: 8px;
color: #333;
font-size: 0.95rem;
}
.form-input,
.form-select,
.form-textarea {
padding: 12px 15px;
border: 2px solid #eee;
border-radius: 10px;
font-size: 1rem;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.form-textarea {
resize: vertical;
min-height: 80px;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: #667eea;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 15px;
padding-top: 20px;
border-top: 2px solid #eee;
}
.icon {
width: 16px;
height: 16px;
display: inline-block;
margin-right: 5px;
}
.action-buttons {
display: flex;
gap: 10px;
}
.btn-small {
padding: 6px 12px;
font-size: 0.8rem;
border-radius: 15px;
}
.btn-edit {
background: #17a2b8;
color: white;
}
.btn-edit:hover {
background: #138496;
}
.btn-delete {
background: #dc3545;
color: white;
}
.btn-delete:hover {
background: #c82333;
}
.tabs {
display: flex;
border-bottom: 2px solid #eee;
margin-bottom: 25px;
}
.tab {
padding: 12px 25px;
border: none;
background: none;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
color: #666;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
}
.tab.active {
color: #667eea;
border-bottom-color: #667eea;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
@media (max-width: 768px) {
.form-grid {
grid-template-columns: 1fr;
}
.controls {
flex-direction: column;
align-items: stretch;
}
.search-box {
min-width: auto;
}
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>🛡️ 防火墙管理中心</h1>
<p>监控和管理网络安全策略,保护您的系统免受恶意攻击</p>
<!-- Statistics -->
<div class="stats">
<div class="stat-card">
<div class="stat-number">127</div>
<div class="stat-label">活跃规则</div>
</div>
<div class="stat-card">
<div class="stat-number">89</div>
<div class="stat-label">入站规则</div>
</div>
<div class="stat-card">
<div class="stat-number">38</div>
<div class="stat-label">出站规则</div>
</div>
<div class="stat-card">
<div class="stat-number">15</div>
<div class="stat-label">阻止的连接</div>
</div>
</div>
</div>
<!-- Controls -->
<div class="controls">
<div class="search-box">
<span>🔍</span>
<input type="text" placeholder="搜索防火墙规则..." id="searchInput">
</div>
<div>
<button class="btn btn-primary" onclick="openModal('add')">
➕ 新建规则
</button>
<button class="btn btn-secondary" onclick="refreshRules()">
🔄 刷新
</button>
</div>
</div>
<!-- Rules Table -->
<div class="table-container">
<table class="table" id="rulesTable">
<thead>
<tr>
<th>名称</th>
<th>方向</th>
<th>操作</th>
<th>协议</th>
<th>端口</th>
<th>程序</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="rulesTableBody">
<!-- 动态生成的内容 -->
</tbody>
</table>
</div>
</div>
<!-- Modal -->
<div class="modal" id="ruleModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="modalTitle">新建防火墙规则</h2>
<button class="close-btn" onclick="closeModal()">&times;</button>
</div>
<!-- Tabs -->
<div class="tabs">
<button class="tab active" onclick="switchTab('general')">常规</button>
<button class="tab" onclick="switchTab('protocols')">协议和端口</button>
<button class="tab" onclick="switchTab('programs')">程序和服务</button>
<button class="tab" onclick="switchTab('advanced')">高级</button>
</div>
<form id="ruleForm">
<!-- General Tab -->
<div class="tab-content active" id="general">
<div class="form-grid">
<div class="form-group">
<label class="form-label">规则名称</label>
<input type="text" class="form-input" name="name" placeholder="输入规则名称" required>
</div>
<div class="form-group">
<label class="form-label">方向</label>
<select class="form-select" name="direction" required>
<option value="inbound">入站</option>
<option value="outbound">出站</option>
</select>
</div>
<div class="form-group">
<label class="form-label">操作</label>
<select class="form-select" name="action" required>
<option value="allow">允许连接</option>
<option value="block">阻止连接</option>
<option value="allow_secure">只有安全连接才允许</option>
</select>
</div>
<div class="form-group">
<label class="form-label">配置文件</label>
<select class="form-select" name="profile">
<option value="all">所有配置文件</option>
<option value="domain">域</option>
<option value="private">专用</option>
<option value="public">公用</option>
</select>
</div>
<div class="form-group full-width">
<label class="form-label">描述</label>
<textarea class="form-textarea" name="description" placeholder="输入规则描述(可选)"></textarea>
</div>
<div class="form-group full-width">
<div class="checkbox-group">
<input type="checkbox" name="enabled" id="enabled" checked>
<label for="enabled">启用此规则</label>
</div>
</div>
</div>
</div>
<!-- Protocols Tab -->
<div class="tab-content" id="protocols">
<div class="form-grid">
<div class="form-group">
<label class="form-label">协议类型</label>
<select class="form-select" name="protocol" onchange="togglePortSettings()">
<option value="any">任何</option>
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
<option value="icmp">ICMPv4</option>
<option value="icmpv6">ICMPv6</option>
<option value="gre">GRE</option>
<option value="ah">AH</option>
<option value="esp">ESP</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="form-group" id="customProtocolGroup" style="display: none;">
<label class="form-label">自定义协议号</label>
<input type="number" class="form-input" name="customProtocol" placeholder="0-255">
</div>
<div class="form-group" id="localPortGroup">
<label class="form-label">本地端口</label>
<select class="form-select" name="localPortType" onchange="toggleLocalPortInput()">
<option value="all">所有端口</option>
<option value="specific">特定端口</option>
<option value="range">端口范围</option>
</select>
</div>
<div class="form-group" id="localPortInputGroup" style="display: none;">
<label class="form-label">端口值</label>
<input type="text" class="form-input" name="localPort" placeholder="例如: 80, 443 或 8000-9000">
</div>
<div class="form-group" id="remotePortGroup">
<label class="form-label">远程端口</label>
<select class="form-select" name="remotePortType" onchange="toggleRemotePortInput()">
<option value="all">所有端口</option>
<option value="specific">特定端口</option>
<option value="range">端口范围</option>
</select>
</div>
<div class="form-group" id="remotePortInputGroup" style="display: none;">
<label class="form-label">端口值</label>
<input type="text" class="form-input" name="remotePort" placeholder="例如: 80, 443 或 8000-9000">
</div>
</div>
</div>
<!-- Programs Tab -->
<div class="tab-content" id="programs">
<div class="form-grid">
<div class="form-group">
<label class="form-label">程序类型</label>
<select class="form-select" name="programType" onchange="toggleProgramSettings()">
<option value="all">所有程序</option>
<option value="specific">特定程序</option>
<option value="service">服务</option>
</select>
</div>
<div class="form-group" id="programPathGroup" style="display: none;">
<label class="form-label">程序路径</label>
<input type="text" class="form-input" name="programPath" placeholder="选择程序路径">
<button type="button" class="btn btn-secondary btn-small" style="margin-top: 10px;" onclick="browseProgramPath()">浏览...</button>
</div>
<div class="form-group" id="serviceGroup" style="display: none;">
<label class="form-label">应用于下列服务</label>
<select class="form-select" name="serviceType" onchange="toggleServiceOptions()">
<option value="all">所有服务</option>
<option value="specific">特定服务</option>
</select>
</div>
<div class="form-group" id="serviceListGroup" style="display: none;">
<label class="form-label">选择服务</label>
<select class="form-select" name="service" multiple size="5">
<option value="http">HTTP 服务</option>
<option value="https">HTTPS 服务</option>
<option value="ftp">FTP 服务</option>
<option value="ssh">SSH 服务</option>
<option value="telnet">Telnet 服务</option>
<option value="smtp">SMTP 服务</option>
<option value="pop3">POP3 服务</option>
<option value="imap">IMAP 服务</option>
<option value="dns">DNS 服务</option>
<option value="dhcp">DHCP 服务</option>
<option value="snmp">SNMP 服务</option>
<option value="ldap">LDAP 服务</option>
</select>
</div>
</div>
</div>
<!-- Advanced Tab -->
<div class="tab-content" id="advanced">
<div class="form-grid">
<div class="form-group">
<label class="form-label">本地IP地址</label>
<select class="form-select" name="localIPType" onchange="toggleLocalIPInput()">
<option value="any">任何IP地址</option>
<option value="specific">特定IP地址</option>
<option value="range">IP地址范围</option>
<option value="subnet">子网</option>
</select>
</div>
<div class="form-group" id="localIPInputGroup" style="display: none;">
<label class="form-label">IP地址</label>
<input type="text" class="form-input" name="localIP" placeholder="例如: 192.168.1.100 或 192.168.1.0/24">
</div>
<div class="form-group">
<label class="form-label">远程IP地址</label>
<select class="form-select" name="remoteIPType" onchange="toggleRemoteIPInput()">
<option value="any">任何IP地址</option>
<option value="specific">特定IP地址</option>
<option value="range">IP地址范围</option>
<option value="subnet">子网</option>
</select>
</div>
<div class="form-group" id="remoteIPInputGroup" style="display: none;">
<label class="form-label">IP地址</label>
<input type="text" class="form-input" name="remoteIP" placeholder="例如: 192.168.1.100 或 192.168.1.0/24">
</div>
<div class="form-group">
<label class="form-label">用户</label>
<select class="form-select" name="userType" onchange="toggleUserInput()">
<option value="any">任何用户</option>
<option value="specific">特定用户和组</option>
</select>
</div>
<div class="form-group" id="userInputGroup" style="display: none;">
<label class="form-label">用户和组</label>
<select class="form-select" name="users" multiple size="4">
<option value="administrators">Administrators</option>
<option value="users">Users</option>
<option value="guests">Guests</option>
<option value="everyone">Everyone</option>
<option value="authenticated_users">Authenticated Users</option>
<option value="network_service">Network Service</option>
<option value="local_service">Local Service</option>
<option value="system">System</option>
</select>
</div>
<div class="form-group">
<label class="form-label">接口类型</label>
<select class="form-select" name="interfaceType">
<option value="all">所有接口类型</option>
<option value="lan">局域网</option>
<option value="wan">广域网</option>
<option value="wireless">无线</option>
<option value="remote_access">远程访问</option>
</select>
</div>
<div class="form-group">
<label class="form-label">边缘遍历</label>
<select class="form-select" name="edgeTraversal">
<option value="block">阻止边缘遍历</option>
<option value="allow">允许边缘遍历</option>
<option value="defer_to_user">延迟到用户</option>
<option value="defer_to_app">延迟到应用程序</option>
</select>
</div>
</div>
</div>
</form>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal()">取消</button>
<button type="button" class="btn btn-primary" onclick="saveRule()">保存规则</button>
</div>
</div>
</div>
<script>
// 模拟数据
let firewallRules = [
{
id: 1,
name: "Core Networking - DNS (UDP-Out)",
direction: "outbound",
action: "allow",
protocol: "UDP",
localPort: "Any",
remotePort: "53",
program: "%SystemRoot%\\system32\\svchost.exe",
service: "DNS Client",
enabled: true,
profile: "all",
localIP: "Any",
remoteIP: "Any",
users: "Any",
description: "允许 DNS 查询出站连接"
},
{
id: 2,
name: "Windows Remote Management (HTTP-In)",
direction: "inbound",
action: "allow",
protocol: "TCP",
localPort: "5985",
remotePort: "Any",
program: "System",
service: "WinRM",
enabled: true,
profile: "domain",
localIP: "Any",
remoteIP: "Any",
users: "Administrators",
description: "允许 WinRM HTTP 入站连接"
},
{
id: 3,
name: "File and Printer Sharing (SMB-In)",
direction: "inbound",
action: "allow",
protocol: "TCP",
localPort: "445",
remotePort: "Any",
program: "System",
service: "Server",
enabled: false,
profile: "private",
localIP: "Any",
remoteIP: "LocalSubnet",
users: "Any",
description: "允许文件和打印机共享"
},
{
id: 4,
name: "Block Malicious Traffic",
direction: "inbound",
action: "block",
protocol: "Any",
localPort: "Any",
remotePort: "Any",
program: "Any",
service: "Any",
enabled: true,
profile: "all",
localIP: "Any",
remoteIP: "192.168.100.0/24",
users: "Any",
description: "阻止来自可疑网段的所有流量"
},
{
id: 5,
name: "Web Server (HTTP)",
direction: "inbound",
action: "allow",
protocol: "TCP",
localPort: "80",
remotePort: "Any",
program: "C:\\inetpub\\wwwroot\\httpd.exe",
service: "HTTP",
enabled: true,
profile: "all",
localIP: "Any",
remoteIP: "Any",
users: "Any",
description: "允许 Web 服务器 HTTP 连接"
},
{
id: 6,
name: "Secure Web Server (HTTPS)",
direction: "inbound",
action: "allow",
protocol: "TCP",
localPort: "443",
remotePort: "Any",
program: "C:\\inetpub\\wwwroot\\httpd.exe",
service: "HTTPS",
enabled: true,
profile: "all",
localIP: "Any",
remoteIP: "Any",
users: "Any",
description: "允许 Web 服务器 HTTPS 连接"
}
];
let currentRule = null;
let isEditMode = false;
// 初始化
document.addEventListener('DOMContentLoaded', function() {
renderRulesTable();
setupSearchFilter();
});
// 渲染规则表格
function renderRulesTable(rules = firewallRules) {
const tbody = document.getElementById('rulesTableBody');
tbody.innerHTML = '';
rules.forEach(rule => {
const row = document.createElement('tr');
row.innerHTML = `
<td><strong>${rule.name}</strong></td>
<td><span class="direction-badge direction-${rule.direction}">${rule.direction === 'inbound' ? '入站' : '出站'}</span></td>
<td><span class="action-badge action-${rule.action}">${getActionText(rule.action)}</span></td>
<td>${rule.protocol}</td>
<td>${rule.localPort}</td>
<td>${rule.program.includes('\\') ? rule.program.split('\\').pop() : rule.program}</td>
<td><span class="status-badge status-${rule.enabled ? 'enabled' : 'disabled'}">${rule.enabled ? '启用' : '禁用'}</span></td>
<td>
<div class="action-buttons">
<button class="btn btn-edit btn-small" onclick="editRule(${rule.id})">编辑</button>
<button class="btn btn-delete btn-small" onclick="deleteRule(${rule.id})">删除</button>
</div>
</td>
`;
tbody.appendChild(row);
});
}
// 获取操作文本
function getActionText(action) {
switch(action) {
case 'allow': return '允许';
case 'block': return '阻止';
case 'allow_secure': return '安全允许';
default: return action;
}
}
// 设置搜索过滤
function setupSearchFilter() {
const searchInput = document.getElementById('searchInput');
searchInput.addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
const filteredRules = firewallRules.filter(rule =>
rule.name.toLowerCase().includes(searchTerm) ||
rule.program.toLowerCase().includes(searchTerm) ||
rule.protocol.toLowerCase().includes(searchTerm)
);
renderRulesTable(filteredRules);
});
}
// 打开模态框
function openModal(mode, ruleId = null) {
const modal = document.getElementById('ruleModal');
const modalTitle = document.getElementById('modalTitle');
isEditMode = mode === 'edit';
if (isEditMode && ruleId) {
currentRule = firewallRules.find(rule => rule.id === ruleId);
modalTitle.textContent = '编辑防火墙规则';
populateForm(currentRule);
} else {
currentRule = null;
modalTitle.textContent = '新建防火墙规则';
resetForm();
}
modal.classList.add('show');
}
// 关闭模态框
function closeModal() {
const modal = document.getElementById('ruleModal');
modal.classList.remove('show');
resetForm();
}
// 重置表单
function resetForm() {
document.getElementById('ruleForm').reset();
document.querySelector('input[name="enabled"]').checked = true;
switchTab('general');
hideAllDynamicFields();
}
// 填充表单
function populateForm(rule) {
const form = document.getElementById('ruleForm');
Object.keys(rule).forEach(key => {
const field = form.querySelector(`[name="${key}"]`);
if (field) {
if (field.type === 'checkbox') {
field.checked = rule[key];
} else {
field.value = rule[key];
}
}
});
}
// 隐藏所有动态字段
function hideAllDynamicFields() {
const dynamicGroups = [
'customProtocolGroup', 'localPortInputGroup', 'remotePortInputGroup',
'programPathGroup', 'serviceGroup', 'serviceListGroup',
'localIPInputGroup', 'remoteIPInputGroup', 'userInputGroup'
];
dynamicGroups.forEach(groupId => {
const group = document.getElementById(groupId);
if (group) group.style.display = 'none';
});
}
// 切换标签页
function switchTab(tabName) {
// 移除所有标签的活动状态
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
// 激活选中的标签
event.target.classList.add('active');
document.getElementById(tabName).classList.add('active');
}
// 协议设置切换
function togglePortSettings() {
const protocol = document.querySelector('[name="protocol"]').value;
const customGroup = document.getElementById('customProtocolGroup');
const localPortGroup = document.getElementById('localPortGroup');
const remotePortGroup = document.getElementById('remotePortGroup');
if (protocol === 'custom') {
customGroup.style.display = 'block';
} else {
customGroup.style.display = 'none';
}
if (['tcp', 'udp'].includes(protocol)) {
localPortGroup.style.display = 'block';
remotePortGroup.style.display = 'block';
} else if (protocol === 'any') {
localPortGroup.style.display = 'block';
remotePortGroup.style.display = 'block';
} else {
localPortGroup.style.display = 'none';
remotePortGroup.style.display = 'none';
document.getElementById('localPortInputGroup').style.display = 'none';
document.getElementById('remotePortInputGroup').style.display = 'none';
}
}
// 本地端口输入切换
function toggleLocalPortInput() {
const portType = document.querySelector('[name="localPortType"]').value;
const inputGroup = document.getElementById('localPortInputGroup');
inputGroup.style.display = portType === 'all' ? 'none' : 'block';
}
// 远程端口输入切换
function toggleRemotePortInput() {
const portType = document.querySelector('[name="remotePortType"]').value;
const inputGroup = document.getElementById('remotePortInputGroup');
inputGroup.style.display = portType === 'all' ? 'none' : 'block';
}
// 程序设置切换
function toggleProgramSettings() {
const programType = document.querySelector('[name="programType"]').value;
const programPathGroup = document.getElementById('programPathGroup');
const serviceGroup = document.getElementById('serviceGroup');
programPathGroup.style.display = programType === 'specific' ? 'block' : 'none';
serviceGroup.style.display = programType === 'service' ? 'block' : 'none';
if (programType !== 'service') {
document.getElementById('serviceListGroup').style.display = 'none';
}
}
// 服务选项切换
function toggleServiceOptions() {
const serviceType = document.querySelector('[name="serviceType"]').value;
const serviceListGroup = document.getElementById('serviceListGroup');
serviceListGroup.style.display = serviceType === 'specific' ? 'block' : 'none';
}
// 本地IP输入切换
function toggleLocalIPInput() {
const ipType = document.querySelector('[name="localIPType"]').value;
const inputGroup = document.getElementById('localIPInputGroup');
inputGroup.style.display = ipType === 'any' ? 'none' : 'block';
}
// 远程IP输入切换
function toggleRemoteIPInput() {
const ipType = document.querySelector('[name="remoteIPType"]').value;
const inputGroup = document.getElementById('remoteIPInputGroup');
inputGroup.style.display = ipType === 'any' ? 'none' : 'block';
}
// 用户输入切换
function toggleUserInput() {
const userType = document.querySelector('[name="userType"]').value;
const inputGroup = document.getElementById('userInputGroup');
inputGroup.style.display = userType === 'any' ? 'none' : 'block';
}
// 浏览程序路径
function browseProgramPath() {
// 模拟文件浏览器
const path = prompt('请输入程序路径:', 'C:\\Program Files\\');
if (path) {
document.querySelector('[name="programPath"]').value = path;
}
}
// 编辑规则
function editRule(ruleId) {
openModal('edit', ruleId);
}
// 删除规则
function deleteRule(ruleId) {
if (confirm('确定要删除这条规则吗?')) {
firewallRules = firewallRules.filter(rule => rule.id !== ruleId);
renderRulesTable();
}
}
// 保存规则
function saveRule() {
const form = document.getElementById('ruleForm');
const formData = new FormData(form);
const ruleData = {
name: formData.get('name'),
direction: formData.get('direction'),
action: formData.get('action'),
protocol: formData.get('protocol'),
localPort: formData.get('localPort') || 'Any',
remotePort: formData.get('remotePort') || 'Any',
program: formData.get('programPath') || 'Any',
service: formData.get('service') || 'Any',
enabled: formData.has('enabled'),
profile: formData.get('profile'),
localIP: formData.get('localIP') || 'Any',
remoteIP: formData.get('remoteIP') || 'Any',
users: formData.get('users') || 'Any',
description: formData.get('description') || ''
};
if (!ruleData.name) {
alert('请输入规则名称');
return;
}
if (isEditMode && currentRule) {
// 更新现有规则
Object.assign(currentRule, ruleData);
} else {
// 添加新规则
ruleData.id = Date.now();
firewallRules.unshift(ruleData);
}
renderRulesTable();
closeModal();
// 显示成功消息
showNotification(isEditMode ? '规则已更新' : '规则已创建');
}
// 刷新规则
function refreshRules() {
renderRulesTable();
showNotification('规则已刷新');
}
// 显示通知
function showNotification(message) {
// 创建一个简单的通知
const notification = document.createElement('div');
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #4CAF50;
color: white;
padding: 15px 25px;
border-radius: 5px;
z-index: 10000;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
animation: slideIn 0.3s ease;
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// 添加CSS动画
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
document.head.appendChild(style);
// 点击模态框外部关闭
document.getElementById('ruleModal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
</script>
</body>
</html>
@@ -0,0 +1,951 @@
<template>
<n-modal
v-model:show="visible"
preset="card"
:style="{ width: '1000px', height: '90vh' }"
:title="isEdit ? '编辑防火墙规则' : '新建防火墙规则'"
:closable="true"
:auto-focus="false"
:mask-closable="false"
class="firewall-rule-modal"
@close="handleClose"
>
<div class="rule-form-container">
<n-form
ref="formRef"
:model="formData"
:rules="formRules"
label-placement="top"
require-mark-placement="right-hanging"
class="firewall-form"
>
<!-- 标签页 -->
<n-tabs v-model:value="activeTab" type="line" size="medium" animated class="rule-tabs">
<!-- 基本信息 -->
<n-tab-pane name="basic" tab="基本信息">
<div class="tab-content">
<div class="form-section">
<div class="section-title">
<n-icon :component="InformationCircleOutline" />
规则标识
</div>
<div class="form-grid">
<n-form-item path="name" label="规则名称" required>
<n-input
v-model:value="formData.name"
placeholder="输入唯一的规则名称"
maxlength="255"
show-count
/>
</n-form-item>
<n-form-item path="description" label="规则描述">
<n-input
v-model:value="formData.description"
type="textarea"
placeholder="描述此规则的用途和目的"
:rows="3"
maxlength="500"
show-count
/>
</n-form-item>
<n-form-item path="grouping" label="规则分组">
<n-input
v-model:value="formData.grouping"
placeholder="可选:输入分组名称"
maxlength="100"
/>
</n-form-item>
</div>
</div>
<div class="form-section">
<div class="section-title">
<n-icon :component="SettingsOutline" />
规则行为
</div>
<div class="form-grid">
<div class="form-row">
<n-form-item path="enabled" label="规则状态">
<n-radio-group v-model:value="formData.enabled">
<n-radio :value="true">启用</n-radio>
<n-radio :value="false">禁用</n-radio>
</n-radio-group>
</n-form-item>
<n-form-item path="direction" label="规则方向" required>
<n-radio-group v-model:value="formData.direction">
<n-radio :value="1">入站 (Inbound)</n-radio>
<n-radio :value="2">出站 (Outbound)</n-radio>
</n-radio-group>
</n-form-item>
</div>
<n-form-item path="action" label="规则动作" required>
<n-radio-group v-model:value="formData.action">
<n-radio :value="1">允许 (Allow)</n-radio>
<n-radio :value="0">阻止 (Block)</n-radio>
</n-radio-group>
</n-form-item>
</div>
</div>
</div>
</n-tab-pane>
<!-- 应用程序和服务 -->
<n-tab-pane name="application" tab="应用程序">
<div class="tab-content">
<div class="form-section">
<div class="section-title">
<n-icon :component="AppsOutline" />
应用程序路径
</div>
<div class="form-grid">
<n-form-item path="applicationName" label="应用程序">
<n-input
v-model:value="formData.applicationName"
placeholder="例: C:\Program Files\Example\app.exe (留空表示所有程序)"
>
<template #suffix>
<n-button text @click="browseApplication">
<n-icon :component="FolderOpenOutline" />
</n-button>
</template>
</n-input>
</n-form-item>
<n-form-item path="serviceName" label="系统服务">
<n-select
v-model:value="formData.serviceName"
:options="serviceOptions"
placeholder="选择系统服务 (可选)"
filterable
clearable
/>
</n-form-item>
</div>
</div>
</div>
</n-tab-pane>
<!-- 协议和端口 -->
<n-tab-pane name="protocol" tab="协议端口">
<div class="tab-content">
<div class="form-section">
<div class="section-title">
<n-icon :component="LayersOutline" />
协议设置
</div>
<div class="form-grid">
<n-form-item path="protocol" label="协议类型" required>
<n-select
v-model:value="formData.protocol"
:options="protocolOptions"
placeholder="选择协议类型"
/>
</n-form-item>
<div class="form-row">
<n-form-item path="localPorts" label="本地端口">
<n-input
v-model:value="formData.localPorts"
placeholder="例: 80, 443, 8080-8090 (留空表示所有端口)"
/>
</n-form-item>
<n-form-item path="remotePorts" label="远程端口">
<n-input
v-model:value="formData.remotePorts"
placeholder="例: 80, 443, 8080-8090 (留空表示所有端口)"
/>
</n-form-item>
</div>
<n-form-item
v-if="formData.protocol === 1 || formData.protocol === 58"
path="icmpTypesAndCodes"
label="ICMP 类型和代码"
>
<n-input
v-model:value="formData.icmpTypesAndCodes"
placeholder="例: 8:0 (留空表示所有ICMP类型)"
/>
</n-form-item>
</div>
</div>
</div>
</n-tab-pane>
<!-- 地址设置 -->
<n-tab-pane name="address" tab="地址设置">
<div class="tab-content">
<div class="form-section">
<div class="section-title">
<n-icon :component="GlobeOutline" />
IP 地址范围
</div>
<div class="form-grid">
<n-form-item path="localAddresses" label="本地地址">
<n-input
v-model:value="formData.localAddresses"
placeholder="例: 192.168.1.0/24, 10.0.0.1 (* 表示任何地址)"
/>
<template #feedback> 支持单个IP、CIDR格式或逗号分隔的多个地址</template>
</n-form-item>
<n-form-item path="remoteAddresses" label="远程地址">
<n-input
v-model:value="formData.remoteAddresses"
placeholder="例: 192.168.1.0/24, 10.0.0.1 (* 表示任何地址)"
/>
<template #feedback> 支持单个IP、CIDR格式或逗号分隔的多个地址</template>
</n-form-item>
</div>
</div>
</div>
</n-tab-pane>
<!-- 配置文件和接口 -->
<n-tab-pane name="profile" tab="配置文件">
<div class="tab-content">
<div class="form-section">
<div class="section-title">
<n-icon :component="ShieldCheckmarkOutline" />
网络配置文件
</div>
<div class="form-grid">
<n-form-item path="profiles" label="应用于配置文件" required>
<n-checkbox-group v-model:value="formData.profiles">
<n-space vertical>
<n-checkbox :value="1">
<div class="profile-option">
<div class="profile-name">域网络 (Domain)</div>
<div class="profile-desc">计算机连接到域时</div>
</div>
</n-checkbox>
<n-checkbox :value="2">
<div class="profile-option">
<div class="profile-name">专用网络 (Private)</div>
<div class="profile-desc">家庭或工作网络</div>
</div>
</n-checkbox>
<n-checkbox :value="4">
<div class="profile-option">
<div class="profile-name">公用网络 (Public)</div>
<div class="profile-desc">公共场所的网络</div>
</div>
</n-checkbox>
</n-space>
</n-checkbox-group>
</n-form-item>
<n-form-item path="interfaceTypes" label="接口类型">
<n-input
v-model:value="formData.interfaceTypes"
placeholder="例: Wireless, Lan (留空或'All'表示所有接口)"
/>
</n-form-item>
<n-form-item path="interfaces" label="特定接口">
<n-dynamic-tags
v-model:value="formData.interfaces"
placeholder="添加网络接口名称"
/>
<template #feedback> 可以指定特定的网络接口,留空表示所有接口</template>
</n-form-item>
</div>
</div>
</div>
</n-tab-pane>
<!-- 高级选项 -->
<n-tab-pane name="advanced" tab="高级选项">
<div class="tab-content">
<div class="form-section">
<div class="section-title">
<n-icon :component="CogOutline" />
边缘遍历设置
</div>
<div class="form-grid">
<n-form-item path="edgeTraversal" label="边缘遍历">
<n-checkbox v-model:checked="formData.edgeTraversal">
允许边缘遍历 (适用于需要穿越NAT的应用)
</n-checkbox>
</n-form-item>
<n-form-item path="edgeTraversalAllowed" label="边缘遍历策略">
<n-checkbox v-model:checked="formData.edgeTraversalAllowed">
允许延迟边缘遍历
</n-checkbox>
</n-form-item>
</div>
</div>
<div class="form-section">
<div class="section-title">
<n-icon :component="LockClosedOutline" />
授权设置
</div>
<div class="form-grid">
<n-form-item path="remoteMachineAuthorizationList" label="远程计算机授权">
<n-input
v-model:value="formData.remoteMachineAuthorizationList"
placeholder="输入安全描述符 (高级用户)"
type="textarea"
:rows="2"
/>
</n-form-item>
<n-form-item path="remoteUserAuthorizationList" label="远程用户授权">
<n-input
v-model:value="formData.remoteUserAuthorizationList"
placeholder="输入安全描述符 (高级用户)"
type="textarea"
:rows="2"
/>
</n-form-item>
<n-form-item path="embeddedContext" label="嵌入式上下文">
<n-input
v-model:value="formData.embeddedContext"
placeholder="可选的嵌入式上下文信息"
/>
</n-form-item>
</div>
</div>
</div>
</n-tab-pane>
</n-tabs>
</n-form>
</div>
<template #footer>
<div class="modal-footer">
<n-space>
<n-button @click="handleClose">取消</n-button>
<n-button @click="handleValidate">验证规则</n-button>
<n-button type="primary" @click="handleSave" :loading="saving">
<template #icon>
<n-icon :component="CheckmarkOutline" />
</template>
{{ isEdit ? '更新规则' : '创建规则' }}
</n-button>
</n-space>
</div>
</template>
</n-modal>
</template>
<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue'
import {
NModal,
NTabs,
NTabPane,
NForm,
NFormItem,
NInput,
NSelect,
NRadioGroup,
NRadio,
NCheckbox,
NCheckboxGroup,
NDynamicTags,
NButton,
NIcon,
NSpace,
FormInst,
FormRules,
useMessage,
} from 'naive-ui'
import {
CheckmarkOutline,
InformationCircleOutline,
SettingsOutline,
AppsOutline,
LayersOutline,
GlobeOutline,
ShieldCheckmarkOutline,
CogOutline,
LockClosedOutline,
FolderOpenOutline,
} from '@vicons/ionicons5'
// 枚举定义 - 对应后端模型
enum RuleDirection {
Inbound = 1,
Outbound = 2,
}
enum RuleAction {
Block = 0,
Allow = 1,
}
enum ProtocolType {
Any = 256,
ICMPV4 = 1,
IGMP = 2,
TCP = 6,
UDP = 17,
IPv6 = 41,
IPv6Route = 43,
IPv6Frag = 44,
GRE = 47,
ICMPv6 = 58,
IPv6NoNxt = 59,
IPv6Opts = 60,
VRRP = 112,
PGM = 113,
L2TP = 115,
}
enum FirewallProfile {
Domain = 1,
Private = 2,
Public = 4,
}
// 防火墙规则接口 - 对应后端 FirewallRule
interface FirewallRule {
name: string
description: string
applicationName: string
serviceName: string
protocol: ProtocolType
localPorts: string
remotePorts: string
localAddresses: string
remoteAddresses: string
icmpTypesAndCodes: string
direction: RuleDirection
enabled: boolean
profiles: FirewallProfile[]
edgeTraversal: boolean
action: RuleAction
grouping: string
interfaceTypes: string
interfaces: string[]
// 高级属性
edgeTraversalAllowed: boolean
looseSourceMapping: boolean
localOnlyMapping: boolean
remoteMachineAuthorizationList: string
remoteUserAuthorizationList: string
embeddedContext: string
flags: number
secureFlags: boolean
}
// 创建规则请求接口 - 对应后端 CreateRuleRequest
interface CreateRuleRequest extends Omit<FirewallRule, 'profiles'> {
profiles: number // 使用位标志表示多个配置文件
}
const props = defineProps<{
modelValue: boolean
editRule?: FirewallRule | null
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
save: [rule: CreateRuleRequest]
}>()
// 响应式数据
const message = useMessage()
const formRef = ref<FormInst>()
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
})
const isEdit = computed(() => !!props.editRule)
const activeTab = ref('basic')
const saving = ref(false)
// 表单数据
const defaultFormData: FirewallRule = {
name: '',
description: '',
applicationName: '',
serviceName: '',
protocol: ProtocolType.Any,
localPorts: '',
remotePorts: '',
localAddresses: '*',
remoteAddresses: '*',
icmpTypesAndCodes: '',
direction: RuleDirection.Inbound,
enabled: true,
profiles: [FirewallProfile.Domain, FirewallProfile.Private],
edgeTraversal: false,
action: RuleAction.Allow,
grouping: '',
interfaceTypes: 'All',
interfaces: [],
// 高级属性
edgeTraversalAllowed: false,
looseSourceMapping: false,
localOnlyMapping: false,
remoteMachineAuthorizationList: '',
remoteUserAuthorizationList: '',
embeddedContext: '',
flags: 0,
secureFlags: false,
}
const formData = ref<FirewallRule>({ ...defaultFormData })
// 表单验证规则
const formRules: FormRules = {
name: [
{ required: true, message: '请输入规则名称', trigger: 'blur' },
{ min: 1, max: 255, message: '规则名称长度应在1-255字符之间', trigger: 'blur' },
],
direction: [{ required: true, message: '请选择规则方向', trigger: 'change', type: 'number' }],
action: [{ required: true, message: '请选择规则动作', trigger: 'change', type: 'number' }],
protocol: [{ required: true, message: '请选择协议类型', trigger: 'change', type: 'number' }],
profiles: [
{
required: true,
message: '请至少选择一个配置文件',
trigger: 'change',
validator: (rule, value: FirewallProfile[]) => {
return value && value.length > 0
},
},
],
}
// 选项数据
const protocolOptions = [
{ label: '任意协议', value: ProtocolType.Any },
{ label: 'TCP', value: ProtocolType.TCP },
{ label: 'UDP', value: ProtocolType.UDP },
{ label: 'ICMPv4', value: ProtocolType.ICMPV4 },
{ label: 'ICMPv6', value: ProtocolType.ICMPv6 },
{ label: 'IGMP', value: ProtocolType.IGMP },
{ label: 'IPv6', value: ProtocolType.IPv6 },
{ label: 'GRE', value: ProtocolType.GRE },
{ label: 'VRRP', value: ProtocolType.VRRP },
{ label: 'PGM', value: ProtocolType.PGM },
{ label: 'L2TP', value: ProtocolType.L2TP },
]
const serviceOptions = [
{ label: 'Windows Update (wuauserv)', value: 'wuauserv' },
{ label: '远程桌面服务 (TermService)', value: 'TermService' },
{ label: 'Windows Defender (WinDefend)', value: 'WinDefend' },
{ label: '打印后台处理程序 (Spooler)', value: 'Spooler' },
{ label: 'Windows 防火墙 (MpsSvc)', value: 'MpsSvc' },
{ label: 'DNS 客户端 (Dnscache)', value: 'Dnscache' },
{ label: 'DHCP 客户端 (Dhcp)', value: 'Dhcp' },
{ label: 'Workstation (LanmanWorkstation)', value: 'LanmanWorkstation' },
{ label: 'Server (LanmanServer)', value: 'LanmanServer' },
]
// 监听编辑规则变化
watch(
() => props.editRule,
(newRule) => {
if (newRule) {
formData.value = { ...newRule }
} else {
formData.value = { ...defaultFormData }
}
},
{ immediate: true },
)
// 方法
const handleClose = () => {
emit('update:modelValue', false)
setTimeout(() => {
formData.value = { ...defaultFormData }
activeTab.value = 'basic'
formRef.value?.restoreValidation()
}, 300)
}
const handleValidate = async () => {
try {
await formRef.value?.validate()
message.success('规则验证通过')
} catch (errors) {
message.error('请检查表单中的错误')
console.log(errors)
}
}
const handleSave = async () => {
try {
await formRef.value?.validate()
saving.value = true
// 转换数据格式以匹配后端要求
const saveData: CreateRuleRequest = {
...formData.value,
// 将配置文件数组转换为位标志
profiles: formData.value.profiles.reduce((acc, profile) => acc | profile, 0),
}
// 模拟保存延迟
await new Promise((resolve) => setTimeout(resolve, 1000))
emit('save', saveData)
message.success(isEdit.value ? '规则更新成功' : '规则创建成功')
handleClose()
} catch (errors) {
message.error('请检查表单中的错误')
console.log(errors)
} finally {
saving.value = false
}
}
const browseApplication = () => {
// 这里可以集成文件选择器或调用 Electron API
message.info('请使用文件浏览器选择应用程序')
}
</script>
<style scoped>
/* 基础模态框样式 */
:deep(.firewall-rule-modal .n-card) {
background: var(--bg-glass);
backdrop-filter: var(--backdrop-blur);
border: 1px solid var(--border-primary);
box-shadow: var(--shadow-xl);
display: flex;
flex-direction: column;
}
:deep(.firewall-rule-modal .n-card__header) {
background: var(--bg-card);
border-bottom: 1px solid var(--border-secondary);
padding: 20px 24px;
flex-shrink: 0;
}
:deep(.firewall-rule-modal .n-card__content) {
padding: 0;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
:deep(.firewall-rule-modal .n-card__content),
.rule-form-container,
.firewall-form,
.rule-tabs,
:deep(.rule-tabs .n-tabs-pane-wrapper),
:deep(.rule-tabs .n-tabs-pane) {
overflow: hidden;
}
:deep(.firewall-rule-modal .n-card__footer) {
background: var(--bg-card);
border-top: 1px solid var(--border-secondary);
padding: 16px 24px;
flex-shrink: 0;
}
/* 表单容器 */
.rule-form-container {
height: 100%;
display: flex;
flex-direction: column;
min-height: 0;
}
.firewall-form {
height: 100%;
display: flex;
flex-direction: column;
min-height: 0;
}
/* 标签页样式 */
.rule-tabs {
height: 100%;
display: flex;
flex-direction: column;
min-height: 0;
}
:deep(.rule-tabs .n-tabs-nav) {
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-secondary);
padding: 0 24px;
flex-shrink: 0;
height: 60px;
display: flex;
align-items: center;
}
:deep(.rule-tabs .n-tabs-pane-wrapper) {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
:deep(.rule-tabs .n-tabs-pane) {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
/* 标签页内容 */
.tab-content {
flex: 1;
overflow-y: auto;
padding: 24px;
scroll-behavior: smooth;
min-height: 0;
/* 新增:计算可用高度 */
max-height: calc(90vh - 220px);
}
.tab-content::-webkit-scrollbar {
width: 6px;
}
.tab-content::-webkit-scrollbar-track {
background: transparent;
}
.tab-content::-webkit-scrollbar-thumb {
background: var(--border-secondary);
border-radius: 3px;
opacity: 0.6;
}
.tab-content::-webkit-scrollbar-thumb:hover {
background: var(--border-hover);
opacity: 1;
}
/* 表单区块样式 */
.form-section {
margin-bottom: 28px;
background: var(--bg-card);
border: 1px solid var(--border-secondary);
border-radius: 12px;
padding: 20px;
transition: all 0.3s ease;
}
.form-section:hover {
border-color: var(--border-hover);
box-shadow: var(--shadow-sm);
}
.form-section:last-child {
margin-bottom: 0;
}
/* 区块标题 */
.section-title {
font-size: 14px;
font-weight: 600;
color: var(--text-quaternary);
margin: 0 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-tertiary);
display: flex;
align-items: center;
gap: 8px;
}
.section-title .n-icon {
color: var(--accent-primary);
background: rgba(59, 130, 246, 0.1);
padding: 4px;
border-radius: 4px;
font-size: 14px;
}
/* 表单网格 */
.form-grid {
display: flex;
flex-direction: column;
gap: 20px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
/* 配置文件选项样式 */
.profile-option {
margin-left: 8px;
}
.profile-name {
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 2px;
}
.profile-desc {
font-size: 11px;
color: var(--text-muted);
line-height: 1.4;
}
/* 模态框底部 */
.modal-footer {
display: flex;
justify-content: flex-end;
}
/* 标签页切换动画 */
:deep(.n-tabs-tab) {
transition: all 0.3s ease;
border-radius: 6px;
padding: 12px 16px;
font-weight: 500;
color: var(--text-muted);
}
:deep(.n-tabs-tab:hover) {
color: var(--text-secondary);
background: var(--bg-hover);
}
:deep(.n-tabs-tab--active) {
color: var(--accent-primary) !important;
background: var(--bg-card);
font-weight: 600;
}
/* Naive UI 组件样式覆盖 */
:deep(.n-form-item-label) {
font-weight: 500;
color: var(--text-muted);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
:deep(.n-form-item-feedback) {
font-size: 11px;
color: var(--text-muted);
font-style: italic;
margin-top: 4px;
}
:deep(.n-input) {
--n-border: 1px solid var(--border-tertiary);
--n-border-hover: 1px solid var(--border-hover);
--n-border-focus: 1px solid var(--accent-primary);
--n-color: var(--bg-card);
--n-text-color: var(--text-secondary);
--n-border-radius: 6px;
--n-font-size: 13px;
}
:deep(.n-select) {
--n-border: 1px solid var(--border-tertiary);
--n-border-hover: 1px solid var(--border-hover);
--n-border-focus: 1px solid var(--accent-primary);
--n-color: var(--bg-card);
--n-text-color: var(--text-secondary);
--n-border-radius: 6px;
--n-font-size: 13px;
}
:deep(.n-checkbox) {
--n-border: 1px solid var(--border-tertiary);
--n-border-checked: 1px solid var(--accent-primary);
--n-color-checked: var(--accent-primary);
--n-font-size: 13px;
}
:deep(.n-radio) {
--n-dot-color-active: var(--accent-primary);
--n-color-active: var(--accent-primary);
--n-font-size: 13px;
}
:deep(.n-checkbox-group) {
display: flex;
flex-direction: column;
gap: 12px;
}
:deep(.n-radio-group) {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
/* 标签页内容切换动画 */
.tab-content {
animation: fadeInSlide 0.3s ease-out;
}
@keyframes fadeInSlide {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式设计 */
@media (max-width: 768px) {
:deep(.firewall-rule-modal .n-card) {
width: 90vw !important;
height: 90vh !important;
}
.tab-content {
padding: 16px;
}
.form-row {
grid-template-columns: 1fr;
gap: 16px;
}
.form-section {
padding: 16px;
margin-bottom: 20px;
}
:deep(.n-radio-group) {
flex-direction: column;
gap: 12px;
}
:deep(.rule-tabs .n-tabs-nav) {
padding: 0 16px;
}
}
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.tab-content::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
}
.tab-content::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.2);
}
}
</style>
+460 -199
View File
@@ -8,19 +8,40 @@
<p class="view-subtitle">配置和管理网络防护规则</p>
</div>
<div class="toolbar-right">
<n-button type="primary" size="medium">
<!-- 防火墙总开关 -->
<div class="firewall-status">
<span class="status-label">防火墙状态</span>
<n-switch v-model:value="firewallEnabled" size="medium" />
</div>
<n-button type="primary" size="medium" @click="openRuleForm()">
<template #icon>
<n-icon :component="AddOutline" />
</template>
添加规则
新建规则
</n-button>
<n-button size="medium" ghost>导入规则</n-button>
<n-button size="medium" ghost>导出规则</n-button>
<n-dropdown :options="batchOptions" placement="bottom-end">
<n-button size="medium" ghost>
批量操作
<template #icon>
<n-icon :component="ChevronDownOutline" />
</template>
</n-button>
</n-dropdown>
</div>
</div>
<!-- 规则统计卡片 -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon-wrapper primary">
<n-icon :component="ListOutline" size="24" />
</div>
<div class="stat-content">
<div class="stat-number">{{ stats.total }}</div>
<div class="stat-label">总规则数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon-wrapper success">
<n-icon :component="CheckmarkCircleOutline" size="24" />
@@ -47,17 +68,7 @@
</div>
<div class="stat-content">
<div class="stat-number">{{ stats.blocked }}</div>
<div class="stat-label">已拦截</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon-wrapper info">
<n-icon :component="ShieldCheckmarkOutline" size="24" />
</div>
<div class="stat-content">
<div class="stat-number">{{ stats.total }}</div>
<div class="stat-label">总规则数</div>
<div class="stat-label">阻止连接</div>
</div>
</div>
</div>
@@ -69,177 +80,399 @@
<div class="panel-controls">
<n-input
v-model:value="searchQuery"
placeholder="搜索规则..."
placeholder="搜索规则名称..."
size="small"
clearable
style="width: 280px"
>
<template #prefix>
<n-icon :component="SearchOutline" />
</template>
</n-input>
<n-select
v-model:value="filterType"
size="small"
style="width: 120px"
:options="filterOptions"
/>
<n-dropdown :options="filterDropdownOptions" placement="bottom-end">
<n-button size="small" ghost>
<template #icon>
<n-icon :component="FilterOutline" />
</template>
筛选
</n-button>
</n-dropdown>
</div>
</div>
<div class="rules-list">
<div v-for="rule in mockRules" :key="rule.id" class="rule-item">
<div class="rule-status">
<n-switch v-model:value="rule.enabled" size="small" />
</div>
<div class="rule-main">
<div class="rule-header">
<h4 class="rule-name">{{ rule.name }}</h4>
<n-tag :type="rule.action === 'allow' ? 'success' : 'error'" size="small">
{{ rule.action === 'allow' ? '允许' : '拒绝' }}
</n-tag>
</div>
<div class="rule-details">
<span class="rule-detail">
<n-icon :component="LocationOutline" size="14" />
{{ rule.source }}
</span>
<span class="rule-arrow">→</span>
<span class="rule-detail">
<n-icon :component="LocationOutline" size="14" />
{{ rule.destination }}
</span>
<span class="rule-detail">
<n-icon :component="LayersOutline" size="14" />
{{ rule.protocol }}
</span>
<span class="rule-detail">
<n-icon :component="ServerOutline" size="14" />
端口: {{ rule.port }}
</span>
</div>
</div>
<div class="rule-actions">
<n-button size="tiny" circle quaternary>
<template #icon>
<n-icon :component="CreateOutline" />
</template>
</n-button>
<n-button size="tiny" circle quaternary>
<template #icon>
<n-icon :component="CopyOutline" />
</template>
</n-button>
<n-button size="tiny" circle quaternary type="error">
<template #icon>
<n-icon :component="TrashOutline" />
</template>
</n-button>
</div>
</div>
<div class="table-container">
<n-data-table
:columns="columns"
:data="filteredRules"
:pagination="false"
:scroll-x="1200"
:virtual-scroll="true"
:max-height="600"
size="small"
striped
:row-key="(row) => row.id"
:checked-row-keys="checkedRowKeys"
@update:checked-row-keys="handleCheck"
/>
</div>
</div>
</div>
<!-- 防火墙规则表单 -->
<FirewallRuleForm
v-model="showRuleForm"
:edit-rule="currentEditRule"
@save="handleSaveRule"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { NButton, NIcon, NInput, NSelect, NSwitch, NTag } from 'naive-ui'
import { ref, computed, h } from 'vue'
import {
NButton,
NIcon,
NInput,
NSwitch,
NTag,
NDataTable,
NDropdown,
useMessage,
type DataTableColumns
} from 'naive-ui'
import FirewallRuleForm from '@/components/FirewallRuleForm.vue'
import {
AddOutline,
SearchOutline,
CheckmarkCircleOutline,
PauseCircleOutline,
CloseCircleOutline,
ShieldCheckmarkOutline,
LocationOutline,
LayersOutline,
ServerOutline,
ListOutline,
ChevronDownOutline,
FilterOutline,
CreateOutline,
CopyOutline,
TrashOutline,
ArrowUpOutline,
ArrowDownOutline,
} from '@vicons/ionicons5'
const message = useMessage()
// 界面状态
const showRuleForm = ref(false)
const currentEditRule = ref(null)
const firewallEnabled = ref(true)
const checkedRowKeys = ref<string[]>([])
// 搜索和过滤
const searchQuery = ref('')
const filterType = ref('all')
const filterOptions = [
{ label: '全部规则', value: 'all' },
{ label: '已启用', value: 'active' },
{ label: '已禁用', value: 'inactive' },
{ label: '入站规则', value: 'inbound' },
{ label: '出站规则', value: 'outbound' },
// 批量操作选项
const batchOptions = [
{
label: '启用选中',
key: 'enable',
icon: () => h(NIcon, { component: CheckmarkCircleOutline })
},
{
label: '禁用选中',
key: 'disable',
icon: () => h(NIcon, { component: PauseCircleOutline })
},
{
label: '删除选中',
key: 'delete',
icon: () => h(NIcon, { component: TrashOutline })
}
]
// 筛选下拉选项
const filterDropdownOptions = [
{
label: '方向',
key: 'direction-header',
type: 'group',
children: [
{ label: '入站规则', key: 'inbound' },
{ label: '出站规则', key: 'outbound' }
]
},
{
label: '操作',
key: 'action-header',
type: 'group',
children: [
{ label: '允许', key: 'allow' },
{ label: '阻止', key: 'block' }
]
},
{
label: '状态',
key: 'status-header',
type: 'group',
children: [
{ label: '已启用', key: 'enabled' },
{ label: '已禁用', key: 'disabled' }
]
}
]
// 统计数据
const stats = ref({
active: 128,
inactive: 24,
blocked: 3456,
total: 152,
total: 127,
active: 89,
inactive: 38,
blocked: 1247,
})
// 模拟规则数据
const mockRules = ref([
{
id: 1,
name: '阻止恶意IP访问',
id: '1',
name: 'Windows 远程桌面',
description: '允许远程桌面连接',
enabled: true,
action: 'deny',
source: '10.0.0.0/8',
destination: '任意',
protocol: 'TCP/UDP',
port: '全部',
direction: 'inbound',
action: 'allow',
program: '系统服务',
protocol: 'TCP',
port: '3389',
profiles: ['域', '专用'],
priority: 100,
},
{
id: 2,
name: '允许HTTPS流量',
id: '2',
name: 'Microsoft Edge',
description: '允许浏览器访问网络',
enabled: true,
direction: 'outbound',
action: 'allow',
source: '任意',
destination: '任意',
protocol: 'TCP',
port: '443',
program: 'C:\\Program Files\\Microsoft\\Edge\\msedge.exe',
protocol: '任意',
port: '任意',
profiles: ['域', '专用', '公用'],
priority: 200,
},
{
id: 3,
name: '允许DNS查询',
id: '3',
name: '阻止 Telnet',
description: '安全策略:阻止 Telnet 连接',
enabled: true,
action: 'allow',
source: '192.168.1.0/24',
destination: '8.8.8.8',
protocol: 'UDP',
port: '53',
direction: 'inbound',
action: 'block',
program: '任意',
protocol: 'TCP',
port: '23',
profiles: ['域', '专用', '公用'],
priority: 300,
},
{
id: 4,
name: '阻止Telnet访问',
id: '4',
name: '文件和打印机共享',
description: '允许局域网内文件共享',
enabled: false,
action: 'deny',
source: '任意',
destination: '任意',
direction: 'inbound',
action: 'allow',
program: '系统服务',
protocol: 'TCP',
port: '23',
port: '445, 139',
profiles: ['专用'],
priority: 400,
},
{
id: 5,
name: '允许SSH管理',
id: '5',
name: 'HTTP 服务器',
description: '允许 Web 服务器接收请求',
enabled: true,
direction: 'inbound',
action: 'allow',
source: '192.168.1.100',
destination: '192.168.1.1',
program: '任意',
protocol: 'TCP',
port: '22',
port: '80, 443',
profiles: ['域', '专用', '公用'],
priority: 500,
},
])
// 表格列定义
const columns: DataTableColumns = [
{
type: 'selection',
width: 40
},
{
title: '规则名称',
key: 'name',
width: 200,
render(row: any) {
return h('div', { class: 'rule-name-cell' }, [
h('div', { class: 'rule-name' }, row.name),
h('div', { class: 'rule-description' }, row.description)
])
}
},
{
title: '状态',
key: 'enabled',
width: 80,
render(row: any) {
return h(NTag, {
type: row.enabled ? 'success' : 'default',
size: 'small'
}, {
default: () => row.enabled ? '启用' : '禁用'
})
}
},
{
title: '方向',
key: 'direction',
width: 80,
render(row: any) {
return h('div', { class: 'direction-cell' }, [
h(NIcon, {
component: row.direction === 'inbound' ? ArrowDownOutline : ArrowUpOutline,
size: 16,
style: { marginRight: '4px' }
}),
row.direction === 'inbound' ? '入站' : '出站'
])
}
},
{
title: '操作',
key: 'action',
width: 80,
render(row: any) {
return h(NTag, {
type: row.action === 'allow' ? 'success' : 'error',
size: 'small'
}, {
default: () => row.action === 'allow' ? '允许' : '阻止'
})
}
},
{
title: '程序/服务',
key: 'program',
width: 250,
ellipsis: {
tooltip: true
}
},
{
title: '协议',
key: 'protocol',
width: 80
},
{
title: '端口',
key: 'port',
width: 100
},
{
title: '配置文件',
key: 'profiles',
width: 150,
render(row: any) {
return h('div', { class: 'profiles-cell' },
row.profiles.map((profile: string) =>
h(NTag, {
key: profile,
type: 'info',
size: 'small',
style: { marginRight: '4px', marginBottom: '2px' }
}, {
default: () => profile
})
)
)
}
},
{
title: '操作',
key: 'actions',
width: 120,
render(row: any) {
return h('div', { class: 'action-buttons' }, [
h(NButton, {
size: 'small',
quaternary: true,
onClick: () => editRule(row)
}, {
default: () => '编辑'
}),
h(NButton, {
size: 'small',
quaternary: true,
type: 'error',
onClick: () => deleteRule(row.id)
}, {
default: () => '删除'
})
])
}
}
]
// 计算属性
const filteredRules = computed(() => {
let rules = mockRules.value
// 搜索过滤
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
rules = rules.filter(rule =>
rule.name.toLowerCase().includes(query) ||
rule.description.toLowerCase().includes(query)
)
}
return rules
})
// 方法
const openRuleForm = (rule: any = null) => {
currentEditRule.value = rule
showRuleForm.value = true
}
const editRule = (rule: any) => {
currentEditRule.value = { ...rule }
showRuleForm.value = true
}
const deleteRule = (id: string) => {
const index = mockRules.value.findIndex(rule => rule.id === id)
if (index !== -1) {
mockRules.value.splice(index, 1)
message.success('规则删除成功')
}
}
const handleCheck = (keys: string[]) => {
checkedRowKeys.value = keys
}
const handleSaveRule = (rule: any) => {
if (rule.id) {
// 编辑现有规则
const index = mockRules.value.findIndex(r => r.id === rule.id)
if (index !== -1) {
mockRules.value[index] = rule
message.success('规则更新成功')
}
} else {
// 添加新规则
rule.id = Date.now().toString()
mockRules.value.unshift(rule)
message.success('规则创建成功')
}
// 重置状态
currentEditRule.value = null
}
</script>
<style scoped>
@@ -261,6 +494,7 @@ const mockRules = ref([
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
gap: 24px;
}
.toolbar-left {
@@ -282,9 +516,26 @@ const mockRules = ref([
.toolbar-right {
display: flex;
align-items: center;
gap: 12px;
}
.firewall-status {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 16px;
background: var(--bg-card);
border: 1px solid var(--border-primary);
border-radius: 8px;
}
.status-label {
font-size: 14px;
color: var(--text-secondary);
white-space: nowrap;
}
/* 统计卡片 */
.stats-grid {
display: grid;
@@ -319,6 +570,11 @@ const mockRules = ref([
justify-content: center;
}
.stat-icon-wrapper.primary {
background: rgba(59, 130, 246, 0.1);
color: var(--accent-primary);
}
.stat-icon-wrapper.success {
background: rgba(34, 197, 94, 0.1);
color: var(--accent-success);
@@ -334,11 +590,6 @@ const mockRules = ref([
color: var(--accent-error);
}
.stat-icon-wrapper.info {
background: rgba(59, 130, 246, 0.1);
color: var(--accent-primary);
}
.stat-content {
flex: 1;
}
@@ -381,120 +632,132 @@ const mockRules = ref([
}
.panel-controls {
display: flex;
gap: 12px;
}
/* 规则列表 */
.rules-list {
padding: 12px;
}
.rule-item {
background: var(--bg-tertiary);
border: 1px solid var(--border-secondary);
border-radius: 8px;
padding: 16px;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 16px;
transition: var(--transition);
}
.rule-item:hover {
background: var(--bg-hover);
border-color: var(--border-hover);
}
.rule-item:last-child {
margin-bottom: 0;
}
.rule-status {
display: flex;
align-items: center;
}
.rule-main {
flex: 1;
}
.rule-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.rule-name {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
/* 表格容器 */
.table-container {
padding: 16px 24px 24px;
}
.rule-details {
/* 表格单元格样式 */
.rule-name-cell {
display: flex;
align-items: center;
gap: 12px;
font-size: 12px;
color: var(--text-muted);
}
.rule-detail {
display: flex;
align-items: center;
flex-direction: column;
gap: 4px;
}
.rule-arrow {
color: var(--text-quaternary);
.rule-name {
font-weight: 600;
color: var(--text-primary);
font-size: 14px;
}
.rule-actions {
.rule-description {
font-size: 12px;
color: var(--text-muted);
line-height: 1.3;
}
.direction-cell {
display: flex;
align-items: center;
font-size: 13px;
color: var(--text-secondary);
}
.profiles-cell {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.action-buttons {
display: flex;
gap: 8px;
}
/* 响应式 */
@media (max-width: 768px) {
@media (max-width: 1200px) {
.firewall-toolbar {
flex-direction: column;
align-items: flex-start;
align-items: stretch;
gap: 16px;
}
.toolbar-right {
width: 100%;
justify-content: space-between;
flex-wrap: wrap;
}
}
@media (max-width: 768px) {
.firewall-container {
padding: 16px;
}
.stats-grid {
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.stat-card {
padding: 16px;
}
.stat-number {
font-size: 24px;
}
.panel-header {
flex-direction: column;
align-items: flex-start;
align-items: stretch;
gap: 12px;
}
.panel-controls {
width: 100%;
justify-content: space-between;
}
.rule-item {
.toolbar-right {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.rule-details {
flex-wrap: wrap;
.firewall-status {
width: 100%;
justify-content: space-between;
}
}
/* Naive UI 样式覆盖 */
/* Naive UI 数据表格样式覆盖 */
:deep(.n-data-table) {
--n-td-color: var(--bg-card);
--n-td-color-striped: var(--bg-tertiary);
--n-th-color: var(--bg-secondary);
--n-border-color: var(--border-secondary);
--n-th-text-color: var(--text-secondary);
--n-td-text-color: var(--text-primary);
--n-font-size: 13px;
}
:deep(.n-data-table-th) {
font-weight: 600;
background-color: var(--bg-secondary);
}
:deep(.n-data-table-td) {
padding: 12px 16px;
}
:deep(.n-data-table-th) {
padding: 12px 16px;
}
:deep(.n-input) {
--n-border: 1px solid var(--border-tertiary);
--n-border-hover: 1px solid var(--border-hover);
@@ -503,12 +766,9 @@ const mockRules = ref([
--n-text-color: var(--text-primary);
}
:deep(.n-select) {
--n-border: 1px solid var(--border-tertiary);
--n-border-hover: 1px solid var(--border-hover);
--n-border-focus: 1px solid var(--accent-primary);
:deep(.n-dropdown-menu) {
--n-color: var(--bg-card);
--n-text-color: var(--text-primary);
--n-option-text-color: var(--text-primary);
}
:deep(.n-switch) {
@@ -517,5 +777,6 @@ const mockRules = ref([
:deep(.n-tag) {
font-weight: 500;
font-size: 12px;
}
</style>