A:增加防火墙UI设计

This commit is contained in:
jimmy committed 2025-07-17 20:48:06 +08:00
1 parent 4c68765570
commit d43064656f
9 files changed
+11623

No files matched your search

@@ -0,0 +1,922 @@
<!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
@@ -0,0 +1,1083 @@
<!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 {
--bg-primary: #0a0f1c;
--bg-secondary: #131a2a;
--bg-card: rgba(20, 27, 45, 0.8);
--text-primary: #e4e8f1;
--text-secondary: #8b92a8;
--accent: #3b82f6;
--accent-hover: #2563eb;
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--border: rgba(255, 255, 255, 0.1);
--glass: rgba(255, 255, 255, 0.05);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
overflow-x: hidden;
}
/* 背景动画 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 20% 50%, rgba(59, 130, 246, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(16, 185, 129, 0.1) 0%, transparent 50%);
z-index: -1;
}
/* 主容器 */
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* 头部 */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 20px 0;
border-bottom: 1px solid var(--border);
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, var(--accent) 0%, var(--success) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* 统计卡片 */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: var(--bg-card);
backdrop-filter: blur(10px);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, var(--accent) 0%, var(--success) 100%);
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.stat-value {
font-size: 2.5rem;
font-weight: 700;
margin: 10px 0;
}
.stat-label {
color: var(--text-secondary);
font-size: 0.9rem;
}
/* 过滤和操作栏 */
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.filter-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 500;
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn-primary:hover {
background: var(--accent-hover);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(59, 130, 246, 0.3);
}
.btn-secondary {
background: var(--glass);
color: var(--text-primary);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
}
.btn-danger {
background: var(--danger);
color: white;
}
.search-box {
position: relative;
width: 300px;
}
.search-box input {
width: 100%;
padding: 10px 40px 10px 15px;
background: var(--glass);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.9rem;
transition: all 0.3s ease;
}
.search-box input:focus {
outline: none;
border-color: var(--accent);
background: rgba(255, 255, 255, 0.1);
}
/* 规则表格 */
.rules-table {
background: var(--bg-card);
backdrop-filter: blur(10px);
border: 1px solid var(--border);
border-radius: 16px;
overflow: hidden;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background: var(--bg-secondary);
padding: 15px;
text-align: left;
font-weight: 600;
font-size: 0.9rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
td {
padding: 15px;
border-top: 1px solid var(--border);
}
tr {
transition: all 0.3s ease;
cursor: pointer;
}
tr:hover {
background: rgba(255, 255, 255, 0.03);
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
}
.status-enabled {
background: rgba(16, 185, 129, 0.2);
color: var(--success);
}
.status-disabled {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
}
.direction-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 6px;
font-size: 0.85rem;
background: var(--glass);
border: 1px solid var(--border);
}
/* 模态框 */
.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.active {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: var(--bg-secondary);
border-radius: 20px;
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
animation: slideUp 0.3s ease;
border: 1px solid var(--border);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.modal-header {
padding: 30px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 {
font-size: 1.8rem;
font-weight: 600;
}
.close-btn {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--glass);
border: 1px solid var(--border);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.1);
transform: rotate(90deg);
}
.modal-body {
padding: 30px;
}
.form-group {
margin-bottom: 25px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: var(--text-secondary);
font-size: 0.9rem;
}
.form-control {
width: 100%;
padding: 12px 15px;
background: var(--glass);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.95rem;
transition: all 0.3s ease;
}
.form-control:focus {
outline: none;
border-color: var(--accent);
background: rgba(255, 255, 255, 0.08);
}
select.form-control {
cursor: pointer;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 30px;
border-bottom: 1px solid var(--border);
}
.tab {
padding: 12px 24px;
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-weight: 500;
position: relative;
transition: all 0.3s ease;
}
.tab.active {
color: var(--text-primary);
}
.tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 2px;
background: var(--accent);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 8px;
}
.checkbox-item input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.modal-footer {
padding: 20px 30px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* 动画 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(50px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* 服务选择器 */
.service-selector {
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px;
background: var(--glass);
}
.service-item {
padding: 8px;
margin: 2px 0;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.service-item:hover {
background: rgba(255, 255, 255, 0.05);
}
.service-item input {
margin-right: 8px;
}
/* 响应式 */
@media (max-width: 768px) {
.header h1 {
font-size: 1.8rem;
}
.action-bar {
flex-direction: column;
align-items: stretch;
}
.search-box {
width: 100%;
}
.form-row {
grid-template-columns: 1fr;
}
.modal-content {
width: 95%;
max-height: 95vh;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 头部 -->
<div class="header">
<h1>防火墙管理中心</h1>
<div class="header-actions">
<button class="btn btn-secondary" onclick="exportRules()">
<span>📤</span> 导出规则
</button>
<button class="btn btn-secondary" onclick="importRules()">
<span>📥</span> 导入规则
</button>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">总规则数</div>
<div class="stat-value" id="total-rules">42</div>
<div class="stat-label">入站: 24 | 出站: 18</div>
</div>
<div class="stat-card">
<div class="stat-label">已启用规则</div>
<div class="stat-value" style="color: var(--success);">35</div>
<div class="stat-label">占比 83.3%</div>
</div>
<div class="stat-card">
<div class="stat-label">已禁用规则</div>
<div class="stat-value" style="color: var(--danger);">7</div>
<div class="stat-label">占比 16.7%</div>
</div>
<div class="stat-card">
<div class="stat-label">安全等级</div>
<div class="stat-value" style="color: var(--success);">高</div>
<div class="stat-label">系统防护良好</div>
</div>
</div>
<!-- 过滤和操作栏 -->
<div class="action-bar">
<div class="filter-group">
<button class="btn btn-secondary" 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-secondary" onclick="filterRules('enabled')">已启用</button>
<button class="btn btn-secondary" onclick="filterRules('disabled')">已禁用</button>
</div>
<div class="search-box">
<input type="text" placeholder="搜索规则名称、程序、端口..." onkeyup="searchRules(this.value)">
</div>
<button class="btn btn-primary" onclick="openAddModal()">
<span>➕</span> 新建规则
</button>
</div>
<!-- 规则表格 -->
<div class="rules-table">
<table>
<thead>
<tr>
<th>状态</th>
<th>名称</th>
<th>方向</th>
<th>程序</th>
<th>协议</th>
<th>端口</th>
<th>远程地址</th>
<th>操作</th>
</tr>
</thead>
<tbody id="rules-tbody">
<!-- 动态生成的规则行 -->
</tbody>
</table>
</div>
</div>
<!-- 添加/编辑规则模态框 -->
<div class="modal" id="rule-modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modal-title">新建防火墙规则</h2>
<button class="close-btn" onclick="closeModal()">✕</button>
</div>
<div class="modal-body">
<!-- 标签页 -->
<div class="tabs">
<button class="tab active" onclick="switchTab('general')">常规</button>
<button class="tab" onclick="switchTab('program')">程序和服务</button>
<button class="tab" onclick="switchTab('protocol')">协议和端口</button>
<button class="tab" onclick="switchTab('scope')">作用域</button>
<button class="tab" onclick="switchTab('advanced')">高级</button>
</div>
<!-- 常规标签页 -->
<div class="tab-content active" id="general-tab">
<div class="form-group">
<label>规则名称 *</label>
<input type="text" class="form-control" id="rule-name" placeholder="输入规则名称">
</div>
<div class="form-group">
<label>描述</label>
<textarea class="form-control" id="rule-description" rows="3" placeholder="输入规则描述"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label>方向 *</label>
<select class="form-control" id="rule-direction">
<option value="inbound">入站</option>
<option value="outbound">出站</option>
</select>
</div>
<div class="form-group">
<label>操作 *</label>
<select class="form-control" id="rule-action">
<option value="allow">允许连接</option>
<option value="block">阻止连接</option>
<option value="allow-secure">允许安全连接</option>
</select>
</div>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="rule-enabled" checked>
启用此规则
</label>
</div>
</div>
<!-- 程序和服务标签页 -->
<div class="tab-content" id="program-tab">
<div class="form-group">
<label>程序路径</label>
<input type="text" class="form-control" id="program-path" placeholder="例如: C:\Program Files\App\app.exe">
</div>
<div class="form-group">
<label>应用于以下服务</label>
<div class="service-selector">
<div class="service-item">
<label><input type="checkbox" value="BITS"> 后台智能传输服务 (BITS)</label>
</div>
<div class="service-item">
<label><input type="checkbox" value="Dnscache"> DNS 客户端 (Dnscache)</label>
</div>
<div class="service-item">
<label><input type="checkbox" value="Dhcp"> DHCP 客户端 (Dhcp)</label>
</div>
<div class="service-item">
<label><input type="checkbox" value="EventLog"> Windows 事件日志 (EventLog)</label>
</div>
<div class="service-item">
<label><input type="checkbox" value="MpsSvc"> Windows 防火墙 (MpsSvc)</label>
</div>
<div class="service-item">
<label><input type="checkbox" value="RpcSs"> 远程过程调用 (RPC)</label>
</div>
<div class="service-item">
<label><input type="checkbox" value="Spooler"> 打印后台处理程序</label>
</div>
<div class="service-item">
<label><input type="checkbox" value="W32Time"> Windows 时间</label>
</div>
<div class="service-item">
<label><input type="checkbox" value="WinRM"> Windows 远程管理</label>
</div>
</div>
</div>
</div>
<!-- 协议和端口标签页 -->
<div class="tab-content" id="protocol-tab">
<div class="form-group">
<label>协议类型</label>
<select class="form-control" id="protocol-type" onchange="togglePortFields()">
<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="custom-protocol-group" style="display: none;">
<label>协议号</label>
<input type="number" class="form-control" id="protocol-number" min="0" max="255" placeholder="0-255">
</div>
<div class="form-row" id="port-fields">
<div class="form-group">
<label>本地端口</label>
<input type="text" class="form-control" id="local-ports" placeholder="例如: 80, 443, 1000-2000">
</div>
<div class="form-group">
<label>远程端口</label>
<input type="text" class="form-control" id="remote-ports" placeholder="例如: 80, 443, 1000-2000">
</div>
</div>
</div>
<!-- 作用域标签页 -->
<div class="tab-content" id="scope-tab">
<div class="form-group">
<label>本地 IP 地址</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="radio" name="local-ip" value="any" checked>
<label>任何 IP 地址</label>
</div>
<div class="checkbox-item">
<input type="radio" name="local-ip" value="custom">
<label>指定 IP 地址</label>
</div>
</div>
<input type="text" class="form-control" id="local-ip-list" placeholder="例如: 192.168.1.0/24, 10.0.0.1" style="margin-top: 10px; display: none;">
</div>
<div class="form-group">
<label>远程 IP 地址</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="radio" name="remote-ip" value="any" checked>
<label>任何 IP 地址</label>
</div>
<div class="checkbox-item">
<input type="radio" name="remote-ip" value="custom">
<label>指定 IP 地址</label>
</div>
</div>
<input type="text" class="form-control" id="remote-ip-list" placeholder="例如: 192.168.1.0/24, 10.0.0.1" style="margin-top: 10px; display: none;">
</div>
</div>
<!-- 高级标签页 -->
<div class="tab-content" id="advanced-tab">
<div class="form-group">
<label>配置文件</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="profile-domain" checked>
<label>域</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="profile-private" checked>
<label>专用</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="profile-public" checked>
<label>公用</label>
</div>
</div>
</div>
<div class="form-group">
<label>用户和组</label>
<input type="text" class="form-control" id="users-groups" placeholder="例如: DOMAIN\User, Everyone">
</div>
<div class="form-group">
<label>边缘遍历</label>
<select class="form-control" id="edge-traversal">
<option value="block">阻止边缘遍历</option>
<option value="allow">允许边缘遍历</option>
<option value="defer-to-user">延迟到用户</option>
<option value="defer-to-app">延迟到应用程序</option>
</select>
</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>
// 模拟规则数据
let rules = [
{
id: 1,
name: "允许 Chrome 浏览器",
enabled: true,
direction: "outbound",
program: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
protocol: "TCP",
localPort: "任意",
remotePort: "80, 443",
remoteAddress: "任意",
action: "allow",
description: "允许 Chrome 浏览器访问网络"
},
{
id: 2,
name: "远程桌面 - 用户模式 (TCP-In)",
enabled: true,
direction: "inbound",
program: "系统",
protocol: "TCP",
localPort: "3389",
remotePort: "任意",
remoteAddress: "任意",
action: "allow",
description: "允许远程桌面连接"
},
{
id: 3,
name: "阻止 SMB 445 端口",
enabled: true,
direction: "inbound",
program: "任意",
protocol: "TCP",
localPort: "445",
remotePort: "任意",
remoteAddress: "任意",
action: "block",
description: "阻止 SMB 文件共享端口"
},
{
id: 4,
name: "Windows 更新服务",
enabled: true,
direction: "outbound",
program: "C:\\Windows\\System32\\svchost.exe",
protocol: "TCP/UDP",
localPort: "任意",
remotePort: "80, 443",
remoteAddress: "任意",
action: "allow",
description: "允许 Windows 更新服务"
},
{
id: 5,
name: "SSH 服务器",
enabled: false,
direction: "inbound",
program: "C:\\Program Files\\OpenSSH\\sshd.exe",
protocol: "TCP",
localPort: "22",
remotePort: "任意",
remoteAddress: "192.168.1.0/24",
action: "allow",
description: "SSH 服务器访问规则"
}
];
let currentEditId = null;
// 渲染规则表格
function renderRules() {
const tbody = document.getElementById('rules-tbody');
tbody.innerHTML = '';
rules.forEach(rule => {
const tr = document.createElement('tr');
tr.onclick = () => editRule(rule.id);
tr.innerHTML = `
<td>
<span class="status-badge ${rule.enabled ? 'status-enabled' : 'status-disabled'}">
<span style="font-size: 0.8rem;">${rule.enabled ? '●' : '○'}</span>
${rule.enabled ? '启用' : '禁用'}
</span>
</td>
<td>${rule.name}</td>
<td>
<span class="direction-badge">
${rule.direction === 'inbound' ? '⬅️ 入站' : '➡️ 出站'}
</span>
</td>
<td>${rule.program.length > 30 ? '...' + rule.program.substr(-30) : rule.program}</td>
<td>${rule.protocol}</td>
<td>${rule.localPort}</td>
<td>${rule.remoteAddress}</td>
<td>
<span style="color: ${rule.action === 'allow' ? 'var(--success)' : 'var(--danger)'}">
${rule.action === 'allow' ? '✓ 允许' : '✗ 阻止'}
</span>
</td>
`;
tbody.appendChild(tr);
});
}
// 打开添加模态框
function openAddModal() {
currentEditId = null;
document.getElementById('modal-title').textContent = '新建防火墙规则';
document.getElementById('rule-modal').classList.add('active');
resetForm();
}
// 编辑规则
function editRule(id) {
currentEditId = id;
const rule = rules.find(r => r.id === id);
if (!rule) return;
document.getElementById('modal-title').textContent = '编辑防火墙规则';
document.getElementById('rule-modal').classList.add('active');
// 填充表单
document.getElementById('rule-name').value = rule.name;
document.getElementById('rule-description').value = rule.description || '';
document.getElementById('rule-direction').value = rule.direction;
document.getElementById('rule-action').value = rule.action;
document.getElementById('rule-enabled').checked = rule.enabled;
document.getElementById('program-path').value = rule.program === '任意' ? '' : rule.program;
// 这里可以继续填充其他字段...
}
// 关闭模态框
function closeModal() {
document.getElementById('rule-modal').classList.remove('active');
}
// 切换标签页
function switchTab(tabName) {
// 更新标签按钮状态
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
event.target.classList.add('active');
// 显示对应内容
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(`${tabName}-tab`).classList.add('active');
}
// 切换端口字段显示
function togglePortFields() {
const protocol = document.getElementById('protocol-type').value;
const portFields = document.getElementById('port-fields');
const customProtocolGroup = document.getElementById('custom-protocol-group');
if (protocol === 'tcp' || protocol === 'udp') {
portFields.style.display = 'grid';
customProtocolGroup.style.display = 'none';
} else if (protocol === 'custom') {
portFields.style.display = 'none';
customProtocolGroup.style.display = 'block';
} else {
portFields.style.display = 'none';
customProtocolGroup.style.display = 'none';
}
}
// 保存规则
function saveRule() {
const name = document.getElementById('rule-name').value.trim();
if (!name) {
alert('请输入规则名称');
return;
}
const rule = {
id: currentEditId || Date.now(),
name: name,
enabled: document.getElementById('rule-enabled').checked,
direction: document.getElementById('rule-direction').value,
program: document.getElementById('program-path').value || '任意',
protocol: document.getElementById('protocol-type').value.toUpperCase(),
localPort: document.getElementById('local-ports').value || '任意',
remotePort: document.getElementById('remote-ports').value || '任意',
remoteAddress: '任意', // 简化处理
action: document.getElementById('rule-action').value,
description: document.getElementById('rule-description').value
};
if (currentEditId) {
// 更新现有规则
const index = rules.findIndex(r => r.id === currentEditId);
if (index !== -1) {
rules[index] = rule;
}
} else {
// 添加新规则
rules.unshift(rule);
}
renderRules();
closeModal();
updateStats();
}
// 重置表单
function resetForm() {
document.getElementById('rule-name').value = '';
document.getElementById('rule-description').value = '';
document.getElementById('rule-direction').value = 'inbound';
document.getElementById('rule-action').value = 'allow';
document.getElementById('rule-enabled').checked = true;
document.getElementById('program-path').value = '';
document.getElementById('protocol-type').value = 'any';
document.getElementById('local-ports').value = '';
document.getElementById('remote-ports').value = '';
// 重置到第一个标签页
document.querySelectorAll('.tab').forEach((tab, index) => {
tab.classList.toggle('active', index === 0);
});
document.querySelectorAll('.tab-content').forEach((content, index) => {
content.classList.toggle('active', index === 0);
});
}
// 过滤规则
function filterRules(type) {
// 实际实现中应该过滤规则
console.log('过滤规则:', type);
}
// 搜索规则
function searchRules(keyword) {
// 实际实现中应该搜索规则
console.log('搜索规则:', keyword);
}
// 更新统计
function updateStats() {
document.getElementById('total-rules').textContent = rules.length;
}
// 导出规则
function exportRules() {
const dataStr = JSON.stringify(rules, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'firewall_rules.json';
link.click();
}
// 导入规则
function importRules() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
try {
const importedRules = JSON.parse(e.target.result);
rules = importedRules;
renderRules();
updateStats();
alert('规则导入成功!');
} catch (err) {
alert('导入失败:文件格式错误');
}
};
reader.readAsText(file);
}
};
input.click();
}
// 处理本地/远程IP选择
document.querySelectorAll('input[name="local-ip"]').forEach(radio => {
radio.addEventListener('change', function() {
document.getElementById('local-ip-list').style.display =
this.value === 'custom' ? 'block' : 'none';
});
});
document.querySelectorAll('input[name="remote-ip"]').forEach(radio => {
radio.addEventListener('change', function() {
document.getElementById('remote-ip-list').style.display =
this.value === 'custom' ? 'block' : 'none';
});
});
// 页面加载时初始化
window.onload = function() {
renderRules();
updateStats();
};
</script>
</body>
</html>
@@ -0,0 +1,1176 @@
<!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: #3b82f6;
--primary-dark: #2563eb;
--secondary: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--dark: #1f2937;
--gray: #6b7280;
--light-gray: #f3f4f6;
--border: #e5e7eb;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f9fafb;
color: var(--dark);
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: white;
padding: 24px 32px;
border-radius: 12px;
box-shadow: var(--shadow);
margin-bottom: 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 28px;
font-weight: 700;
color: var(--dark);
display: flex;
align-items: center;
gap: 12px;
}
.header .icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 24px;
}
.stat-card {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: var(--shadow);
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.stat-card h3 {
font-size: 14px;
color: var(--gray);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.stat-card .value {
font-size: 32px;
font-weight: 700;
color: var(--dark);
display: flex;
align-items: baseline;
gap: 8px;
}
.stat-card .badge {
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
font-weight: 500;
}
.stat-card .badge.success {
background: #d1fae5;
color: #065f46;
}
.stat-card .badge.danger {
background: #fee2e2;
color: #991b1b;
}
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 16px;
}
.filters {
display: flex;
gap: 12px;
align-items: center;
}
.search-box {
position: relative;
}
.search-box input {
padding: 10px 16px 10px 40px;
border: 2px solid var(--border);
border-radius: 8px;
font-size: 14px;
width: 300px;
transition: all 0.3s ease;
}
.search-box input:focus {
outline: none;
border-color: var(--primary);
}
.search-box::before {
content: "🔍";
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
}
.filter-btn {
padding: 10px 16px;
border: 2px solid var(--border);
background: white;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 6px;
}
.filter-btn:hover {
border-color: var(--primary);
color: var(--primary);
}
.filter-btn.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.btn-primary {
padding: 10px 20px;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary:hover {
background: var(--primary-dark);
transform: translateY(-1px);
}
.table-container {
background: white;
border-radius: 12px;
box-shadow: var(--shadow);
overflow: hidden;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background: var(--light-gray);
padding: 16px;
text-align: left;
font-weight: 600;
font-size: 14px;
color: var(--gray);
text-transform: uppercase;
letter-spacing: 0.5px;
}
td {
padding: 16px;
border-top: 1px solid var(--border);
}
tr:hover {
background: #f9fafb;
}
.rule-name {
font-weight: 500;
color: var(--dark);
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
}
.status-badge.enabled {
background: #d1fae5;
color: #065f46;
}
.status-badge.disabled {
background: #f3f4f6;
color: #6b7280;
}
.direction-badge {
padding: 4px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
.direction-badge.inbound {
background: #dbeafe;
color: #1e40af;
}
.direction-badge.outbound {
background: #fce7f3;
color: #9f1239;
}
.actions {
display: flex;
gap: 8px;
}
.action-btn {
padding: 6px 12px;
border: 1px solid var(--border);
background: white;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.action-btn:hover {
border-color: var(--primary);
color: var(--primary);
}
.action-btn.delete:hover {
border-color: var(--danger);
color: var(--danger);
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
padding: 20px;
overflow-y: auto;
}
.modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
width: 100%;
max-width: 800px;
border-radius: 16px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
padding: 24px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 {
font-size: 20px;
font-weight: 600;
}
.close-btn {
width: 32px;
height: 32px;
border: none;
background: var(--light-gray);
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.close-btn:hover {
background: var(--border);
}
.modal-body {
padding: 24px;
}
.form-section {
margin-bottom: 24px;
}
.form-section h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: var(--dark);
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
color: var(--dark);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px 14px;
border: 2px solid var(--border);
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--primary);
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.checkbox-group label {
margin-bottom: 0;
cursor: pointer;
}
.radio-group {
display: flex;
gap: 20px;
margin-bottom: 12px;
}
.radio-group label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
margin-bottom: 0;
}
.service-list {
max-height: 200px;
overflow-y: auto;
border: 2px solid var(--border);
border-radius: 8px;
padding: 12px;
}
.service-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.service-item:last-child {
margin-bottom: 0;
}
.modal-footer {
padding: 24px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 12px;
}
.btn-secondary {
padding: 10px 20px;
background: white;
color: var(--gray);
border: 2px solid var(--border);
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-secondary:hover {
border-color: var(--gray);
color: var(--dark);
}
.port-inputs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.tabs {
display: flex;
gap: 4px;
background: var(--light-gray);
padding: 4px;
border-radius: 10px;
margin-bottom: 20px;
}
.tab {
padding: 8px 16px;
background: transparent;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.tab.active {
background: white;
box-shadow: var(--shadow);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>
<div class="icon">🛡️</div>
防火墙管理中心
</h1>
<div style="display: flex; gap: 12px;">
<button class="btn-secondary">导入规则</button>
<button class="btn-secondary">导出规则</button>
</div>
</div>
<!-- Statistics -->
<div class="stats">
<div class="stat-card">
<h3>活跃规则</h3>
<div class="value">
156
<span class="badge success">+12%</span>
</div>
</div>
<div class="stat-card">
<h3>入站规则</h3>
<div class="value">89</div>
</div>
<div class="stat-card">
<h3>出站规则</h3>
<div class="value">67</div>
</div>
<div class="stat-card">
<h3>阻止连接</h3>
<div class="value">
2.3K
<span class="badge danger">今日</span>
</div>
</div>
</div>
<!-- Toolbar -->
<div class="toolbar">
<div class="filters">
<div class="search-box">
<input type="text" placeholder="搜索规则名称、程序或端口...">
</div>
<button class="filter-btn active">全部</button>
<button class="filter-btn">启用</button>
<button class="filter-btn">禁用</button>
<button class="filter-btn">入站</button>
<button class="filter-btn">出站</button>
</div>
<button class="btn-primary" onclick="openModal()">
<span>+</span> 新建规则
</button>
</div>
<!-- Rules Table -->
<div class="table-container">
<table>
<thead>
<tr>
<th>规则名称</th>
<th>状态</th>
<th>方向</th>
<th>程序</th>
<th>协议</th>
<th>端口</th>
<th>操作</th>
<th style="width: 120px;">管理</th>
</tr>
</thead>
<tbody id="rulesTable">
<!-- Table rows will be populated by JavaScript -->
</tbody>
</table>
</div>
</div>
<!-- Rule Modal -->
<div id="ruleModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">新建防火墙规则</h2>
<button class="close-btn" onclick="closeModal()">✕</button>
</div>
<div class="modal-body">
<!-- Tabs -->
<div class="tabs">
<button class="tab active" onclick="switchTab('general')">常规</button>
<button class="tab" onclick="switchTab('program')">程序</button>
<button class="tab" onclick="switchTab('protocol')">协议和端口</button>
<button class="tab" onclick="switchTab('scope')">作用域</button>
<button class="tab" onclick="switchTab('advanced')">高级</button>
</div>
<!-- Tab Contents -->
<div id="general-tab" class="tab-content active">
<div class="form-section">
<h3>基本信息</h3>
<div class="form-group">
<label>规则名称</label>
<input type="text" id="ruleName" placeholder="输入规则名称">
</div>
<div class="form-group">
<label>描述(可选)</label>
<textarea id="ruleDescription" placeholder="输入规则描述"></textarea>
</div>
<div class="form-group">
<label>方向</label>
<div class="radio-group">
<label>
<input type="radio" name="direction" value="inbound" checked>
入站
</label>
<label>
<input type="radio" name="direction" value="outbound">
出站
</label>
</div>
</div>
<div class="form-group">
<label>操作</label>
<div class="radio-group">
<label>
<input type="radio" name="action" value="allow" checked>
允许连接
</label>
<label>
<input type="radio" name="action" value="block">
阻止连接
</label>
<label>
<input type="radio" name="action" value="allowSecure">
允许安全连接
</label>
</div>
</div>
</div>
</div>
<div id="program-tab" class="tab-content">
<div class="form-section">
<h3>程序设置</h3>
<div class="form-group">
<div class="radio-group">
<label>
<input type="radio" name="programType" value="all" checked onchange="toggleProgramPath()">
所有程序
</label>
<label>
<input type="radio" name="programType" value="specific" onchange="toggleProgramPath()">
指定程序
</label>
</div>
</div>
<div class="form-group" id="programPathGroup" style="display: none;">
<label>程序路径</label>
<input type="text" id="programPath" placeholder="浏览或输入程序路径">
</div>
<div class="form-section">
<h3>自定义服务设置</h3>
<div class="checkbox-group">
<input type="checkbox" id="applyToServices" onchange="toggleServiceList()">
<label for="applyToServices">应用于下列服务</label>
</div>
<div class="service-list" id="serviceList" style="display: none;">
<div class="service-item">
<input type="checkbox" id="service1">
<label for="service1">Application Layer Gateway Service</label>
</div>
<div class="service-item">
<input type="checkbox" id="service2">
<label for="service2">Background Intelligent Transfer Service</label>
</div>
<div class="service-item">
<input type="checkbox" id="service3">
<label for="service3">Base Filtering Engine</label>
</div>
<div class="service-item">
<input type="checkbox" id="service4">
<label for="service4">Bluetooth Support Service</label>
</div>
<div class="service-item">
<input type="checkbox" id="service5">
<label for="service5">Certificate Propagation</label>
</div>
<div class="service-item">
<input type="checkbox" id="service6">
<label for="service6">Credential Manager</label>
</div>
<div class="service-item">
<input type="checkbox" id="service7">
<label for="service7">Cryptographic Services</label>
</div>
<div class="service-item">
<input type="checkbox" id="service8">
<label for="service8">DHCP Client</label>
</div>
<div class="service-item">
<input type="checkbox" id="service9">
<label for="service9">Distributed Link Tracking Client</label>
</div>
<div class="service-item">
<input type="checkbox" id="service10">
<label for="service10">DNS Client</label>
</div>
</div>
</div>
</div>
</div>
<div id="protocol-tab" class="tab-content">
<div class="form-section">
<h3>协议和端口</h3>
<div class="form-group">
<label>协议类型</label>
<select id="protocol" onchange="togglePortSettings()">
<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>
<option value="ah">AH (IPSec)</option>
<option value="esp">ESP (IPSec)</option>
<option value="gre">GRE</option>
<option value="l2tp">L2TP</option>
<option value="igmp">IGMP</option>
<option value="ipv6">IPv6</option>
<option value="ipv6-route">IPv6-Route</option>
<option value="ipv6-frag">IPv6-Frag</option>
</select>
</div>
<div id="portSettings" style="display: none;">
<div class="form-group">
<label>端口类型</label>
<div class="radio-group">
<label>
<input type="radio" name="portType" value="all" checked onchange="togglePortInputs()">
所有端口
</label>
<label>
<input type="radio" name="portType" value="specific" onchange="togglePortInputs()">
特定端口
</label>
</div>
</div>
<div id="portInputs" style="display: none;">
<div class="port-inputs">
<div class="form-group">
<label>本地端口</label>
<input type="text" id="localPort" placeholder="例如: 80, 443, 1000-2000">
</div>
<div class="form-group">
<label>远程端口</label>
<input type="text" id="remotePort" placeholder="例如: 80, 443, 1000-2000">
</div>
</div>
</div>
</div>
<div id="icmpSettings" style="display: none;">
<div class="form-group">
<label>ICMP 类型</label>
<select id="icmpType">
<option value="all">所有 ICMP 类型</option>
<option value="echo-request">Echo Request</option>
<option value="echo-reply">Echo Reply</option>
<option value="destination-unreachable">Destination Unreachable</option>
<option value="source-quench">Source Quench</option>
<option value="redirect">Redirect</option>
<option value="time-exceeded">Time Exceeded</option>
</select>
</div>
</div>
</div>
</div>
<div id="scope-tab" class="tab-content">
<div class="form-section">
<h3>本地 IP 地址</h3>
<div class="radio-group">
<label>
<input type="radio" name="localIP" value="any" checked>
任何 IP 地址
</label>
<label>
<input type="radio" name="localIP" value="specific">
下列 IP 地址
</label>
</div>
<div class="form-group" style="margin-top: 12px;">
<textarea placeholder="输入 IP 地址或范围,每行一个" disabled></textarea>
</div>
</div>
<div class="form-section">
<h3>远程 IP 地址</h3>
<div class="radio-group">
<label>
<input type="radio" name="remoteIP" value="any" checked>
任何 IP 地址
</label>
<label>
<input type="radio" name="remoteIP" value="specific">
下列 IP 地址
</label>
</div>
<div class="form-group" style="margin-top: 12px;">
<textarea placeholder="输入 IP 地址或范围,每行一个" disabled></textarea>
</div>
</div>
</div>
<div id="advanced-tab" class="tab-content">
<div class="form-section">
<h3>配置文件</h3>
<div class="checkbox-group">
<input type="checkbox" id="profileDomain" checked>
<label for="profileDomain">域</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="profilePrivate" checked>
<label for="profilePrivate">专用</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="profilePublic" checked>
<label for="profilePublic">公用</label>
</div>
</div>
<div class="form-section">
<h3>用户和计算机</h3>
<div class="form-group">
<label>授权的用户</label>
<div class="radio-group">
<label>
<input type="radio" name="users" value="all" checked>
所有用户
</label>
<label>
<input type="radio" name="users" value="specific">
指定用户和组
</label>
</div>
</div>
<div class="form-group">
<label>授权的计算机</label>
<div class="radio-group">
<label>
<input type="radio" name="computers" value="all" checked>
所有计算机
</label>
<label>
<input type="radio" name="computers" value="specific">
指定计算机
</label>
</div>
</div>
</div>
<div class="form-section">
<h3>接口类型</h3>
<div class="checkbox-group">
<input type="checkbox" id="interfaceAll" checked>
<label for="interfaceAll">所有接口类型</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="interfaceLAN">
<label for="interfaceLAN">局域网</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="interfaceWireless">
<label for="interfaceWireless">无线</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="interfaceRemote">
<label for="interfaceRemote">远程访问</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeModal()">取消</button>
<button class="btn-primary" onclick="saveRule()">保存规则</button>
</div>
</div>
</div>
<script>
// Sample data
const rules = [
{
id: 1,
name: "Windows 远程桌面",
status: "enabled",
direction: "inbound",
program: "System",
protocol: "TCP",
port: "3389",
action: "允许"
},
{
id: 2,
name: "HTTP 服务器",
status: "enabled",
direction: "inbound",
program: "nginx.exe",
protocol: "TCP",
port: "80",
action: "允许"
},
{
id: 3,
name: "HTTPS 服务器",
status: "enabled",
direction: "inbound",
program: "nginx.exe",
protocol: "TCP",
port: "443",
action: "允许"
},
{
id: 4,
name: "Steam 客户端",
status: "enabled",
direction: "outbound",
program: "steam.exe",
protocol: "TCP/UDP",
port: "27015-27030",
action: "允许"
},
{
id: 5,
name: "BitTorrent",
status: "disabled",
direction: "inbound",
program: "uTorrent.exe",
protocol: "TCP",
port: "6881-6889",
action: "阻止"
},
{
id: 6,
name: "MySQL 数据库",
status: "enabled",
direction: "inbound",
program: "mysqld.exe",
protocol: "TCP",
port: "3306",
action: "允许"
},
{
id: 7,
name: "SSH 服务",
status: "enabled",
direction: "inbound",
program: "sshd.exe",
protocol: "TCP",
port: "22",
action: "允许"
},
{
id: 8,
name: "FTP 服务器",
status: "disabled",
direction: "inbound",
program: "filezilla-server.exe",
protocol: "TCP",
port: "21",
action: "允许"
}
];
let currentEditId = null;
// Render table
function renderTable() {
const tbody = document.getElementById('rulesTable');
tbody.innerHTML = rules.map(rule => `
<tr>
<td class="rule-name">${rule.name}</td>
<td>
<span class="status-badge ${rule.status}">
<span style="font-size: 8px;">●</span>
${rule.status === 'enabled' ? '启用' : '禁用'}
</span>
</td>
<td>
<span class="direction-badge ${rule.direction}">
${rule.direction === 'inbound' ? '入站' : '出站'}
</span>
</td>
<td>${rule.program}</td>
<td>${rule.protocol}</td>
<td>${rule.port}</td>
<td>${rule.action}</td>
<td class="actions">
<button class="action-btn" onclick="editRule(${rule.id})">编辑</button>
<button class="action-btn delete" onclick="deleteRule(${rule.id})">删除</button>
</td>
</tr>
`).join('');
}
// Modal functions
function openModal() {
document.getElementById('ruleModal').classList.add('active');
document.getElementById('modalTitle').textContent = '新建防火墙规则';
currentEditId = null;
clearForm();
}
function closeModal() {
document.getElementById('ruleModal').classList.remove('active');
}
function editRule(id) {
const rule = rules.find(r => r.id === id);
if (rule) {
document.getElementById('ruleModal').classList.add('active');
document.getElementById('modalTitle').textContent = '编辑防火墙规则';
currentEditId = id;
// Populate form with rule data
document.getElementById('ruleName').value = rule.name;
document.querySelector(`input[name="direction"][value="${rule.direction}"]`).checked = true;
// ... populate other fields
}
}
function deleteRule(id) {
if (confirm('确定要删除这条规则吗?')) {
const index = rules.findIndex(r => r.id === id);
if (index > -1) {
rules.splice(index, 1);
renderTable();
}
}
}
function saveRule() {
const ruleName = document.getElementById('ruleName').value;
const direction = document.querySelector('input[name="direction"]:checked').value;
const action = document.querySelector('input[name="action"]:checked').value;
if (!ruleName) {
alert('请输入规则名称');
return;
}
if (currentEditId) {
// Update existing rule
const rule = rules.find(r => r.id === currentEditId);
if (rule) {
rule.name = ruleName;
rule.direction = direction;
// ... update other fields
}
} else {
// Add new rule
const newRule = {
id: rules.length + 1,
name: ruleName,
status: 'enabled',
direction: direction,
program: 'Custom',
protocol: 'TCP',
port: '自定义',
action: action === 'allow' ? '允许' : '阻止'
};
rules.push(newRule);
}
renderTable();
closeModal();
}
function clearForm() {
document.getElementById('ruleName').value = '';
document.getElementById('ruleDescription').value = '';
document.querySelector('input[name="direction"][value="inbound"]').checked = true;
document.querySelector('input[name="action"][value="allow"]').checked = true;
}
// Tab switching
function switchTab(tabName) {
// Remove active class from all tabs and contents
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
// Add active class to selected tab and content
event.target.classList.add('active');
document.getElementById(tabName + '-tab').classList.add('active');
}
// Toggle functions
function toggleProgramPath() {
const programType = document.querySelector('input[name="programType"]:checked').value;
document.getElementById('programPathGroup').style.display =
programType === 'specific' ? 'block' : 'none';
}
function toggleServiceList() {
const checked = document.getElementById('applyToServices').checked;
document.getElementById('serviceList').style.display = checked ? 'block' : 'none';
}
function togglePortSettings() {
const protocol = document.getElementById('protocol').value;
const showPorts = ['tcp', 'udp'].includes(protocol);
const showIcmp = ['icmpv4', 'icmpv6'].includes(protocol);
document.getElementById('portSettings').style.display = showPorts ? 'block' : 'none';
document.getElementById('icmpSettings').style.display = showIcmp ? 'block' : 'none';
}
function togglePortInputs() {
const portType = document.querySelector('input[name="portType"]:checked').value;
document.getElementById('portInputs').style.display =
portType === 'specific' ? 'block' : 'none';
}
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', function() {
// Remove active class from all filter buttons
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
// Add active class to clicked button
this.classList.add('active');
// Here you would implement the actual filtering logic
});
});
// Initial render
renderTable();
// Close modal when clicking outside
document.getElementById('ruleModal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
</script>
</body>
</html>
@@ -0,0 +1,1148 @@
<!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 {
--bg-primary: #0a0e27;
--bg-secondary: #151932;
--bg-card: #1a1f3a;
--bg-hover: #242a4a;
--text-primary: #ffffff;
--text-secondary: #a8b2d1;
--text-muted: #64748b;
--accent: #3b82f6;
--accent-hover: #2563eb;
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--border: #2d3561;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: var(--bg-secondary);
padding: 24px 32px;
border-radius: 16px;
margin-bottom: 32px;
box-shadow: 0 4px 24px rgba(0,0,0,0.3);
display: flex;
justify-content: space-between;
align-items: center;
backdrop-filter: blur(10px);
border: 1px solid var(--border);
}
.header h1 {
font-size: 28px;
font-weight: 600;
background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 32px;
}
.stat-card {
background: var(--bg-card);
padding: 24px;
border-radius: 12px;
border: 1px solid var(--border);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--accent) 0%, #60a5fa 100%);
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.stat-card:hover::before {
transform: translateX(0);
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(59, 130, 246, 0.2);
}
.stat-value {
font-size: 36px;
font-weight: 700;
margin-bottom: 8px;
}
.stat-label {
color: var(--text-secondary);
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 16px;
}
.search-box {
flex: 1;
max-width: 400px;
position: relative;
}
.search-box input {
width: 100%;
padding: 12px 48px 12px 20px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 14px;
transition: all 0.3s ease;
}
.search-box input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.search-icon {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
}
.filter-buttons {
display: flex;
gap: 12px;
}
.filter-btn {
padding: 8px 16px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
}
.filter-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.filter-btn.active {
background: var(--accent);
color: white;
border-color: var(--accent);
}
.btn-primary {
padding: 12px 24px;
background: linear-gradient(135deg, var(--accent) 0%, #2563eb 100%);
border: none;
border-radius: 8px;
color: white;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(59, 130, 246, 0.4);
}
.rules-table {
background: var(--bg-card);
border-radius: 12px;
overflow: hidden;
border: 1px solid var(--border);
box-shadow: 0 4px 24px rgba(0,0,0,0.2);
}
.table-header {
display: grid;
grid-template-columns: 40px 2fr 1fr 1fr 100px 100px 120px 100px 100px 60px;
padding: 16px 24px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
font-weight: 600;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-secondary);
}
.rule-row {
display: grid;
grid-template-columns: 40px 2fr 1fr 1fr 100px 100px 120px 100px 100px 60px;
padding: 16px 24px;
border-bottom: 1px solid var(--border);
transition: all 0.3s ease;
cursor: pointer;
align-items: center;
}
.rule-row:hover {
background: var(--bg-hover);
transform: translateX(4px);
}
.rule-row:last-child {
border-bottom: none;
}
.checkbox {
width: 20px;
height: 20px;
accent-color: var(--accent);
}
.rule-name {
font-weight: 500;
color: var(--text-primary);
}
.rule-program {
color: var(--text-secondary);
font-size: 13px;
}
.status-badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
display: inline-block;
}
.status-enabled {
background: rgba(16, 185, 129, 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(59, 130, 246, 0.2);
color: #60a5fa;
}
.direction-out {
background: rgba(245, 158, 11, 0.2);
color: var(--warning);
}
.actions {
display: flex;
gap: 8px;
}
.action-btn {
width: 32px;
height: 32px;
border: none;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
border-radius: 6px;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.action-btn:hover {
background: var(--bg-hover);
color: var(--accent);
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
z-index: 1000;
animation: fadeIn 0.3s ease;
}
.modal.active {
display: flex;
align-items: center;
justify-content: center;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
background: var(--bg-card);
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
border-radius: 16px;
border: 1px solid var(--border);
animation: slideUp 0.3s ease;
}
@keyframes slideUp {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.modal-header {
padding: 24px 32px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
background: var(--bg-secondary);
}
.modal-header h2 {
font-size: 20px;
font-weight: 600;
}
.close-btn {
width: 40px;
height: 40px;
border: none;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
border-radius: 8px;
font-size: 24px;
transition: all 0.3s ease;
}
.close-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.modal-body {
padding: 32px;
}
.form-section {
margin-bottom: 32px;
}
.form-section:last-child {
margin-bottom: 0;
}
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: var(--text-secondary);
}
.form-input, .form-select, .form-textarea {
width: 100%;
padding: 12px 16px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 14px;
transition: all 0.3s ease;
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-textarea {
resize: vertical;
min-height: 100px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.radio-group, .checkbox-group {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.radio-label, .checkbox-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: 14px;
}
.service-list {
max-height: 200px;
overflow-y: auto;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px;
}
.service-item {
padding: 8px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s ease;
}
.service-item:hover {
background: var(--bg-hover);
}
.modal-footer {
padding: 24px 32px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 12px;
background: var(--bg-secondary);
}
.btn-secondary {
padding: 12px 24px;
background: transparent;
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-secondary);
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-secondary:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent);
}
/* 响应式设计 */
@media (max-width: 1200px) {
.table-header, .rule-row {
grid-template-columns: 40px 2fr 1fr 80px 80px 100px 60px;
}
.rule-protocol, .rule-scope {
display: none;
}
}
@media (max-width: 768px) {
.stats {
grid-template-columns: 1fr;
}
.controls {
flex-direction: column;
}
.search-box {
max-width: 100%;
}
.form-row {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 页头 -->
<div class="header">
<h1>🛡️ 防火墙管理控制台</h1>
<div class="header-actions">
<button class="btn-primary" onclick="openModal()">
<span>+</span>
<span>新建规则</span>
</button>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="totalRules">42</div>
<div class="stat-label">总规则数</div>
</div>
<div class="stat-card">
<div class="stat-value" id="activeRules">38</div>
<div class="stat-label">活动规则</div>
</div>
<div class="stat-card">
<div class="stat-value" id="inboundRules">18</div>
<div class="stat-label">入站规则</div>
</div>
<div class="stat-card">
<div class="stat-value" id="outboundRules">24</div>
<div class="stat-label">出站规则</div>
</div>
</div>
<!-- 控制栏 -->
<div class="controls">
<div class="search-box">
<input type="text" placeholder="搜索规则名称、程序或端口..." id="searchInput">
<span class="search-icon">🔍</span>
</div>
<div class="filter-buttons">
<button class="filter-btn active" onclick="filterRules('all')">全部</button>
<button class="filter-btn" onclick="filterRules('inbound')">入站</button>
<button class="filter-btn" onclick="filterRules('outbound')">出站</button>
<button class="filter-btn" onclick="filterRules('enabled')">已启用</button>
<button class="filter-btn" onclick="filterRules('disabled')">已禁用</button>
</div>
</div>
<!-- 规则表格 -->
<div class="rules-table">
<div class="table-header">
<div></div>
<div>名称</div>
<div>程序</div>
<div>协议</div>
<div>端口</div>
<div>远程端口</div>
<div>作用域</div>
<div>方向</div>
<div>状态</div>
<div>操作</div>
</div>
<div id="rulesContainer">
<!-- 规则将通过JavaScript动态生成 -->
</div>
</div>
</div>
<!-- 新建/编辑规则模态框 -->
<div class="modal" id="ruleModal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">新建防火墙规则</h2>
<button class="close-btn" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">
<!-- 基本信息 -->
<div class="form-section">
<h3 class="section-title">📋 基本信息</h3>
<div class="form-group">
<label class="form-label">规则名称</label>
<input type="text" class="form-input" id="ruleName" placeholder="输入规则名称">
</div>
<div class="form-group">
<label class="form-label">描述</label>
<textarea class="form-textarea" id="ruleDescription" placeholder="输入规则描述(可选)"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">规则类型</label>
<select class="form-select" id="ruleType">
<option value="program">程序</option>
<option value="port">端口</option>
<option value="predefined">预定义</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="form-group">
<label class="form-label">方向</label>
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="direction" value="inbound" checked>
入站
</label>
<label class="radio-label">
<input type="radio" name="direction" value="outbound">
出站
</label>
</div>
</div>
</div>
</div>
<!-- 程序设置 -->
<div class="form-section">
<h3 class="section-title">💻 程序设置</h3>
<div class="form-group">
<label class="form-label">程序路径</label>
<input type="text" class="form-input" id="programPath" placeholder="例如:C:\Program Files\MyApp\app.exe">
</div>
<div class="form-group">
<label class="form-label">应用于服务</label>
<div class="checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="applyToAllServices">
应用于所有程序和服务
</label>
<label class="checkbox-label">
<input type="checkbox" id="applyToServices">
仅应用于服务
</label>
</div>
</div>
<div class="form-group">
<label class="form-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">Windows Defender</label>
</div>
<div class="service-item">
<input type="checkbox" id="service3">
<label for="service3">Remote Desktop</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 class="form-section">
<h3 class="section-title">🔌 协议和端口</h3>
<div class="form-row">
<div class="form-group">
<label class="form-label">协议类型</label>
<select class="form-select" id="protocolType">
<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">
<label class="form-label">协议编号</label>
<input type="number" class="form-input" id="protocolNumber" placeholder="0-255" disabled>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">本地端口</label>
<input type="text" class="form-input" id="localPort" placeholder="例如:80, 443, 1000-2000">
</div>
<div class="form-group">
<label class="form-label">远程端口</label>
<input type="text" class="form-input" id="remotePort" placeholder="例如:80, 443, 1000-2000">
</div>
</div>
</div>
<!-- 作用域 -->
<div class="form-section">
<h3 class="section-title">🌐 作用域</h3>
<div class="form-group">
<label class="form-label">本地IP地址</label>
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="localIP" value="any" checked>
任何IP地址
</label>
<label class="radio-label">
<input type="radio" name="localIP" value="specific">
指定IP地址
</label>
</div>
<input type="text" class="form-input" id="localIPList" placeholder="例如:192.168.1.0/24" style="margin-top: 10px; display: none;">
</div>
<div class="form-group">
<label class="form-label">远程IP地址</label>
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="remoteIP" value="any" checked>
任何IP地址
</label>
<label class="radio-label">
<input type="radio" name="remoteIP" value="specific">
指定IP地址
</label>
</div>
<input type="text" class="form-input" id="remoteIPList" placeholder="例如:10.0.0.0/8" style="margin-top: 10px; display: none;">
</div>
</div>
<!-- 高级设置 -->
<div class="form-section">
<h3 class="section-title">⚙️ 高级设置</h3>
<div class="form-group">
<label class="form-label">配置文件</label>
<div class="checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="profileDomain" checked>
域
</label>
<label class="checkbox-label">
<input type="checkbox" id="profilePrivate" checked>
专用
</label>
<label class="checkbox-label">
<input type="checkbox" id="profilePublic" checked>
公用
</label>
</div>
</div>
<div class="form-group">
<label class="form-label">用户和计算机</label>
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="users" value="all" checked>
所有用户和计算机
</label>
<label class="radio-label">
<input type="radio" name="users" value="specific">
指定用户或组
</label>
</div>
<input type="text" class="form-input" id="userList" placeholder="输入用户或组名称" style="margin-top: 10px; display: none;">
</div>
<div class="form-group">
<label class="form-label">操作</label>
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="action" value="allow" checked>
允许连接
</label>
<label class="radio-label">
<input type="radio" name="action" value="block">
阻止连接
</label>
<label class="radio-label">
<input type="radio" name="action" value="secure">
允许安全连接
</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeModal()">取消</button>
<button class="btn-primary" onclick="saveRule()">保存规则</button>
</div>
</div>
</div>
<script>
// 模拟防火墙规则数据
let rules = [
{
id: 1,
name: "Chrome 浏览器",
program: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
protocol: "TCP",
port: "80, 443",
remotePort: "任意",
scope: "任意",
direction: "outbound",
status: "enabled",
action: "允许"
},
{
id: 2,
name: "远程桌面 - 用户模式 (TCP-In)",
program: "系统",
protocol: "TCP",
port: "3389",
remotePort: "任意",
scope: "任意",
direction: "inbound",
status: "enabled",
action: "允许"
},
{
id: 3,
name: "Windows Media Player",
program: "C:\\Program Files\\Windows Media Player\\wmplayer.exe",
protocol: "UDP",
port: "554, 8554",
remotePort: "任意",
scope: "本地子网",
direction: "inbound",
status: "disabled",
action: "允许"
},
{
id: 4,
name: "SQL Server",
program: "C:\\Program Files\\Microsoft SQL Server\\MSSQL\\Binn\\sqlservr.exe",
protocol: "TCP",
port: "1433",
remotePort: "任意",
scope: "192.168.1.0/24",
direction: "inbound",
status: "enabled",
action: "允许"
},
{
id: 5,
name: "核心网络 - DHCP (Out)",
program: "系统",
protocol: "UDP",
port: "68",
remotePort: "67",
scope: "任意",
direction: "outbound",
status: "enabled",
action: "允许"
}
];
let currentFilter = 'all';
let editingRule = null;
// 渲染规则列表
function renderRules() {
const container = document.getElementById('rulesContainer');
container.innerHTML = '';
let filteredRules = rules;
// 应用过滤器
if (currentFilter === 'inbound') {
filteredRules = rules.filter(r => r.direction === 'inbound');
} else if (currentFilter === 'outbound') {
filteredRules = rules.filter(r => r.direction === 'outbound');
} else if (currentFilter === 'enabled') {
filteredRules = rules.filter(r => r.status === 'enabled');
} else if (currentFilter === 'disabled') {
filteredRules = rules.filter(r => r.status === 'disabled');
}
// 应用搜索
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
if (searchTerm) {
filteredRules = filteredRules.filter(r =>
r.name.toLowerCase().includes(searchTerm) ||
r.program.toLowerCase().includes(searchTerm) ||
r.port.toLowerCase().includes(searchTerm)
);
}
filteredRules.forEach(rule => {
const row = document.createElement('div');
row.className = 'rule-row';
row.innerHTML = `
<div><input type="checkbox" class="checkbox"></div>
<div class="rule-name">${rule.name}</div>
<div class="rule-program">${rule.program}</div>
<div>${rule.protocol}</div>
<div>${rule.port}</div>
<div>${rule.remotePort}</div>
<div class="rule-scope">${rule.scope}</div>
<div><span class="direction-badge direction-${rule.direction === 'inbound' ? 'in' : 'out'}">${rule.direction === 'inbound' ? '入站' : '出站'}</span></div>
<div><span class="status-badge status-${rule.status}">${rule.status === 'enabled' ? '已启用' : '已禁用'}</span></div>
<div class="actions">
<button class="action-btn" onclick="editRule(${rule.id})" title="编辑">✏️</button>
<button class="action-btn" onclick="deleteRule(${rule.id})" title="删除">🗑️</button>
</div>
`;
row.onclick = (e) => {
if (!e.target.classList.contains('action-btn') && e.target.type !== 'checkbox') {
editRule(rule.id);
}
};
container.appendChild(row);
});
updateStats();
}
// 更新统计信息
function updateStats() {
document.getElementById('totalRules').textContent = rules.length;
document.getElementById('activeRules').textContent = rules.filter(r => r.status === 'enabled').length;
document.getElementById('inboundRules').textContent = rules.filter(r => r.direction === 'inbound').length;
document.getElementById('outboundRules').textContent = rules.filter(r => r.direction === 'outbound').length;
}
// 过滤规则
function filterRules(filter) {
currentFilter = filter;
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
renderRules();
}
// 打开模态框
function openModal() {
document.getElementById('ruleModal').classList.add('active');
document.getElementById('modalTitle').textContent = '新建防火墙规则';
editingRule = null;
clearForm();
}
// 关闭模态框
function closeModal() {
document.getElementById('ruleModal').classList.remove('active');
editingRule = null;
}
// 编辑规则
function editRule(id) {
const rule = rules.find(r => r.id === id);
if (rule) {
editingRule = rule;
document.getElementById('modalTitle').textContent = '编辑防火墙规则';
document.getElementById('ruleName').value = rule.name;
document.getElementById('programPath').value = rule.program;
document.getElementById('protocolType').value = rule.protocol.toLowerCase();
document.getElementById('localPort').value = rule.port;
document.getElementById('remotePort').value = rule.remotePort === '任意' ? '' : rule.remotePort;
document.querySelector(`input[name="direction"][value="${rule.direction}"]`).checked = true;
document.querySelector(`input[name="action"][value="allow"]`).checked = rule.action === '允许';
document.getElementById('ruleModal').classList.add('active');
}
}
// 删除规则
function deleteRule(id) {
if (confirm('确定要删除这条规则吗?')) {
rules = rules.filter(r => r.id !== id);
renderRules();
}
}
// 保存规则
function saveRule() {
const name = document.getElementById('ruleName').value;
const program = document.getElementById('programPath').value || '系统';
const protocol = document.getElementById('protocolType').value.toUpperCase();
const port = document.getElementById('localPort').value || '任意';
const remotePort = document.getElementById('remotePort').value || '任意';
const direction = document.querySelector('input[name="direction"]:checked').value;
const action = document.querySelector('input[name="action"]:checked').value === 'allow' ? '允许' : '阻止';
if (!name) {
alert('请输入规则名称');
return;
}
if (editingRule) {
// 更新现有规则
editingRule.name = name;
editingRule.program = program;
editingRule.protocol = protocol;
editingRule.port = port;
editingRule.remotePort = remotePort;
editingRule.direction = direction;
editingRule.action = action;
} else {
// 创建新规则
const newRule = {
id: Date.now(),
name,
program,
protocol,
port,
remotePort,
scope: '任意',
direction,
status: 'enabled',
action
};
rules.push(newRule);
}
renderRules();
closeModal();
}
// 清空表单
function clearForm() {
document.getElementById('ruleName').value = '';
document.getElementById('ruleDescription').value = '';
document.getElementById('programPath').value = '';
document.getElementById('protocolType').value = 'any';
document.getElementById('localPort').value = '';
document.getElementById('remotePort').value = '';
document.querySelector('input[name="direction"][value="inbound"]').checked = true;
document.querySelector('input[name="action"][value="allow"]').checked = true;
}
// 搜索功能
document.getElementById('searchInput').addEventListener('input', renderRules);
// 协议类型变化时的处理
document.getElementById('protocolType').addEventListener('change', function() {
const protocolNumber = document.getElementById('protocolNumber');
if (this.value === 'custom') {
protocolNumber.disabled = false;
} else {
protocolNumber.disabled = true;
protocolNumber.value = '';
}
});
// IP地址选择的处理
document.querySelectorAll('input[name="localIP"]').forEach(radio => {
radio.addEventListener('change', function() {
const localIPList = document.getElementById('localIPList');
if (this.value === 'specific') {
localIPList.style.display = 'block';
} else {
localIPList.style.display = 'none';
}
});
});
document.querySelectorAll('input[name="remoteIP"]').forEach(radio => {
radio.addEventListener('change', function() {
const remoteIPList = document.getElementById('remoteIPList');
if (this.value === 'specific') {
remoteIPList.style.display = 'block';
} else {
remoteIPList.style.display = 'none';
}
});
});
// 用户选择的处理
document.querySelectorAll('input[name="users"]').forEach(radio => {
radio.addEventListener('change', function() {
const userList = document.getElementById('userList');
if (this.value === 'specific') {
userList.style.display = 'block';
} else {
userList.style.display = 'none';
}
});
});
// 点击模态框外部关闭
document.getElementById('ruleModal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// 初始化
renderRules();
</script>
</body>
</html>
@@ -0,0 +1,1600 @@
<!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>
/* 基础变量定义 */
:root {
/* 浅色主题 */
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--bg-tertiary: #f1f5f9;
--bg-hover: #f3f4f6;
--text-primary: #1e293b;
--text-secondary: #64748b;
--text-tertiary: #94a3b8;
--border-color: #e2e8f0;
--border-hover: #cbd5e1;
--primary-color: #3b82f6;
--primary-hover: #2563eb;
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--info-color: #6366f1;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
}
/* 深色主题 */
[data-theme="dark"] {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #334155;
--bg-hover: #475569;
--text-primary: #f1f5f9;
--text-secondary: #cbd5e1;
--text-tertiary: #94a3b8;
--border-color: #334155;
--border-hover: #475569;
--primary-color: #3b82f6;
--primary-hover: #60a5fa;
--success-color: #34d399;
--warning-color: #fbbf24;
--danger-color: #f87171;
--info-color: #818cf8;
}
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
background-color: var(--bg-secondary);
color: var(--text-primary);
line-height: 1.6;
transition: background-color 0.3s, color 0.3s;
}
/* 布局容器 */
.app-container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 顶部导航栏 */
.app-header {
background-color: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
padding: 16px 24px;
box-shadow: var(--shadow-sm);
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
max-width: 1400px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
}
.header-title {
display: flex;
align-items: center;
gap: 12px;
}
.header-title h1 {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
}
.header-actions {
display: flex;
align-items: center;
gap: 16px;
}
/* 主内容区域 */
.main-content {
flex: 1;
max-width: 1400px;
width: 100%;
margin: 0 auto;
padding: 24px;
}
/* 卡片组件 */
.card {
background-color: var(--bg-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-color);
overflow: hidden;
transition: all 0.3s;
}
.card-header {
padding: 20px 24px;
border-bottom: 1px solid var(--border-color);
background-color: var(--bg-secondary);
}
.card-title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.card-body {
padding: 24px;
}
/* 按钮样式 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
border-radius: var(--radius-md);
border: none;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-secondary {
background-color: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background-color: var(--bg-hover);
border-color: var(--border-hover);
}
.btn-success {
background-color: var(--success-color);
color: white;
}
.btn-danger {
background-color: var(--danger-color);
color: white;
}
.btn-sm {
padding: 6px 12px;
font-size: 13px;
}
.btn-icon {
padding: 8px;
width: 36px;
height: 36px;
}
/* 图标 */
.icon {
width: 20px;
height: 20px;
display: inline-block;
}
/* 状态开关 */
.switch {
position: relative;
display: inline-block;
width: 48px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.switch-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--bg-tertiary);
transition: 0.3s;
border-radius: 24px;
border: 1px solid var(--border-color);
}
.switch-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
box-shadow: var(--shadow-sm);
}
.switch input:checked + .switch-slider {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.switch input:checked + .switch-slider:before {
transform: translateX(24px);
}
/* 搜索框 */
.search-box {
position: relative;
flex: 1;
max-width: 400px;
}
.search-input {
width: 100%;
padding: 10px 16px 10px 42px;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 14px;
color: var(--text-primary);
transition: all 0.3s;
}
.search-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.search-icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: var(--text-tertiary);
pointer-events: none;
}
/* 下拉菜单 */
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
position: absolute;
top: 100%;
margin-top: 4px;
background-color: var(--bg-primary);
min-width: 200px;
box-shadow: var(--shadow-lg);
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.3s;
z-index: 50;
}
.dropdown:hover .dropdown-content {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.dropdown-item {
padding: 10px 16px;
font-size: 14px;
color: var(--text-primary);
cursor: pointer;
transition: background-color 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.dropdown-item:hover {
background-color: var(--bg-hover);
}
/* 表格样式 */
.table-wrapper {
overflow-x: auto;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
}
.table {
width: 100%;
border-collapse: collapse;
background-color: var(--bg-primary);
}
.table th {
background-color: var(--bg-secondary);
font-weight: 600;
font-size: 14px;
color: var(--text-secondary);
text-align: left;
padding: 12px 16px;
border-bottom: 1px solid var(--border-color);
white-space: nowrap;
}
.table td {
padding: 14px 16px;
font-size: 14px;
color: var(--text-primary);
border-bottom: 1px solid var(--border-color);
}
.table tr:hover td {
background-color: var(--bg-hover);
}
.table tr:last-child td {
border-bottom: none;
}
/* 标签样式 */
.badge {
display: inline-flex;
align-items: center;
padding: 4px 10px;
font-size: 12px;
font-weight: 500;
border-radius: 20px;
line-height: 1;
}
.badge-success {
background-color: rgba(16, 185, 129, 0.1);
color: var(--success-color);
border: 1px solid rgba(16, 185, 129, 0.2);
}
.badge-danger {
background-color: rgba(239, 68, 68, 0.1);
color: var(--danger-color);
border: 1px solid rgba(239, 68, 68, 0.2);
}
.badge-warning {
background-color: rgba(245, 158, 11, 0.1);
color: var(--warning-color);
border: 1px solid rgba(245, 158, 11, 0.2);
}
.badge-info {
background-color: rgba(99, 102, 241, 0.1);
color: var(--info-color);
border: 1px solid rgba(99, 102, 241, 0.2);
}
/* 模态框 */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
.modal.show {
display: flex;
align-items: center;
justify-content: center;
animation: fadeIn 0.3s;
}
.modal-content {
background-color: var(--bg-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow: hidden;
animation: slideUp 0.3s;
}
.modal-header {
padding: 20px 24px;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-title {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
}
.modal-body {
padding: 24px;
overflow-y: auto;
max-height: calc(90vh - 140px);
}
.modal-footer {
padding: 16px 24px;
border-top: 1px solid var(--border-color);
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* 表单样式 */
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: 10px 14px;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 14px;
color: var(--text-primary);
transition: all 0.3s;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-textarea {
resize: vertical;
min-height: 80px;
}
.form-hint {
font-size: 12px;
color: var(--text-tertiary);
margin-top: 4px;
}
/* 单选框和复选框 */
.radio-group,
.checkbox-group {
display: flex;
gap: 16px;
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;
accent-color: var(--primary-color);
cursor: pointer;
}
/* 标签页 */
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 24px;
}
.tab {
padding: 12px 24px;
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
cursor: pointer;
position: relative;
transition: color 0.3s;
}
.tab:hover {
color: var(--text-primary);
}
.tab.active {
color: var(--primary-color);
}
.tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background-color: var(--primary-color);
}
/* 工具栏 */
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.toolbar-left,
.toolbar-right {
display: flex;
align-items: center;
gap: 12px;
}
/* 筛选器 */
.filter-group {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background-color: var(--bg-secondary);
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
}
/* 分页 */
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: 24px;
}
.page-item {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
color: var(--text-secondary);
}
.page-item:hover {
background-color: var(--bg-hover);
color: var(--text-primary);
}
.page-item.active {
background-color: var(--primary-color);
color: white;
}
/* 动画 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* 响应式 */
@media (max-width: 768px) {
.main-content {
padding: 16px;
}
.toolbar {
flex-direction: column;
align-items: stretch;
}
.toolbar-left,
.toolbar-right {
width: 100%;
justify-content: space-between;
}
.table-wrapper {
border-radius: 0;
margin: 0 -16px;
border-left: none;
border-right: none;
}
.modal-content {
width: 100%;
height: 100%;
max-height: 100vh;
border-radius: 0;
}
}
/* 折叠面板 */
.collapse {
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
overflow: hidden;
margin-bottom: 12px;
}
.collapse-header {
padding: 16px 20px;
background-color: var(--bg-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
transition: background-color 0.3s;
}
.collapse-header:hover {
background-color: var(--bg-hover);
}
.collapse-title {
font-weight: 500;
color: var(--text-primary);
}
.collapse-content {
padding: 20px;
background-color: var(--bg-primary);
display: none;
}
.collapse.active .collapse-content {
display: block;
}
/* 端口输入组 */
.port-input-group {
display: flex;
align-items: center;
gap: 12px;
}
.port-input {
flex: 1;
}
/* 地址输入组 */
.address-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.address-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background-color: var(--bg-secondary);
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
}
/* 用户选择器 */
.user-selector {
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 8px;
}
.user-item {
padding: 10px;
cursor: pointer;
border-radius: var(--radius-sm);
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: space-between;
}
.user-item:hover {
background-color: var(--bg-hover);
}
.user-item.selected {
background-color: var(--primary-color);
color: white;
}
</style>
</head>
<body data-theme="light">
<div class="app-container">
<!-- 顶部导航栏 -->
<header class="app-header">
<div class="header-content">
<div class="header-title">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M19,11H13V5a1,1,0,0,0-2,0v6H5a1,1,0,0,0,0,2h6v6a1,1,0,0,0,2,0V13h6a1,1,0,0,0,0-2Z"/>
</svg>
<h1>防火墙规则管理</h1>
</div>
<div class="header-actions">
<!-- 防火墙总开关 -->
<div style="display: flex; align-items: center; gap: 12px; padding: 8px 16px; background-color: var(--bg-secondary); border-radius: var(--radius-md);">
<span style="font-size: 14px; color: var(--text-secondary);">防火墙状态</span>
<label class="switch">
<input type="checkbox" checked>
<span class="switch-slider"></span>
</label>
</div>
<!-- 主题切换 -->
<button class="btn btn-icon btn-secondary" onclick="toggleTheme()">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</button>
</div>
</div>
</header>
<!-- 主内容区域 -->
<main class="main-content">
<!-- 统计卡片 -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 24px;">
<div class="card">
<div class="card-body" style="padding: 20px;">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="font-size: 14px; color: var(--text-secondary); margin-bottom: 4px;">总规则数</div>
<div style="font-size: 28px; font-weight: 600; color: var(--text-primary);">127</div>
</div>
<div style="width: 48px; height: 48px; background-color: rgba(59, 130, 246, 0.1); border-radius: 12px; display: flex; align-items: center; justify-content: center;">
<svg class="icon" style="color: var(--primary-color);" viewBox="0 0 24 24" fill="currentColor">
<path d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm0,18a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"/>
</svg>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body" style="padding: 20px;">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="font-size: 14px; color: var(--text-secondary); margin-bottom: 4px;">启用规则</div>
<div style="font-size: 28px; font-weight: 600; color: var(--success-color);">89</div>
</div>
<div style="width: 48px; height: 48px; background-color: rgba(16, 185, 129, 0.1); border-radius: 12px; display: flex; align-items: center; justify-content: center;">
<svg class="icon" style="color: var(--success-color);" viewBox="0 0 24 24" fill="currentColor">
<path d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm5.707,7.293-8,8a1,1,0,0,1-1.414,0l-3-3a1,1,0,1,1,1.414-1.414L9,15.172l7.293-7.293a1,1,0,0,1,1.414,1.414Z"/>
</svg>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body" style="padding: 20px;">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="font-size: 14px; color: var(--text-secondary); margin-bottom: 4px;">禁用规则</div>
<div style="font-size: 28px; font-weight: 600; color: var(--text-tertiary);">38</div>
</div>
<div style="width: 48px; height: 48px; background-color: rgba(148, 163, 184, 0.1); border-radius: 12px; display: flex; align-items: center; justify-content: center;">
<svg class="icon" style="color: var(--text-tertiary);" viewBox="0 0 24 24" fill="currentColor">
<path d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm0,18a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"/>
<path d="M12,7a1,1,0,0,0-1,1v8a1,1,0,0,0,2,0V8A1,1,0,0,0,12,7Z"/>
</svg>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body" style="padding: 20px;">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="font-size: 14px; color: var(--text-secondary); margin-bottom: 4px;">阻止连接</div>
<div style="font-size: 28px; font-weight: 600; color: var(--danger-color);">1,247</div>
</div>
<div style="width: 48px; height: 48px; background-color: rgba(239, 68, 68, 0.1); border-radius: 12px; display: flex; align-items: center; justify-content: center;">
<svg class="icon" style="color: var(--danger-color);" viewBox="0 0 24 24" fill="currentColor">
<path d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm0,18a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"/>
<path d="M15.707,8.293a1,1,0,0,0-1.414,0L12,10.586,9.707,8.293A1,1,0,0,0,8.293,9.707L10.586,12l-2.293,2.293a1,1,0,1,0,1.414,1.414L12,13.414l2.293,2.293a1,1,0,0,0,1.414-1.414L13.414,12l2.293-2.293A1,1,0,0,0,15.707,8.293Z"/>
</svg>
</div>
</div>
</div>
</div>
</div>
<!-- 规则列表卡片 -->
<div class="card">
<div class="card-header">
<h2 class="card-title">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M4,6H20a1,1,0,0,0,0-2H4A1,1,0,0,0,4,6ZM20,11H4a1,1,0,0,0,0,2H20a1,1,0,0,0,0-2Zm0,7H4a1,1,0,0,0,0,2H20a1,1,0,0,0,0-2Z"/>
</svg>
防火墙规则列表
</h2>
</div>
<div class="card-body">
<!-- 工具栏 -->
<div class="toolbar">
<div class="toolbar-left">
<button class="btn btn-primary" onclick="document.getElementById('ruleModal').classList.add('show')">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M19,11H13V5a1,1,0,0,0-2,0v6H5a1,1,0,0,0,0,2h6v6a1,1,0,0,0,2,0V13h6a1,1,0,0,0,0-2Z"/>
</svg>
新建规则
</button>
<div class="dropdown">
<button class="btn btn-secondary">
批量操作
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M7,10l5,5,5-5Z"/>
</svg>
</button>
<div class="dropdown-content">
<div class="dropdown-item">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm5.707,7.293-8,8a1,1,0,0,1-1.414,0l-3-3a1,1,0,1,1,1.414-1.414L9,15.172l7.293-7.293a1,1,0,0,1,1.414,1.414Z"/>
</svg>
启用选中
</div>
<div class="dropdown-item">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm0,18a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"/>
<path d="M12,7a1,1,0,0,0-1,1v8a1,1,0,0,0,2,0V8A1,1,0,0,0,12,7Z"/>
</svg>
禁用选中
</div>
<div class="dropdown-item" style="color: var(--danger-color);">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M21,4H8A1,1,0,0,0,8,6H20V19a1,1,0,0,0,2,0V5A1,1,0,0,0,21,4Z"/>
<path d="M4,8H16a1,1,0,0,1,1,1V21a1,1,0,0,1-1,1H4a1,1,0,0,1-1-1V9A1,1,0,0,1,4,8ZM6,12v6a1,1,0,0,0,2,0V12A1,1,0,0,0,6,12Zm4,0v6a1,1,0,0,0,2,0V12a1,1,0,0,0-2,0Zm4,0v6a1,1,0,0,0,2,0V12a1,1,0,0,0-2,0Z"/>
</svg>
删除选中
</div>
</div>
</div>
</div>
<div class="toolbar-right">
<div class="search-box">
<svg class="icon search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<input type="text" class="search-input" placeholder="搜索规则名称...">
</div>
<div class="dropdown">
<button class="btn btn-secondary">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="4" y1="21" x2="4" y2="14"></line>
<line x1="4" y1="10" x2="4" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12" y2="3"></line>
<line x1="20" y1="21" x2="20" y2="16"></line>
<line x1="20" y1="12" x2="20" y2="3"></line>
<line x1="1" y1="14" x2="7" y2="14"></line>
<line x1="9" y1="8" x2="15" y2="8"></line>
<line x1="17" y1="16" x2="23" y2="16"></line>
</svg>
筛选
</button>
<div class="dropdown-content" style="width: 300px; padding: 16px;">
<div class="form-group">
<label class="form-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>
</div>
</div>
<div class="form-group">
<label class="form-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>
</div>
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label">配置文件</label>
<select class="form-select">
<option>全部</option>
<option>域</option>
<option>专用</option>
<option>公用</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- 表格 -->
<div class="table-wrapper">
<table class="table">
<thead>
<tr>
<th style="width: 40px;">
<input type="checkbox">
</th>
<th>规则名称</th>
<th>状态</th>
<th>方向</th>
<th>操作</th>
<th>程序/服务</th>
<th>协议</th>
<th>端口</th>
<th>配置文件</th>
<th style="width: 120px;">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox"></td>
<td>
<div style="font-weight: 500;">Windows 远程桌面</div>
<div style="font-size: 12px; color: var(--text-tertiary);">允许远程桌面连接</div>
</td>
<td>
<span class="badge badge-success">启用</span>
</td>
<td>
<span style="display: flex; align-items: center; gap: 4px;">
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M11,16.414V7.828L8.707,10.121a1,1,0,0,1-1.414-1.414l4-4a1,1,0,0,1,1.414,0l4,4a1,1,0,1,1-1.414,1.414L13,7.828v8.586a1,1,0,0,1-2,0ZM20,12v7a1,1,0,0,1-1,1H5a1,1,0,0,1-1-1V12a1,1,0,0,1,2,0v6H18V12a1,1,0,0,1,2,0Z"/>
</svg>
入站
</span>
</td>
<td>
<span class="badge badge-success">允许</span>
</td>
<td>系统服务</td>
<td>TCP</td>
<td>3389</td>
<td>
<div style="display: flex; gap: 4px;">
<span class="badge badge-info">域</span>
<span class="badge badge-info">专用</span>
</div>
</td>
<td>
<div style="display: flex; gap: 8px;">
<button class="btn btn-sm btn-secondary" onclick="document.getElementById('ruleModal').classList.add('show')">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</div>
</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>
<div style="font-weight: 500;">Microsoft Edge</div>
<div style="font-size: 12px; color: var(--text-tertiary);">允许浏览器访问网络</div>
</td>
<td>
<span class="badge badge-success">启用</span>
</td>
<td>
<span style="display: flex; align-items: center; gap: 4px;">
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M13,7.828V16.414a1,1,0,0,1-2,0V7.828L8.707,10.121a1,1,0,0,1-1.414-1.414l4-4a1,1,0,0,1,1.414,0l4,4a1,1,0,0,1-1.414,1.414ZM20,12v7a1,1,0,0,1-1,1H5a1,1,0,0,1-1-1V12a1,1,0,0,1,2,0v6H18V12a1,1,0,0,1,2,0Z"/>
</svg>
出站
</span>
</td>
<td>
<span class="badge badge-success">允许</span>
</td>
<td>C:\Program Files\Microsoft\Edge\msedge.exe</td>
<td>任意</td>
<td>任意</td>
<td>
<div style="display: flex; gap: 4px;">
<span class="badge badge-info">域</span>
<span class="badge badge-info">专用</span>
<span class="badge badge-info">公用</span>
</div>
</td>
<td>
<div style="display: flex; gap: 8px;">
<button class="btn btn-sm btn-secondary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</div>
</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>
<div style="font-weight: 500;">阻止 Telnet</div>
<div style="font-size: 12px; color: var(--text-tertiary);">安全策略:阻止 Telnet 连接</div>
</td>
<td>
<span class="badge badge-success">启用</span>
</td>
<td>
<span style="display: flex; align-items: center; gap: 4px;">
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M11,16.414V7.828L8.707,10.121a1,1,0,0,1-1.414-1.414l4-4a1,1,0,0,1,1.414,0l4,4a1,1,0,1,1-1.414,1.414L13,7.828v8.586a1,1,0,0,1-2,0ZM20,12v7a1,1,0,0,1-1,1H5a1,1,0,0,1-1-1V12a1,1,0,0,1,2,0v6H18V12a1,1,0,0,1,2,0Z"/>
</svg>
入站
</span>
</td>
<td>
<span class="badge badge-danger">阻止</span>
</td>
<td>任意</td>
<td>TCP</td>
<td>23</td>
<td>
<div style="display: flex; gap: 4px;">
<span class="badge badge-info">域</span>
<span class="badge badge-info">专用</span>
<span class="badge badge-info">公用</span>
</div>
</td>
<td>
<div style="display: flex; gap: 8px;">
<button class="btn btn-sm btn-secondary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</div>
</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>
<div style="font-weight: 500;">文件和打印机共享</div>
<div style="font-size: 12px; color: var(--text-tertiary);">允许局域网内文件共享</div>
</td>
<td>
<span class="badge" style="background-color: rgba(148, 163, 184, 0.1); color: var(--text-tertiary); border: 1px solid rgba(148, 163, 184, 0.2);">禁用</span>
</td>
<td>
<span style="display: flex; align-items: center; gap: 4px;">
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M11,16.414V7.828L8.707,10.121a1,1,0,0,1-1.414-1.414l4-4a1,1,0,0,1,1.414,0l4,4a1,1,0,1,1-1.414,1.414L13,7.828v8.586a1,1,0,0,1-2,0ZM20,12v7a1,1,0,0,1-1,1H5a1,1,0,0,1-1-1V12a1,1,0,0,1,2,0v6H18V12a1,1,0,0,1,2,0Z"/>
</svg>
入站
</span>
</td>
<td>
<span class="badge badge-success">允许</span>
</td>
<td>系统服务</td>
<td>TCP</td>
<td>445, 139</td>
<td>
<div style="display: flex; gap: 4px;">
<span class="badge badge-info">专用</span>
</div>
</td>
<td>
<div style="display: flex; gap: 8px;">
<button class="btn btn-sm btn-secondary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</div>
</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>
<div style="font-weight: 500;">HTTP 服务器</div>
<div style="font-size: 12px; color: var(--text-tertiary);">允许 Web 服务器接收请求</div>
</td>
<td>
<span class="badge badge-success">启用</span>
</td>
<td>
<span style="display: flex; align-items: center; gap: 4px;">
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M11,16.414V7.828L8.707,10.121a1,1,0,0,1-1.414-1.414l4-4a1,1,0,0,1,1.414,0l4,4a1,1,0,1,1-1.414,1.414L13,7.828v8.586a1,1,0,0,1-2,0ZM20,12v7a1,1,0,0,1-1,1H5a1,1,0,0,1-1-1V12a1,1,0,0,1,2,0v6H18V12a1,1,0,0,1,2,0Z"/>
</svg>
入站
</span>
</td>
<td>
<span class="badge badge-success">允许</span>
</td>
<td>任意</td>
<td>TCP</td>
<td>80, 443</td>
<td>
<div style="display: flex; gap: 4px;">
<span class="badge badge-info">域</span>
<span class="badge badge-info">专用</span>
<span class="badge badge-info">公用</span>
</div>
</td>
<td>
<div style="display: flex; gap: 8px;">
<button class="btn btn-sm btn-secondary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<button class="page-item">
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M15.707,17.293,11.414,13l4.293-4.293a1,1,0,0,0-1.414-1.414l-5,5a1,1,0,0,0,0,1.414l5,5a1,1,0,0,0,1.414-1.414Z"/>
</svg>
</button>
<button class="page-item active">1</button>
<button class="page-item">2</button>
<button class="page-item">3</button>
<button class="page-item">...</button>
<button class="page-item">12</button>
<button class="page-item">
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M8.293,17.293a1,1,0,0,0,1.414,1.414l5-5a1,1,0,0,0,0-1.414l-5-5A1,1,0,0,0,8.293,6.707L12.586,11,8.293,15.293Z"/>
</svg>
</button>
</div>
</div>
</div>
</main>
</div>
<!-- 规则编辑模态框 -->
<div id="ruleModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">新建防火墙规则</h3>
<button class="btn btn-icon btn-secondary" onclick="this.closest('.modal').classList.remove('show')">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.707,5.293a1,1,0,0,0-1.414,0L12,10.586,6.707,5.293A1,1,0,0,0,5.293,6.707L10.586,12l-5.293,5.293a1,1,0,1,0,1.414,1.414L12,13.414l5.293,5.293a1,1,0,0,0,1.414-1.414L13.414,12l5.293-5.293A1,1,0,0,0,18.707,5.293Z"/>
</svg>
</button>
</div>
<div class="modal-body">
<!-- 标签页 -->
<div class="tabs">
<div class="tab active">基本信息</div>
<div class="tab">匹配条件</div>
<div class="tab">网络属性</div>
<div class="tab">安全控制</div>
</div>
<!-- 基本信息 -->
<div class="tab-content">
<div class="collapse active">
<div class="collapse-header">
<span class="collapse-title">规则标识</span>
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M7,10l5,5,5-5Z"/>
</svg>
</div>
<div class="collapse-content">
<div class="form-group">
<label class="form-label">规则名称 *</label>
<input type="text" class="form-input" placeholder="输入规则名称">
<div class="form-hint">请输入一个描述性的名称,便于识别规则用途</div>
</div>
<div class="form-group">
<label class="form-label">描述</label>
<textarea class="form-textarea" placeholder="输入规则描述"></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div class="form-group">
<label class="form-label">优先级</label>
<input type="number" class="form-input" value="100" min="0" max="999">
<div class="form-hint">数值越小优先级越高</div>
</div>
<div class="form-group">
<label class="form-label">策略组</label>
<select class="form-select">
<option>默认组</option>
<option>安全策略组</option>
<option>应用程序组</option>
<option>自定义组</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">标签</label>
<input type="text" class="form-input" placeholder="输入标签,用逗号分隔">
<div class="form-hint">例如: Web服务, 生产环境, 高优先级</div>
</div>
</div>
</div>
<div class="collapse">
<div class="collapse-header">
<span class="collapse-title">规则行为</span>
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M7,10l5,5,5-5Z"/>
</svg>
</div>
<div class="collapse-content">
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px;">
<div class="form-group">
<label class="form-label">状态</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="status" checked>
<span>启用</span>
</label>
<label class="radio-item">
<input type="radio" name="status">
<span>禁用</span>
</label>
</div>
</div>
<div class="form-group">
<label class="form-label">方向</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="direction" checked>
<span>入站</span>
</label>
<label class="radio-item">
<input type="radio" name="direction">
<span>出站</span>
</label>
</div>
</div>
<div class="form-group">
<label class="form-label">操作</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="action" checked>
<span>允许</span>
</label>
<label class="radio-item">
<input type="radio" name="action">
<span>阻止</span>
</label>
<label class="radio-item">
<input type="radio" name="action">
<span>询问</span>
</label>
</div>
</div>
</div>
<div class="form-group">
<label class="checkbox-item">
<input type="checkbox">
<span>记录匹配的连接日志</span>
</label>
</div>
</div>
</div>
</div>
<!-- 其他标签页内容暂时隐藏 -->
<!-- 程序/服务选择器示例 -->
<div class="collapse">
<div class="collapse-header">
<span class="collapse-title">程序和服务</span>
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M7,10l5,5,5-5Z"/>
</svg>
</div>
<div class="collapse-content">
<div class="form-group">
<label class="form-label">应用程序</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="app" checked>
<span>所有程序</span>
</label>
<label class="radio-item">
<input type="radio" name="app">
<span>指定程序</span>
</label>
</div>
</div>
<div class="form-group">
<label class="form-label">程序路径</label>
<div style="display: flex; gap: 12px;">
<input type="text" class="form-input" placeholder="C:\Program Files\..." style="flex: 1;">
<button class="btn btn-secondary">浏览</button>
</div>
</div>
<div class="form-group">
<label class="form-label">系统服务</label>
<div class="search-box" style="margin-bottom: 12px;">
<svg class="icon search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<input type="text" class="search-input" placeholder="搜索服务...">
</div>
<div style="border: 1px solid var(--border-color); border-radius: var(--radius-md); max-height: 200px; overflow-y: auto;">
<label class="checkbox-item" style="padding: 10px 16px; border-bottom: 1px solid var(--border-color); display: flex;">
<input type="checkbox">
<div style="margin-left: 8px;">
<div>Windows Update</div>
<div style="font-size: 12px; color: var(--text-tertiary);">wuauserv - 自动更新服务</div>
</div>
</label>
<label class="checkbox-item" style="padding: 10px 16px; border-bottom: 1px solid var(--border-color); display: flex;">
<input type="checkbox" checked>
<div style="margin-left: 8px;">
<div>远程桌面服务</div>
<div style="font-size: 12px; color: var(--text-tertiary);">TermService - 允许远程连接</div>
</div>
</label>
<label class="checkbox-item" style="padding: 10px 16px; border-bottom: 1px solid var(--border-color); display: flex;">
<input type="checkbox">
<div style="margin-left: 8px;">
<div>Windows Defender</div>
<div style="font-size: 12px; color: var(--text-tertiary);">WinDefend - 防病毒服务</div>
</div>
</label>
<label class="checkbox-item" style="padding: 10px 16px; display: flex;">
<input type="checkbox">
<div style="margin-left: 8px;">
<div>打印后台处理程序</div>
<div style="font-size: 12px; color: var(--text-tertiary);">Spooler - 管理打印作业</div>
</div>
</label>
</div>
</div>
</div>
</div>
<!-- 端口配置示例 -->
<div class="collapse">
<div class="collapse-header">
<span class="collapse-title">协议和端口</span>
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M7,10l5,5,5-5Z"/>
</svg>
</div>
<div class="collapse-content">
<div class="form-group">
<label class="form-label">协议类型</label>
<select class="form-select">
<option>任意</option>
<option selected>TCP</option>
<option>UDP</option>
<option>ICMPv4</option>
<option>ICMPv6</option>
<option>自定义</option>
</select>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div class="form-group">
<label class="form-label">本地端口</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="local-port" checked>
<span>所有端口</span>
</label>
<label class="radio-item">
<input type="radio" name="local-port">
<span>指定端口</span>
</label>
</div>
<div class="port-input-group" style="margin-top: 12px;">
<input type="text" class="form-input port-input" placeholder="例: 80, 443, 8080-8090">
</div>
</div>
<div class="form-group">
<label class="form-label">远程端口</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="remote-port" checked>
<span>所有端口</span>
</label>
<label class="radio-item">
<input type="radio" name="remote-port">
<span>指定端口</span>
</label>
</div>
<div class="port-input-group" style="margin-top: 12px;">
<input type="text" class="form-input port-input" placeholder="例: 80, 443, 8080-8090">
</div>
</div>
</div>
</div>
</div>
<!-- 用户选择器示例 -->
<div class="collapse">
<div class="collapse-header">
<span class="collapse-title">用户和用户组</span>
<svg class="icon" style="width: 16px; height: 16px;" viewBox="0 0 24 24" fill="currentColor">
<path d="M7,10l5,5,5-5Z"/>
</svg>
</div>
<div class="collapse-content">
<div class="form-group">
<label class="form-label">应用到</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="user-scope" checked>
<span>所有用户</span>
</label>
<label class="radio-item">
<input type="radio" name="user-scope">
<span>指定用户/组</span>
</label>
</div>
</div>
<div class="form-group">
<label class="form-label">选择用户或用户组</label>
<div class="user-selector">
<div class="user-item">
<span>Everyone</span>
<input type="checkbox">
</div>
<div class="user-item">
<span>Administrators</span>
<input type="checkbox" checked>
</div>
<div class="user-item">
<span>Users</span>
<input type="checkbox">
</div>
<div class="user-item">
<span>Network Service</span>
<input type="checkbox">
</div>
<div class="user-item">
<span>Local Service</span>
<input type="checkbox">
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">自定义 SID</label>
<input type="text" class="form-input" placeholder="S-1-5-21-...">
<div class="form-hint">输入安全标识符 (SID) 或用户名</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="this.closest('.modal').classList.remove('show')">取消</button>
<button class="btn btn-primary">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M9,20.42l-6.21-6.21a1,1,0,0,1,0-1.42l1.42-1.42a1,1,0,0,1,1.41,0L9,14.77l9.38-9.38a1,1,0,0,1,1.41,0l1.42,1.42a1,1,0,0,1,0,1.41Z"/>
</svg>
保存规则
</button>
</div>
</div>
</div>
<script>
// 主题切换(仅用于演示)
function toggleTheme() {
const body = document.body;
const currentTheme = body.getAttribute('data-theme');
body.setAttribute('data-theme', currentTheme === 'dark' ? 'light' : 'dark');
}
// 折叠面板切换(仅用于演示)
document.querySelectorAll('.collapse-header').forEach(header => {
header.addEventListener('click', function() {
this.parentElement.classList.toggle('active');
});
});
// 标签页切换(仅用于演示)
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
</script>
</body>
</html>
@@ -0,0 +1,1233 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防火墙管理看板</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.3.4/vue.global.prod.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
}
.app-container {
padding: 20px;
max-width: 1400px;
margin: 0 auto;
}
.header {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 24px 32px;
margin-bottom: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: #2d3748;
font-size: 2rem;
font-weight: 700;
display: flex;
align-items: center;
gap: 12px;
}
.header-stats {
display: flex;
gap: 24px;
}
.stat-item {
text-align: center;
padding: 12px 20px;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
color: white;
border-radius: 12px;
min-width: 80px;
}
.stat-number {
font-size: 1.5rem;
font-weight: 700;
}
.stat-label {
font-size: 0.875rem;
opacity: 0.9;
}
.controls {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 20px 32px;
margin-bottom: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 16px;
}
.filter-group {
display: flex;
gap: 12px;
align-items: center;
}
.filter-select, .search-input {
padding: 10px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
background: white;
}
.filter-select:focus, .search-input:focus {
outline: none;
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
.search-input {
min-width: 250px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, #4f46e5, #7c3aed);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(79, 70, 229, 0.3);
}
.btn-success {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
}
.btn-danger {
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
}
.btn-secondary {
background: #f1f5f9;
color: #64748b;
border: 1px solid #e2e8f0;
}
.btn:hover {
transform: translateY(-1px);
}
.rules-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
overflow: hidden;
}
.rules-header {
padding: 20px 32px;
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
border-bottom: 1px solid #e2e8f0;
}
.rules-grid {
display: grid;
grid-template-columns: 1fr 120px 100px 150px 120px 100px 140px;
gap: 16px;
font-weight: 600;
color: #475569;
}
.rule-item {
display: grid;
grid-template-columns: 1fr 120px 100px 150px 120px 100px 140px;
gap: 16px;
padding: 20px 32px;
border-bottom: 1px solid #f1f5f9;
transition: all 0.3s ease;
align-items: center;
}
.rule-item:hover {
background: rgba(79, 70, 229, 0.05);
transform: translateX(4px);
}
.rule-name {
font-weight: 600;
color: #2d3748;
}
.rule-program {
font-size: 0.875rem;
color: #64748b;
}
.status-badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-align: center;
}
.status-enabled {
background: #dcfce7;
color: #166534;
}
.status-disabled {
background: #fee2e2;
color: #991b1b;
}
.action-badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-align: center;
}
.action-allow {
background: #dcfce7;
color: #166534;
}
.action-block {
background: #fee2e2;
color: #991b1b;
}
.direction-badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-align: center;
}
.direction-inbound {
background: #dbeafe;
color: #1e40af;
}
.direction-outbound {
background: #fef3c7;
color: #92400e;
}
.rule-actions {
display: flex;
gap: 8px;
}
.icon-btn {
width: 32px;
height: 32px;
border: none;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.icon-btn:hover {
transform: scale(1.1);
}
.icon-btn.edit {
background: #f0f9ff;
color: #0369a1;
}
.icon-btn.delete {
background: #fef2f2;
color: #dc2626;
}
.icon-btn.toggle {
background: #f7fee7;
color: #65a30d;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.modal.show {
opacity: 1;
visibility: visible;
}
.modal-content {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 800px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
transform: scale(0.9);
transition: transform 0.3s ease;
}
.modal.show .modal-content {
transform: scale(1);
}
.modal-header {
padding: 24px 32px;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
font-size: 1.5rem;
font-weight: 700;
color: #2d3748;
}
.close-btn {
width: 40px;
height: 40px;
border: none;
background: #f1f5f9;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.close-btn:hover {
background: #e2e8f0;
transform: rotate(90deg);
}
.modal-body {
padding: 32px;
}
.form-group {
margin-bottom: 24px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 24px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151;
}
.form-input, .form-select, .form-textarea {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
outline: none;
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
.form-textarea {
resize: vertical;
min-height: 80px;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.checkbox {
width: 18px;
height: 18px;
}
.service-list {
max-height: 200px;
overflow-y: auto;
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 12px;
background: #f8fafc;
}
.service-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 6px;
transition: background 0.2s ease;
}
.service-item:hover {
background: #e2e8f0;
}
.modal-footer {
padding: 24px 32px;
border-top: 1px solid #e2e8f0;
display: flex;
gap: 12px;
justify-content: flex-end;
}
.tabs {
display: flex;
border-bottom: 2px solid #e2e8f0;
margin-bottom: 24px;
}
.tab {
padding: 12px 24px;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
font-weight: 600;
}
.tab.active {
color: #4f46e5;
border-bottom-color: #4f46e5;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
@media (max-width: 1200px) {
.rules-grid, .rule-item {
grid-template-columns: 1fr 100px 80px 120px 100px 120px;
}
}
@media (max-width: 768px) {
.controls {
flex-direction: column;
align-items: stretch;
}
.filter-group {
justify-content: center;
}
.rules-grid, .rule-item {
grid-template-columns: 1fr;
gap: 8px;
}
.rule-item {
padding: 16px 20px;
}
.modal-content {
width: 95%;
margin: 20px;
}
.form-row {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div id="app" class="app-container">
<!-- Header -->
<div class="header">
<div class="header-content">
<h1>
<i data-lucide="shield"></i>
防火墙管理看板
</h1>
<div class="header-stats">
<div class="stat-item">
<div class="stat-number">{{ enabledRules }}</div>
<div class="stat-label">已启用</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ disabledRules }}</div>
<div class="stat-label">已禁用</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ totalRules }}</div>
<div class="stat-label">总规则</div>
</div>
</div>
</div>
</div>
<!-- Controls -->
<div class="controls">
<div class="filter-group">
<select v-model="filterDirection" class="filter-select">
<option value="">所有方向</option>
<option value="inbound">入站</option>
<option value="outbound">出站</option>
</select>
<select v-model="filterAction" class="filter-select">
<option value="">所有操作</option>
<option value="allow">允许</option>
<option value="block">阻止</option>
</select>
<select v-model="filterStatus" class="filter-select">
<option value="">所有状态</option>
<option value="enabled">已启用</option>
<option value="disabled">已禁用</option>
</select>
</div>
<div class="filter-group">
<input
type="text"
placeholder="搜索规则名称..."
v-model="searchText"
class="search-input"
>
<button class="btn btn-primary" @click="openModal()">
<i data-lucide="plus"></i>
新增规则
</button>
</div>
</div>
<!-- Rules Container -->
<div class="rules-container">
<div class="rules-header">
<div class="rules-grid">
<div>规则名称</div>
<div>方向</div>
<div>操作</div>
<div>协议/端口</div>
<div>状态</div>
<div>作用域</div>
<div>操作</div>
</div>
</div>
<div v-for="rule in filteredRules" :key="rule.id" class="rule-item">
<div>
<div class="rule-name">{{ rule.name }}</div>
<div class="rule-program">{{ rule.program }}</div>
</div>
<div>
<span :class="'direction-badge direction-' + rule.direction">
{{ rule.direction === 'inbound' ? '入站' : '出站' }}
</span>
</div>
<div>
<span :class="'action-badge action-' + rule.action">
{{ rule.action === 'allow' ? '允许' : '阻止' }}
</span>
</div>
<div>{{ rule.protocol }}/{{ rule.ports }}</div>
<div>
<span :class="'status-badge status-' + rule.status">
{{ rule.status === 'enabled' ? '已启用' : '已禁用' }}
</span>
</div>
<div>{{ rule.scope }}</div>
<div class="rule-actions">
<button class="icon-btn edit" @click="editRule(rule)" title="编辑">
<i data-lucide="edit"></i>
</button>
<button class="icon-btn toggle" @click="toggleRule(rule)" title="切换状态">
<i :data-lucide="rule.status === 'enabled' ? 'pause' : 'play'"></i>
</button>
<button class="icon-btn delete" @click="deleteRule(rule.id)" title="删除">
<i data-lucide="trash-2"></i>
</button>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" :class="{ show: showModal }" @click.self="closeModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">{{ editingRule ? '编辑规则' : '新增规则' }}</h2>
<button class="close-btn" @click="closeModal">
<i data-lucide="x"></i>
</button>
</div>
<div class="modal-body">
<div class="tabs">
<div class="tab" :class="{ active: activeTab === 'general' }" @click="activeTab = 'general'">
常规
</div>
<div class="tab" :class="{ active: activeTab === 'programs' }" @click="activeTab = 'programs'">
程序和服务
</div>
<div class="tab" :class="{ active: activeTab === 'protocols' }" @click="activeTab = 'protocols'">
协议和端口
</div>
<div class="tab" :class="{ active: activeTab === 'scope' }" @click="activeTab = 'scope'">
作用域
</div>
<div class="tab" :class="{ active: activeTab === 'advanced' }" @click="activeTab = 'advanced'">
高级
</div>
</div>
<!-- 常规选项卡 -->
<div class="tab-content" :class="{ active: activeTab === 'general' }">
<div class="form-group">
<label class="form-label">规则名称</label>
<input type="text" v-model="formData.name" class="form-input" placeholder="输入规则名称">
</div>
<div class="form-group">
<label class="form-label">描述</label>
<textarea v-model="formData.description" class="form-textarea" placeholder="规则描述(可选)"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">方向</label>
<select v-model="formData.direction" class="form-select">
<option value="inbound">入站</option>
<option value="outbound">出站</option>
</select>
</div>
<div class="form-group">
<label class="form-label">操作</label>
<select v-model="formData.action" class="form-select">
<option value="allow">允许连接</option>
<option value="block">阻止连接</option>
</select>
</div>
</div>
<div class="checkbox-group">
<input type="checkbox" v-model="formData.enabled" class="checkbox" id="enabled">
<label for="enabled">启用此规则</label>
</div>
</div>
<!-- 程序和服务选项卡 -->
<div class="tab-content" :class="{ active: activeTab === 'programs' }">
<div class="form-group">
<label class="form-label">应用程序</label>
<div class="checkbox-group">
<input type="radio" v-model="formData.programType" value="all" name="programType" id="all-programs">
<label for="all-programs">所有程序</label>
</div>
<div class="checkbox-group">
<input type="radio" v-model="formData.programType" value="specific" name="programType" id="specific-program">
<label for="specific-program">此程序路径:</label>
</div>
<input
type="text"
v-model="formData.programPath"
class="form-input"
placeholder="C:\Program Files\..."
:disabled="formData.programType !== 'specific'"
>
</div>
<div class="form-group">
<label class="form-label">服务</label>
<div class="checkbox-group">
<input type="radio" v-model="formData.serviceType" value="all" name="serviceType" id="all-services">
<label for="all-services">应用于所有程序和服务</label>
</div>
<div class="checkbox-group">
<input type="radio" v-model="formData.serviceType" value="specific" name="serviceType" id="specific-services">
<label for="specific-services">仅应用于这些服务:</label>
</div>
<div v-if="formData.serviceType === 'specific'" class="service-list">
<div v-for="service in availableServices" :key="service.name" class="service-item">
<input
type="checkbox"
:value="service.name"
v-model="formData.selectedServices"
:id="'service-' + service.name"
>
<label :for="'service-' + service.name">{{ service.displayName }}</label>
</div>
</div>
</div>
</div>
<!-- 协议和端口选项卡 -->
<div class="tab-content" :class="{ active: activeTab === 'protocols' }">
<div class="form-group">
<label class="form-label">协议类型</label>
<select v-model="formData.protocolType" class="form-select">
<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 v-if="formData.protocolType === 'custom'" class="form-group">
<label class="form-label">协议号</label>
<input type="number" v-model="formData.protocolNumber" class="form-input" placeholder="0-255">
</div>
<div v-if="['tcp', 'udp'].includes(formData.protocolType)" class="form-row">
<div class="form-group">
<label class="form-label">本地端口</label>
<div class="checkbox-group">
<input type="radio" v-model="formData.localPortType" value="all" name="localPortType" id="all-local-ports">
<label for="all-local-ports">所有端口</label>
</div>
<div class="checkbox-group">
<input type="radio" v-model="formData.localPortType" value="specific" name="localPortType" id="specific-local-ports">
<label for="specific-local-ports">特定端口:</label>
</div>
<input
type="text"
v-model="formData.localPorts"
class="form-input"
placeholder="80,443,8000-8080"
:disabled="formData.localPortType !== 'specific'"
>
</div>
<div class="form-group">
<label class="form-label">远程端口</label>
<div class="checkbox-group">
<input type="radio" v-model="formData.remotePortType" value="all" name="remotePortType" id="all-remote-ports">
<label for="all-remote-ports">所有端口</label>
</div>
<div class="checkbox-group">
<input type="radio" v-model="formData.remotePortType" value="specific" name="remotePortType" id="specific-remote-ports">
<label for="specific-remote-ports">特定端口:</label>
</div>
<input
type="text"
v-model="formData.remotePorts"
class="form-input"
placeholder="80,443,8000-8080"
:disabled="formData.remotePortType !== 'specific'"
>
</div>
</div>
</div>
<!-- 作用域选项卡 -->
<div class="tab-content" :class="{ active: activeTab === 'scope' }">
<div class="form-group">
<label class="form-label">本地IP地址</label>
<div class="checkbox-group">
<input type="radio" v-model="formData.localAddressType" value="any" name="localAddressType" id="any-local-address">
<label for="any-local-address">任何IP地址</label>
</div>
<div class="checkbox-group">
<input type="radio" v-model="formData.localAddressType" value="specific" name="localAddressType" id="specific-local-address">
<label for="specific-local-address">这些IP地址:</label>
</div>
<textarea
v-model="formData.localAddresses"
class="form-textarea"
placeholder="192.168.1.1&#10;10.0.0.0/8&#10;172.16.0.0-172.31.255.255"
:disabled="formData.localAddressType !== 'specific'"
></textarea>
</div>
<div class="form-group">
<label class="form-label">远程IP地址</label>
<div class="checkbox-group">
<input type="radio" v-model="formData.remoteAddressType" value="any" name="remoteAddressType" id="any-remote-address">
<label for="any-remote-address">任何IP地址</label>
</div>
<div class="checkbox-group">
<input type="radio" v-model="formData.remoteAddressType" value="specific" name="remoteAddressType" id="specific-remote-address">
<label for="specific-remote-address">这些IP地址:</label>
</div>
<textarea
v-model="formData.remoteAddresses"
class="form-textarea"
placeholder="8.8.8.8&#10;192.168.0.0/16&#10;203.0.113.0-203.0.113.255"
:disabled="formData.remoteAddressType !== 'specific'"
></textarea>
</div>
</div>
<!-- 高级选项卡 -->
<div class="tab-content" :class="{ active: activeTab === 'advanced' }">
<div class="form-group">
<label class="form-label">配置文件</label>
<div class="checkbox-group">
<input type="checkbox" v-model="formData.profiles.domain" id="profile-domain">
<label for="profile-domain">域</label>
</div>
<div class="checkbox-group">
<input type="checkbox" v-model="formData.profiles.private" id="profile-private">
<label for="profile-private">专用</label>
</div>
<div class="checkbox-group">
<input type="checkbox" v-model="formData.profiles.public" id="profile-public">
<label for="profile-public">公用</label>
</div>
</div>
<div class="form-group">
<label class="form-label">边缘遍历</label>
<select v-model="formData.edgeTraversal" class="form-select">
<option value="no">阻止边缘遍历</option>
<option value="allow">允许边缘遍历</option>
<option value="defer">延迟到用户</option>
<option value="defer-app">延迟到应用程序</option>
</select>
</div>
<div class="form-group">
<label class="form-label">授权用户</label>
<div class="checkbox-group">
<input type="radio" v-model="formData.userType" value="any" name="userType" id="any-user">
<label for="any-user">任何用户</label>
</div>
<div class="checkbox-group">
<input type="radio" v-model="formData.userType" value="specific" name="userType" id="specific-users">
<label for="specific-users">仅这些用户:</label>
</div>
<input
type="text"
v-model="formData.authorizedUsers"
class="form-input"
placeholder="DOMAIN\Username, Everyone, ..."
:disabled="formData.userType !== 'specific'"
>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="closeModal">取消</button>
<button class="btn btn-success" @click="saveRule">
{{ editingRule ? '保存' : '创建' }}
</button>
</div>
</div>
</div>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
showModal: false,
editingRule: null,
activeTab: 'general',
searchText: '',
filterDirection: '',
filterAction: '',
filterStatus: '',
// 表单数据
formData: {
name: '',
description: '',
direction: 'inbound',
action: 'allow',
enabled: true,
programType: 'all',
programPath: '',
serviceType: 'all',
selectedServices: [],
protocolType: 'any',
protocolNumber: '',
localPortType: 'all',
localPorts: '',
remotePortType: 'all',
remotePorts: '',
localAddressType: 'any',
localAddresses: '',
remoteAddressType: 'any',
remoteAddresses: '',
profiles: {
domain: true,
private: true,
public: false
},
edgeTraversal: 'no',
userType: 'any',
authorizedUsers: ''
},
// 可用服务列表
availableServices: [
{ name: 'http', displayName: 'HTTP (80)' },
{ name: 'https', displayName: 'HTTPS (443)' },
{ name: 'ftp', displayName: 'FTP (21)' },
{ name: 'ssh', displayName: 'SSH (22)' },
{ name: 'telnet', displayName: 'Telnet (23)' },
{ name: 'smtp', displayName: 'SMTP (25)' },
{ name: 'dns', displayName: 'DNS (53)' },
{ name: 'dhcp', displayName: 'DHCP (67-68)' },
{ name: 'pop3', displayName: 'POP3 (110)' },
{ name: 'imap', displayName: 'IMAP (143)' },
{ name: 'snmp', displayName: 'SNMP (161)' },
{ name: 'ldap', displayName: 'LDAP (389)' },
{ name: 'smb', displayName: 'SMB (445)' },
{ name: 'rdp', displayName: 'RDP (3389)' }
],
// 防火墙规则数据
rules: [
{
id: 1,
name: 'Core Networking - DNS (UDP-Out)',
program: 'svchost.exe',
direction: 'outbound',
action: 'allow',
protocol: 'UDP',
ports: '53',
status: 'enabled',
scope: '任何'
},
{
id: 2,
name: 'Windows Remote Management (HTTP-In)',
program: 'svchost.exe',
direction: 'inbound',
action: 'allow',
protocol: 'TCP',
ports: '5985',
status: 'enabled',
scope: '本地子网'
},
{
id: 3,
name: 'File and Printer Sharing (SMB-In)',
program: 'System',
direction: 'inbound',
action: 'allow',
protocol: 'TCP',
ports: '445',
status: 'disabled',
scope: '专用'
},
{
id: 4,
name: 'Remote Desktop - User Mode (TCP-In)',
program: 'svchost.exe',
direction: 'inbound',
action: 'allow',
protocol: 'TCP',
ports: '3389',
status: 'enabled',
scope: '任何'
},
{
id: 5,
name: 'Block Suspicious Outbound',
program: 'malware.exe',
direction: 'outbound',
action: 'block',
protocol: 'TCP',
ports: '任何',
status: 'enabled',
scope: '任何'
},
{
id: 6,
name: 'Web Browser (HTTP/HTTPS-Out)',
program: 'chrome.exe',
direction: 'outbound',
action: 'allow',
protocol: 'TCP',
ports: '80,443',
status: 'enabled',
scope: '任何'
},
{
id: 7,
name: 'Email Client (SMTP-Out)',
program: 'outlook.exe',
direction: 'outbound',
action: 'allow',
protocol: 'TCP',
ports: '25,587',
status: 'disabled',
scope: '任何'
},
{
id: 8,
name: 'Windows Update (HTTP-Out)',
program: 'svchost.exe',
direction: 'outbound',
action: 'allow',
protocol: 'TCP',
ports: '80,443',
status: 'enabled',
scope: '任何'
}
]
}
},
computed: {
filteredRules() {
let filtered = this.rules;
if (this.searchText) {
filtered = filtered.filter(rule =>
rule.name.toLowerCase().includes(this.searchText.toLowerCase()) ||
rule.program.toLowerCase().includes(this.searchText.toLowerCase())
);
}
if (this.filterDirection) {
filtered = filtered.filter(rule => rule.direction === this.filterDirection);
}
if (this.filterAction) {
filtered = filtered.filter(rule => rule.action === this.filterAction);
}
if (this.filterStatus) {
filtered = filtered.filter(rule => rule.status === this.filterStatus);
}
return filtered;
},
totalRules() {
return this.rules.length;
},
enabledRules() {
return this.rules.filter(rule => rule.status === 'enabled').length;
},
disabledRules() {
return this.rules.filter(rule => rule.status === 'disabled').length;
}
},
methods: {
openModal(rule = null) {
this.editingRule = rule;
this.activeTab = 'general';
if (rule) {
// 编辑模式 - 填充表单数据
this.formData = {
name: rule.name,
description: '',
direction: rule.direction,
action: rule.action,
enabled: rule.status === 'enabled',
programType: 'all',
programPath: rule.program,
serviceType: 'all',
selectedServices: [],
protocolType: rule.protocol.toLowerCase(),
protocolNumber: '',
localPortType: 'specific',
localPorts: rule.ports,
remotePortType: 'all',
remotePorts: '',
localAddressType: 'any',
localAddresses: '',
remoteAddressType: 'any',
remoteAddresses: '',
profiles: {
domain: true,
private: true,
public: false
},
edgeTraversal: 'no',
userType: 'any',
authorizedUsers: ''
};
} else {
// 新增模式 - 重置表单
this.resetForm();
}
this.showModal = true;
},
closeModal() {
this.showModal = false;
this.editingRule = null;
this.resetForm();
},
resetForm() {
this.formData = {
name: '',
description: '',
direction: 'inbound',
action: 'allow',
enabled: true,
programType: 'all',
programPath: '',
serviceType: 'all',
selectedServices: [],
protocolType: 'any',
protocolNumber: '',
localPortType: 'all',
localPorts: '',
remotePortType: 'all',
remotePorts: '',
localAddressType: 'any',
localAddresses: '',
remoteAddressType: 'any',
remoteAddresses: '',
profiles: {
domain: true,
private: true,
public: false
},
edgeTraversal: 'no',
userType: 'any',
authorizedUsers: ''
};
},
saveRule() {
if (!this.formData.name.trim()) {
alert('请输入规则名称');
return;
}
const ruleData = {
name: this.formData.name,
program: this.formData.programType === 'specific' ? this.formData.programPath : 'System',
direction: this.formData.direction,
action: this.formData.action,
protocol: this.formData.protocolType.toUpperCase(),
ports: this.formData.localPortType === 'specific' ? this.formData.localPorts : '任何',
status: this.formData.enabled ? 'enabled' : 'disabled',
scope: '任何'
};
if (this.editingRule) {
// 编辑现有规则
const index = this.rules.findIndex(rule => rule.id === this.editingRule.id);
if (index !== -1) {
this.rules[index] = { ...this.rules[index], ...ruleData };
}
} else {
// 新增规则
const newRule = {
id: Math.max(...this.rules.map(r => r.id)) + 1,
...ruleData
};
this.rules.push(newRule);
}
this.closeModal();
},
editRule(rule) {
this.openModal(rule);
},
toggleRule(rule) {
rule.status = rule.status === 'enabled' ? 'disabled' : 'enabled';
},
deleteRule(ruleId) {
if (confirm('确定要删除这个规则吗?')) {
this.rules = this.rules.filter(rule => rule.id !== ruleId);
}
}
},
mounted() {
// 初始化Lucide图标
lucide.createIcons();
},
updated() {
// 更新时重新创建图标
lucide.createIcons();
}
}).mount('#app');
</script>
</body>
</html>
@@ -0,0 +1,1497 @@
<!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>
@@ -0,0 +1,1205 @@
<!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,1759 @@
<!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-color: #3b82f6;
--primary-hover: #2563eb;
--success-color: #10b981;
--warning-color: #f59e0b;
--error-color: #ef4444;
--bg-color: #ffffff;
--surface-color: #f8fafc;
--card-color: #ffffff;
--text-primary: #1f2937;
--text-secondary: #6b7280;
--border-color: #e5e7eb;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
[data-theme="dark"] {
--bg-color: #0f172a;
--surface-color: #1e293b;
--card-color: #334155;
--text-primary: #f1f5f9;
--text-secondary: #cbd5e1;
--border-color: #475569;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: var(--bg-color);
color: var(--text-primary);
line-height: 1.6;
transition: all 0.3s ease;
}
.app-container {
min-height: 100vh;
background-color: var(--surface-color);
}
.app-header {
background-color: var(--card-color);
border-bottom: 1px solid var(--border-color);
padding: 1rem 2rem;
box-shadow: var(--shadow);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.app-title {
font-size: 1.5rem;
font-weight: 600;
color: var(--text-primary);
}
.header-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.theme-toggle {
background: none;
border: 1px solid var(--border-color);
padding: 0.5rem;
border-radius: 0.5rem;
cursor: pointer;
color: var(--text-primary);
transition: all 0.2s ease;
}
.theme-toggle:hover {
background-color: var(--surface-color);
}
.main-content {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.firewall-status {
background-color: var(--card-color);
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: var(--shadow);
border: 1px solid var(--border-color);
}
.status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.status-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
}
.status-toggle {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.status-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: var(--success-color);
}
input:checked + .toggle-slider:before {
transform: translateX(26px);
}
.status-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.status-item {
text-align: center;
padding: 1rem;
background-color: var(--surface-color);
border-radius: 0.5rem;
border: 1px solid var(--border-color);
}
.status-value {
font-size: 1.5rem;
font-weight: 600;
color: var(--primary-color);
}
.status-label {
font-size: 0.875rem;
color: var(--text-secondary);
margin-top: 0.25rem;
}
.rules-section {
background-color: var(--card-color);
border-radius: 0.75rem;
padding: 1.5rem;
box-shadow: var(--shadow);
border: 1px solid var(--border-color);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.section-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
}
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
text-decoration: none;
transition: all 0.2s ease;
cursor: pointer;
border: none;
font-size: 0.875rem;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-hover);
}
.btn-secondary {
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background-color: var(--surface-color);
color: var(--text-primary);
}
.btn-danger {
background-color: var(--error-color);
color: white;
}
.btn-danger:hover {
background-color: #dc2626;
}
.filters-bar {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
align-items: center;
}
.filter-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
.filter-input {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
background-color: var(--bg-color);
color: var(--text-primary);
font-size: 0.875rem;
}
.filter-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.filter-select {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
background-color: var(--bg-color);
color: var(--text-primary);
font-size: 0.875rem;
cursor: pointer;
}
.rules-table {
width: 100%;
border-collapse: collapse;
margin-top: 1rem;
}
.rules-table th,
.rules-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.rules-table th {
font-weight: 600;
background-color: var(--surface-color);
color: var(--text-primary);
font-size: 0.875rem;
}
.rules-table tr:hover {
background-color: var(--surface-color);
}
.rule-name {
font-weight: 500;
color: var(--text-primary);
}
.rule-description {
color: var(--text-secondary);
font-size: 0.875rem;
margin-top: 0.25rem;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
}
.status-enabled {
background-color: rgba(16, 185, 129, 0.1);
color: var(--success-color);
}
.status-disabled {
background-color: rgba(239, 68, 68, 0.1);
color: var(--error-color);
}
.direction-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
}
.direction-inbound {
background-color: rgba(59, 130, 246, 0.1);
color: var(--primary-color);
}
.direction-outbound {
background-color: rgba(245, 158, 11, 0.1);
color: var(--warning-color);
}
.action-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
}
.action-allow {
background-color: rgba(16, 185, 129, 0.1);
color: var(--success-color);
}
.action-block {
background-color: rgba(239, 68, 68, 0.1);
color: var(--error-color);
}
.actions-menu {
position: relative;
display: inline-block;
}
.actions-btn {
background: none;
border: none;
padding: 0.5rem;
cursor: pointer;
color: var(--text-secondary);
border-radius: 0.25rem;
transition: all 0.2s ease;
}
.actions-btn:hover {
background-color: var(--surface-color);
color: var(--text-primary);
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
.modal {
background-color: var(--card-color);
border-radius: 0.75rem;
box-shadow: var(--shadow-lg);
max-width: 800px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
border: 1px solid var(--border-color);
}
.modal-header {
padding: 1.5rem;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--text-secondary);
padding: 0.25rem;
border-radius: 0.25rem;
transition: all 0.2s ease;
}
.modal-close:hover {
background-color: var(--surface-color);
color: var(--text-primary);
}
.modal-body {
padding: 1.5rem;
}
.modal-footer {
padding: 1.5rem;
border-top: 1px solid var(--border-color);
display: flex;
justify-content: flex-end;
gap: 1rem;
}
.form-section {
margin-bottom: 2rem;
}
.form-section-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-label {
font-weight: 500;
color: var(--text-primary);
font-size: 0.875rem;
}
.form-input {
padding: 0.75rem;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
background-color: var(--bg-color);
color: var(--text-primary);
font-size: 0.875rem;
transition: all 0.2s ease;
}
.form-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-textarea {
padding: 0.75rem;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
background-color: var(--bg-color);
color: var(--text-primary);
font-size: 0.875rem;
resize: vertical;
min-height: 80px;
}
.form-textarea:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-select {
padding: 0.75rem;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
background-color: var(--bg-color);
color: var(--text-primary);
font-size: 0.875rem;
cursor: pointer;
}
.form-select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-checkbox {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0;
}
.form-checkbox input {
width: 1rem;
height: 1rem;
accent-color: var(--primary-color);
}
.form-checkbox label {
font-size: 0.875rem;
color: var(--text-primary);
cursor: pointer;
}
.form-radio-group {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.form-radio {
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-radio input {
width: 1rem;
height: 1rem;
accent-color: var(--primary-color);
}
.form-radio label {
font-size: 0.875rem;
color: var(--text-primary);
cursor: pointer;
}
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 1.5rem;
}
.tab {
padding: 0.75rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
font-weight: 500;
color: var(--text-secondary);
transition: all 0.2s ease;
}
.tab.active {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
}
.tab:hover {
color: var(--text-primary);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.port-input-group {
display: flex;
gap: 0.5rem;
align-items: center;
}
.port-input-group input {
flex: 1;
}
.port-range-separator {
color: var(--text-secondary);
}
.file-picker {
display: flex;
gap: 0.5rem;
align-items: center;
}
.file-picker input {
flex: 1;
}
.file-picker button {
padding: 0.5rem 1rem;
background-color: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
cursor: pointer;
color: var(--text-primary);
font-size: 0.875rem;
transition: all 0.2s ease;
}
.file-picker button:hover {
background-color: var(--primary-color);
color: white;
}
.multi-select {
position: relative;
}
.multi-select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
background-color: var(--bg-color);
cursor: pointer;
transition: all 0.2s ease;
}
.multi-select-trigger:hover {
border-color: var(--primary-color);
}
.multi-select-tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.multi-select-tag {
background-color: var(--primary-color);
color: white;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
display: flex;
align-items: center;
gap: 0.25rem;
}
.multi-select-tag button {
background: none;
border: none;
color: white;
cursor: pointer;
padding: 0;
font-size: 0.875rem;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
margin-top: 2rem;
}
.pagination-btn {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
background-color: var(--bg-color);
color: var(--text-primary);
cursor: pointer;
font-size: 0.875rem;
transition: all 0.2s ease;
}
.pagination-btn:hover {
background-color: var(--primary-color);
color: white;
}
.pagination-btn.active {
background-color: var(--primary-color);
color: white;
}
.pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination-btn:disabled:hover {
background-color: var(--bg-color);
color: var(--text-primary);
}
.icon {
width: 1rem;
height: 1rem;
fill: currentColor;
}
.collapsible-section {
border: 1px solid var(--border-color);
border-radius: 0.5rem;
margin-bottom: 1rem;
overflow: hidden;
}
.collapsible-header {
padding: 1rem;
background-color: var(--surface-color);
border-bottom: 1px solid var(--border-color);
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.2s ease;
}
.collapsible-header:hover {
background-color: var(--border-color);
}
.collapsible-content {
padding: 1rem;
display: none;
}
.collapsible-content.active {
display: block;
}
.collapsible-toggle {
transition: transform 0.2s ease;
}
.collapsible-toggle.active {
transform: rotate(180deg);
}
@media (max-width: 768px) {
.main-content {
padding: 1rem;
}
.form-grid {
grid-template-columns: 1fr;
}
.filters-bar {
flex-direction: column;
align-items: stretch;
}
.filter-group {
flex-direction: column;
}
.modal {
width: 95%;
}
.rules-table {
font-size: 0.875rem;
}
.rules-table th,
.rules-table td {
padding: 0.5rem;
}
}
</style>
</head>
<body>
<div class="app-container">
<!-- Header -->
<header class="app-header">
<div class="header-content">
<h1 class="app-title">防火墙规则管理</h1>
<div class="header-actions">
<button class="theme-toggle" onclick="toggleTheme()">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
</svg>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="main-content">
<!-- Firewall Status -->
<div class="firewall-status">
<div class="status-header">
<h2 class="status-title">防火墙状态</h2>
<label class="status-toggle">
<input type="checkbox" checked>
<span class="toggle-slider"></span>
</label>
</div>
<div class="status-info">
<div class="status-item">
<div class="status-value">127</div>
<div class="status-label">总规则数</div>
</div>
<div class="status-item">
<div class="status-value">89</div>
<div class="status-label">已启用</div>
</div>
<div class="status-item">
<div class="status-value">38</div>
<div class="status-label">已禁用</div>
</div>
<div class="status-item">
<div class="status-value">12</div>
<div class="status-label">待审核</div>
</div>
</div>
</div>
<!-- Rules Management -->
<div class="rules-section">
<div class="section-header">
<h2 class="section-title">规则管理</h2>
<div style="display: flex; gap: 1rem;">
<button class="btn btn-secondary">
<svg class="icon" viewBox="0 0 24 24">
<path d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"/>
</svg>
导出规则
</button>
<button class="btn btn-primary" onclick="openModal('add-rule-modal')">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 4v16m8-8H4"/>
</svg>
新增规则
</button>
</div>
</div>
<!-- Filters -->
<div class="filters-bar">
<div class="filter-group">
<label>搜索</label>
<input type="text" class="filter-input" placeholder="规则名称或描述">
</div>
<div class="filter-group">
<label>状态</label>
<select class="filter-select">
<option value="">全部</option>
<option value="enabled">已启用</option>
<option value="disabled">已禁用</option>
</select>
</div>
<div class="filter-group">
<label>方向</label>
<select class="filter-select">
<option value="">全部</option>
<option value="inbound">入站</option>
<option value="outbound">出站</option>
</select>
</div>
<div class="filter-group">
<label>操作</label>
<select class="filter-select">
<option value="">全部</option>
<option value="allow">允许</option>
<option value="block">阻止</option>
</select>
</div>
</div>
<!-- Rules Table -->
<table class="rules-table">
<thead>
<tr>
<th>
<input type="checkbox">
</th>
<th>规则名称</th>
<th>状态</th>
<th>方向</th>
<th>操作</th>
<th>协议</th>
<th>端口</th>
<th>程序</th>
<th>优先级</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox"></td>
<td>
<div class="rule-name">Windows 远程桌面</div>
<div class="rule-description">允许远程桌面连接访问</div>
</td>
<td>
<span class="status-badge status-enabled">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
已启用
</span>
</td>
<td>
<span class="direction-badge direction-inbound">
<svg class="icon" viewBox="0 0 24 24">
<path d="M7 16l-4-4m0 0l4-4m-4 4h18"/>
</svg>
入站
</span>
</td>
<td>
<span class="action-badge action-allow">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
允许
</span>
</td>
<td>TCP</td>
<td>3389</td>
<td>任何</td>
<td>100</td>
<td>
<div class="actions-menu">
<button class="actions-btn" onclick="openModal('edit-rule-modal')">
<svg class="icon" viewBox="0 0 24 24">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
<button class="actions-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>
<div class="rule-name">HTTP 服务</div>
<div class="rule-description">允许 HTTP 服务对外访问</div>
</td>
<td>
<span class="status-badge status-enabled">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
已启用
</span>
</td>
<td>
<span class="direction-badge direction-inbound">
<svg class="icon" viewBox="0 0 24 24">
<path d="M7 16l-4-4m0 0l4-4m-4 4h18"/>
</svg>
入站
</span>
</td>
<td>
<span class="action-badge action-allow">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
允许
</span>
</td>
<td>TCP</td>
<td>80</td>
<td>任何</td>
<td>200</td>
<td>
<div class="actions-menu">
<button class="actions-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
<button class="actions-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>
<div class="rule-name">恶意软件阻止</div>
<div class="rule-description">阻止已知恶意软件的网络访问</div>
</td>
<td>
<span class="status-badge status-disabled">
<svg class="icon" viewBox="0 0 24 24">
<path d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
已禁用
</span>
</td>
<td>
<span class="direction-badge direction-outbound">
<svg class="icon" viewBox="0 0 24 24">
<path d="M17 8l4 4m0 0l-4 4m4-4H3"/>
</svg>
出站
</span>
</td>
<td>
<span class="action-badge action-block">
<svg class="icon" viewBox="0 0 24 24">
<path d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18.364 5.636M5.636 18.364l12.728-12.728"/>
</svg>
阻止
</span>
</td>
<td>任何</td>
<td>任何</td>
<td>C:\temp\malware.exe</td>
<td>50</td>
<td>
<div class="actions-menu">
<button class="actions-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
<button class="actions-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>
<div class="rule-name">SSH 管理访问</div>
<div class="rule-description">仅允许管理员网段的 SSH 访问</div>
</td>
<td>
<span class="status-badge status-enabled">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
已启用
</span>
</td>
<td>
<span class="direction-badge direction-inbound">
<svg class="icon" viewBox="0 0 24 24">
<path d="M7 16l-4-4m0 0l4-4m-4 4h18"/>
</svg>
入站
</span>
</td>
<td>
<span class="action-badge action-allow">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
允许
</span>
</td>
<td>TCP</td>
<td>22</td>
<td>任何</td>
<td>150</td>
<td>
<div class="actions-menu">
<button class="actions-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
<button class="actions-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>
<div class="rule-name">FTP 数据传输</div>
<div class="rule-description">允许 FTP 服务的数据传输</div>
</td>
<td>
<span class="status-badge status-enabled">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
已启用
</span>
</td>
<td>
<span class="direction-badge direction-inbound">
<svg class="icon" viewBox="0 0 24 24">
<path d="M7 16l-4-4m0 0l4-4m-4 4h18"/>
</svg>
入站
</span>
</td>
<td>
<span class="action-badge action-allow">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
允许
</span>
</td>
<td>TCP</td>
<td>21, 20000-20100</td>
<td>任何</td>
<td>300</td>
<td>
<div class="actions-menu">
<button class="actions-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
<button class="actions-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</td>
</tr>
</tbody>
</table>
<!-- Pagination -->
<div class="pagination">
<button class="pagination-btn" disabled>
<svg class="icon" viewBox="0 0 24 24">
<path d="M15 18l-6-6 6-6"/>
</svg>
</button>
<button class="pagination-btn active">1</button>
<button class="pagination-btn">2</button>
<button class="pagination-btn">3</button>
<button class="pagination-btn">4</button>
<button class="pagination-btn">5</button>
<button class="pagination-btn">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 18l6-6-6-6"/>
</svg>
</button>
</div>
</div>
</main>
<!-- Add/Edit Rule Modal -->
<div id="add-rule-modal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">新增防火墙规则</h3>
<button class="modal-close" onclick="closeModal('add-rule-modal')">×</button>
</div>
<div class="modal-body">
<!-- Tabs -->
<div class="tabs">
<div class="tab active" onclick="switchTab('basic-tab')">基本信息</div>
<div class="tab" onclick="switchTab('conditions-tab')">匹配条件</div>
<div class="tab" onclick="switchTab('network-tab')">网络属性</div>
<div class="tab" onclick="switchTab('security-tab')">安全控制</div>
</div>
<!-- Basic Information Tab -->
<div id="basic-tab" class="tab-content active">
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
基本信息
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">规则名称 *</label>
<input type="text" class="form-input" placeholder="输入规则名称" required>
</div>
<div class="form-group">
<label class="form-label">优先级</label>
<input type="number" class="form-input" value="100" min="1" max="1000">
</div>
<div class="form-group">
<label class="form-label">描述</label>
<textarea class="form-textarea" placeholder="输入规则描述"></textarea>
</div>
<div class="form-group">
<label class="form-label">所属策略组</label>
<select class="form-select">
<option value="">默认策略组</option>
<option value="web-server">Web 服务器</option>
<option value="database">数据库服务</option>
<option value="management">管理服务</option>
</select>
</div>
</div>
</div>
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
规则配置
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">规则状态</label>
<div class="form-checkbox">
<input type="checkbox" id="rule-enabled" checked>
<label for="rule-enabled">启用此规则</label>
</div>
</div>
<div class="form-group">
<label class="form-label">方向</label>
<div class="form-radio-group">
<div class="form-radio">
<input type="radio" id="direction-inbound" name="direction" value="inbound" checked>
<label for="direction-inbound">入站</label>
</div>
<div class="form-radio">
<input type="radio" id="direction-outbound" name="direction" value="outbound">
<label for="direction-outbound">出站</label>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">操作</label>
<div class="form-radio-group">
<div class="form-radio">
<input type="radio" id="action-allow" name="action" value="allow" checked>
<label for="action-allow">允许</label>
</div>
<div class="form-radio">
<input type="radio" id="action-block" name="action" value="block">
<label for="action-block">阻止</label>
</div>
<div class="form-radio">
<input type="radio" id="action-ask" name="action" value="ask">
<label for="action-ask">询问</label>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">记录日志</label>
<div class="form-checkbox">
<input type="checkbox" id="log-enabled">
<label for="log-enabled">启用日志记录</label>
</div>
</div>
</div>
</div>
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/>
</svg>
自定义标签
</h4>
<div class="form-group">
<label class="form-label">标签</label>
<div class="multi-select">
<div class="multi-select-trigger">
<div class="multi-select-tags">
<div class="multi-select-tag">
Web服务
<button type="button">×</button>
</div>
<div class="multi-select-tag">
高优先级
<button type="button">×</button>
</div>
</div>
<svg class="icon" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"/>
</svg>
</div>
</div>
</div>
</div>
</div>
<!-- Matching Conditions Tab -->
<div id="conditions-tab" class="tab-content">
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
程序和服务
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">程序路径</label>
<div class="file-picker">
<input type="text" class="form-input" placeholder="选择程序路径或留空表示任何程序">
<button type="button">浏览</button>
</div>
</div>
<div class="form-group">
<label class="form-label">服务</label>
<div class="multi-select">
<div class="multi-select-trigger">
<div class="multi-select-tags">
<div class="multi-select-tag">
HTTP
<button type="button">×</button>
</div>
<div class="multi-select-tag">
HTTPS
<button type="button">×</button>
</div>
</div>
<svg class="icon" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"/>
</svg>
</div>
</div>
</div>
</div>
</div>
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"/>
</svg>
协议和端口
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">协议</label>
<select class="form-select">
<option value="any">任意</option>
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
<option value="icmpv4">ICMPv4</option>
<option value="icmpv6">ICMPv6</option>
</select>
</div>
<div class="form-group">
<label class="form-label">本地端口</label>
<div class="port-input-group">
<input type="text" class="form-input" placeholder="起始端口">
<span class="port-range-separator">-</span>
<input type="text" class="form-input" placeholder="结束端口">
</div>
</div>
<div class="form-group">
<label class="form-label">远程端口</label>
<div class="port-input-group">
<input type="text" class="form-input" placeholder="起始端口">
<span class="port-range-separator">-</span>
<input type="text" class="form-input" placeholder="结束端口">
</div>
</div>
</div>
</div>
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
IP 地址
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">本地地址</label>
<input type="text" class="form-input" placeholder="IP 地址或 CIDR,留空表示任何地址">
</div>
<div class="form-group">
<label class="form-label">远程地址</label>
<input type="text" class="form-input" placeholder="IP 地址或 CIDR,留空表示任何地址">
</div>
</div>
</div>
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
用户和用户组
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">用户</label>
<div class="multi-select">
<div class="multi-select-trigger">
<div class="multi-select-tags">
<div class="multi-select-tag">
Administrators
<button type="button">×</button>
</div>
</div>
<svg class="icon" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"/>
</svg>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">用户组</label>
<div class="multi-select">
<div class="multi-select-trigger">
<div class="multi-select-tags">
<div class="multi-select-tag">
Power Users
<button type="button">×</button>
</div>
</div>
<svg class="icon" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Network Properties Tab -->
<div id="network-tab" class="tab-content">
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"/>
</svg>
网络配置文件
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">配置文件</label>
<div class="form-checkbox">
<input type="checkbox" id="profile-domain" checked>
<label for="profile-domain">域</label>
</div>
<div class="form-checkbox">
<input type="checkbox" id="profile-private" checked>
<label for="profile-private">专用</label>
</div>
<div class="form-checkbox">
<input type="checkbox" id="profile-public">
<label for="profile-public">公用</label>
</div>
</div>
</div>
</div>
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M8 16l2.879-2.879m0 0a3 3 0 104.243-4.242 3 3 0 00-4.243 4.242zM21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
接口类型
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">接口类型</label>
<div class="form-checkbox">
<input type="checkbox" id="interface-wireless" checked>
<label for="interface-wireless">无线</label>
</div>
<div class="form-checkbox">
<input type="checkbox" id="interface-lan" checked>
<label for="interface-lan">局域网</label>
</div>
<div class="form-checkbox">
<input type="checkbox" id="interface-ras">
<label for="interface-ras">远程访问</label>
</div>
</div>
</div>
</div>
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"/>
</svg>
作用范围
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">作用范围</label>
<div class="form-radio-group">
<div class="form-radio">
<input type="radio" id="scope-all" name="scope" value="all" checked>
<label for="scope-all">全部</label>
</div>
<div class="form-radio">
<input type="radio" id="scope-subnet" name="scope" value="subnet">
<label for="scope-subnet">子网</label>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">子网设置</label>
<input type="text" class="form-input" placeholder="192.168.1.0/24" disabled>
</div>
</div>
</div>
</div>
<!-- Security Control Tab -->
<div id="security-tab" class="tab-content">
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
安全连接
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">连接安全</label>
<div class="form-checkbox">
<input type="checkbox" id="require-secure">
<label for="require-secure">要求安全连接</label>
</div>
</div>
<div class="form-group">
<label class="form-label">IPsec 策略</label>
<select class="form-select">
<option value="">无</option>
<option value="request">请求身份验证</option>
<option value="require">需要身份验证</option>
<option value="require-encrypt">需要加密</option>
</select>
</div>
</div>
</div>
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
日志记录
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">日志级别</label>
<select class="form-select">
<option value="none">不记录</option>
<option value="basic">基本</option>
<option value="detailed">详细</option>
<option value="debug">调试</option>
</select>
</div>
<div class="form-group">
<label class="form-label">日志选项</label>
<div class="form-checkbox">
<input type="checkbox" id="log-success">
<label for="log-success">记录成功连接</label>
</div>
<div class="form-checkbox">
<input type="checkbox" id="log-failure" checked>
<label for="log-failure">记录失败连接</label>
</div>
<div class="form-checkbox">
<input type="checkbox" id="log-dropped" checked>
<label for="log-dropped">记录丢弃的数据包</label>
</div>
</div>
</div>
</div>
<div class="form-section">
<h4 class="form-section-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
高级选项
</h4>
<div class="form-grid">
<div class="form-group">
<label class="form-label">边缘遍历</label>
<select class="form-select">
<option value="block">阻止</option>
<option value="allow">允许</option>
<option value="defer-to-app">延迟到应用程序</option>
<option value="defer-to-user">延迟到用户</option>
</select>
</div>
<div class="form-group">
<label class="form-label">其他选项</label>
<div class="form-checkbox">
<input type="checkbox" id="allow-bypass">
<label for="allow-bypass">允许绕过</label>
</div>
<div class="form-checkbox">
<input type="checkbox" id="defer-authorization">
<label for="defer-authorization">延迟授权</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('add-rule-modal')">取消</button>
<button class="btn btn-primary">保存规则</button>
</div>
</div>
</div>
<!-- Edit Rule Modal -->
<div id="edit-rule-modal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">编辑防火墙规则</h3>
<button class="modal-close" onclick="closeModal('edit-rule-modal')">×</button>
</div>
<div class="modal-body">
<p>编辑规则表单内容与新增规则相同...</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('edit-rule-modal')">取消</button>
<button class="btn btn-primary">保存更改</button>
</div>
</div>
</div>
</div>
<script>
// Theme toggle functionality
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
// Load saved theme
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
// Modal functionality
function openModal(modalId) {
document.getElementById(modalId).classList.add('active');
}
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
// Tab switching
function switchTab(tabId) {
// Remove active class from all tabs and contents
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 corresponding content
event.target.classList.add('active');
document.getElementById(tabId).classList.add('active');
}
// Close modal when clicking outside
document.addEventListener('click', function(event) {
if (event.target.classList.contains('modal-overlay')) {
event.target.classList.remove('active');
}
});
</script>
</body>
</html>