A:增加防火墙UI设计
This commit is contained in:
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()">×</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.0.0.0/8 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 192.168.0.0/16 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()">×</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>
|
||||
Reference in new issue
Block a user