DUI
This commit is contained in:
1 parent
ed77712bfc
commit
c5108ac91e
2 files changed
-2127
No files matched your search
@@ -1,922 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>防火墙管理中心</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #2563eb;
|
||||
--primary-dark: #1d4ed8;
|
||||
--secondary: #64748b;
|
||||
--success: #22c55e;
|
||||
--danger: #ef4444;
|
||||
--warning: #f59e0b;
|
||||
--background: #0f172a;
|
||||
--surface: #1e293b;
|
||||
--surface-light: #334155;
|
||||
--text: #f1f5f9;
|
||||
--text-secondary: #94a3b8;
|
||||
--border: #475569;
|
||||
--shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--background);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: var(--surface);
|
||||
padding: 20px 30px;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.stat-icon.active {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.stat-icon.total {
|
||||
background: rgba(37, 99, 235, 0.2);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.stat-icon.blocked {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 12px 20px 12px 45px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.search-box::before {
|
||||
content: "🔍";
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%;
|
||||
}
|
||||
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px -10px rgba(37, 99, 235, 0.5);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--surface-light);
|
||||
}
|
||||
|
||||
.btn-secondary.active {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
/* Rules Table */
|
||||
.rules-container {
|
||||
background: var(--surface);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.rules-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.rules-table th {
|
||||
background: var(--surface-light);
|
||||
padding: 16px 20px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.rules-table td {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid rgba(71, 85, 105, 0.3);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.rules-table tr:hover {
|
||||
background: rgba(37, 99, 235, 0.05);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rules-table tr:hover td {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.rule-name {
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.rule-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-enabled {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.status-disabled {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.direction-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.direction-in {
|
||||
background: rgba(37, 99, 235, 0.2);
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.direction-out {
|
||||
background: rgba(245, 158, 11, 0.2);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
z-index: 1000;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
.modal.active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--surface);
|
||||
border-radius: 20px;
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid var(--border);
|
||||
animation: slideUp 0.3s;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 30px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: var(--surface-light);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: var(--danger);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.form-section h3 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
background: var(--background);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
select.form-control {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio-group, .checkbox-group {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.radio-item, .checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio-item input[type="radio"],
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 12px 24px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.port-range {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.service-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.service-item {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.service-item:hover {
|
||||
background: var(--surface-light);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 20px 30px;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(50px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--surface-light);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--border);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<h1>防火墙管理中心</h1>
|
||||
<p style="color: var(--text-secondary);">实时监控和管理网络安全规则</p>
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon active">✓</div>
|
||||
<div>
|
||||
<div style="font-size: 24px; font-weight: 600;">42</div>
|
||||
<div style="font-size: 12px; color: var(--text-secondary);">活动规则</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon total">🛡️</div>
|
||||
<div>
|
||||
<div style="font-size: 24px; font-weight: 600;">68</div>
|
||||
<div style="font-size: 12px; color: var(--text-secondary);">总规则数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon blocked">🚫</div>
|
||||
<div>
|
||||
<div style="font-size: 24px; font-weight: 600;">1,247</div>
|
||||
<div style="font-size: 12px; color: var(--text-secondary);">今日阻止</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="controls">
|
||||
<div class="search-box">
|
||||
<input type="text" placeholder="搜索规则名称、程序、端口...">
|
||||
</div>
|
||||
<div class="filter-buttons">
|
||||
<button class="btn btn-secondary active" onclick="filterRules('all')">全部</button>
|
||||
<button class="btn btn-secondary" onclick="filterRules('inbound')">入站</button>
|
||||
<button class="btn btn-secondary" onclick="filterRules('outbound')">出站</button>
|
||||
<button class="btn btn-primary" onclick="openModal()">
|
||||
<span>+</span> 新建规则
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rules Table -->
|
||||
<div class="rules-container">
|
||||
<table class="rules-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>状态</th>
|
||||
<th>方向</th>
|
||||
<th>程序</th>
|
||||
<th>协议/端口</th>
|
||||
<th>作用域</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="rulesTableBody">
|
||||
<!-- Rules will be populated here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal" id="ruleModal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="modalTitle">新建防火墙规则</h2>
|
||||
<button class="close-btn" onclick="closeModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Basic Settings -->
|
||||
<div class="form-section">
|
||||
<h3>🔧 基本设置</h3>
|
||||
<div class="form-group">
|
||||
<label>规则名称</label>
|
||||
<input type="text" class="form-control" id="ruleName" placeholder="输入规则名称">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>描述(可选)</label>
|
||||
<input type="text" class="form-control" id="ruleDescription" placeholder="输入规则描述">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>方向</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="direction" value="inbound" checked>
|
||||
<span>入站</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="direction" value="outbound">
|
||||
<span>出站</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>操作</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="action" value="allow" checked>
|
||||
<span>允许连接</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="action" value="block">
|
||||
<span>阻止连接</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="action" value="secure">
|
||||
<span>允许安全连接</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Program Settings -->
|
||||
<div class="form-section">
|
||||
<h3>📱 程序设置</h3>
|
||||
<div class="tabs">
|
||||
<button class="tab active" onclick="switchTab('program', 'allPrograms')">所有程序</button>
|
||||
<button class="tab" onclick="switchTab('program', 'specificProgram')">指定程序</button>
|
||||
<button class="tab" onclick="switchTab('program', 'service')">服务</button>
|
||||
</div>
|
||||
<div class="tab-content active" id="allPrograms">
|
||||
<p style="color: var(--text-secondary);">此规则适用于所有程序</p>
|
||||
</div>
|
||||
<div class="tab-content" id="specificProgram">
|
||||
<div class="form-group">
|
||||
<label>程序路径</label>
|
||||
<input type="text" class="form-control" placeholder="浏览或输入程序路径">
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content" id="service">
|
||||
<div class="form-group">
|
||||
<label>选择服务</label>
|
||||
<div class="service-list">
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service1">
|
||||
<label for="service1">Windows Update</label>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service2">
|
||||
<label for="service2">远程桌面服务</label>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service3">
|
||||
<label for="service3">文件和打印机共享</label>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service4">
|
||||
<label for="service4">SQL Server</label>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service5">
|
||||
<label for="service5">IIS</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Protocol and Ports -->
|
||||
<div class="form-section">
|
||||
<h3>🔌 协议和端口</h3>
|
||||
<div class="form-group">
|
||||
<label>协议类型</label>
|
||||
<select class="form-control" id="protocol" onchange="updatePortOptions()">
|
||||
<option value="any">任意</option>
|
||||
<option value="tcp">TCP</option>
|
||||
<option value="udp">UDP</option>
|
||||
<option value="icmpv4">ICMPv4</option>
|
||||
<option value="icmpv6">ICMPv6</option>
|
||||
<option value="custom">自定义</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="portSettings" style="display: none;">
|
||||
<label>端口</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="portType" value="all" checked>
|
||||
<span>所有端口</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="portType" value="specific">
|
||||
<span>特定端口</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="port-range" style="margin-top: 10px; display: none;" id="portRange">
|
||||
<input type="text" class="form-control" placeholder="端口或范围 (如: 80, 443, 1000-2000)" style="flex: 1;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scope -->
|
||||
<div class="form-section">
|
||||
<h3>🌐 作用域</h3>
|
||||
<div class="form-group">
|
||||
<label>本地IP地址</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="localIP" value="any" checked>
|
||||
<span>任何IP地址</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="localIP" value="specific">
|
||||
<span>指定IP地址</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>远程IP地址</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="remoteIP" value="any" checked>
|
||||
<span>任何IP地址</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="remoteIP" value="specific">
|
||||
<span>指定IP地址</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced -->
|
||||
<div class="form-section">
|
||||
<h3>⚙️ 高级设置</h3>
|
||||
<div class="form-group">
|
||||
<label>配置文件</label>
|
||||
<div class="checkbox-group">
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" checked>
|
||||
<span>域</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" checked>
|
||||
<span>专用</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" checked>
|
||||
<span>公用</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>用户和组</label>
|
||||
<button class="btn btn-secondary">选择用户或组...</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeModal()">取消</button>
|
||||
<button class="btn btn-primary" onclick="saveRule()">保存规则</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Sample data
|
||||
const rules = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Chrome 浏览器",
|
||||
status: "enabled",
|
||||
direction: "outbound",
|
||||
program: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
||||
protocol: "TCP",
|
||||
port: "80, 443",
|
||||
scope: "任意",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "远程桌面 - 用户模式 (TCP-In)",
|
||||
status: "enabled",
|
||||
direction: "inbound",
|
||||
program: "系统",
|
||||
protocol: "TCP",
|
||||
port: "3389",
|
||||
scope: "任意",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "文件和打印机共享 (SMB-In)",
|
||||
status: "disabled",
|
||||
direction: "inbound",
|
||||
program: "系统",
|
||||
protocol: "TCP",
|
||||
port: "445",
|
||||
scope: "本地子网",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Windows Update",
|
||||
status: "enabled",
|
||||
direction: "outbound",
|
||||
program: "svchost.exe",
|
||||
protocol: "TCP/UDP",
|
||||
port: "80, 443",
|
||||
scope: "任意",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "SQL Server",
|
||||
status: "enabled",
|
||||
direction: "inbound",
|
||||
program: "sqlservr.exe",
|
||||
protocol: "TCP",
|
||||
port: "1433",
|
||||
scope: "指定IP",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "阻止可疑端口",
|
||||
status: "enabled",
|
||||
direction: "inbound",
|
||||
program: "所有程序",
|
||||
protocol: "TCP",
|
||||
port: "135, 139, 445",
|
||||
scope: "任意",
|
||||
action: "阻止"
|
||||
}
|
||||
];
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
renderRules(rules);
|
||||
}
|
||||
|
||||
// Render rules table
|
||||
function renderRules(rulesToRender) {
|
||||
const tbody = document.getElementById('rulesTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
rulesToRender.forEach(rule => {
|
||||
const row = document.createElement('tr');
|
||||
row.onclick = () => editRule(rule);
|
||||
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<div class="rule-name">
|
||||
<div class="rule-icon">${rule.direction === 'inbound' ? '↓' : '↑'}</div>
|
||||
<span>${rule.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-badge status-${rule.status}">
|
||||
${rule.status === 'enabled' ? '已启用' : '已禁用'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="direction-badge direction-${rule.direction === 'inbound' ? 'in' : 'out'}">
|
||||
${rule.direction === 'inbound' ? '入站' : '出站'}
|
||||
</span>
|
||||
</td>
|
||||
<td>${rule.program.split('\\').pop()}</td>
|
||||
<td>${rule.protocol}${rule.port !== '任意' ? ': ' + rule.port : ''}</td>
|
||||
<td>${rule.scope}</td>
|
||||
<td>${rule.action}</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter rules
|
||||
function filterRules(type) {
|
||||
const buttons = document.querySelectorAll('.filter-buttons .btn-secondary');
|
||||
buttons.forEach(btn => btn.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
|
||||
let filtered = rules;
|
||||
if (type === 'inbound') {
|
||||
filtered = rules.filter(r => r.direction === 'inbound');
|
||||
} else if (type === 'outbound') {
|
||||
filtered = rules.filter(r => r.direction === 'outbound');
|
||||
}
|
||||
|
||||
renderRules(filtered);
|
||||
}
|
||||
|
||||
// Search functionality
|
||||
document.querySelector('.search-box input').addEventListener('input', function(e
|
||||
@@ -1,1205 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>防火墙管理看板</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 25px;
|
||||
padding: 10px 20px;
|
||||
min-width: 300px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.search-box i {
|
||||
color: #666;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 25px;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.6);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #333;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: white;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table thead {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 15px 20px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.table th {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.table tbody tr {
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.table tbody tr:hover {
|
||||
background-color: #f8f9ff;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 5px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-enabled {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-disabled {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.action-badge {
|
||||
padding: 5px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.action-allow {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.action-block {
|
||||
background: #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.direction-badge {
|
||||
padding: 5px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.direction-inbound {
|
||||
background: #ffeaa7;
|
||||
color: #d68910;
|
||||
}
|
||||
|
||||
.direction-outbound {
|
||||
background: #fab1a0;
|
||||
color: #e17055;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(5px);
|
||||
z-index: 1000;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
max-width: 800px;
|
||||
width: 90%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.3);
|
||||
transform: scale(0.8);
|
||||
animation: modalScale 0.3s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes modalScale {
|
||||
to { transform: scale(1); }
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #eee;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 2rem;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 25px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-group.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-select,
|
||||
.form-textarea {
|
||||
padding: 12px 15px;
|
||||
border: 2px solid #eee;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-select:focus,
|
||||
.form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.checkbox-group input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: #667eea;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 15px;
|
||||
padding-top: 20px;
|
||||
border-top: 2px solid #eee;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background: #138496;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #eee;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 12px 25px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
border-bottom: 3px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: #667eea;
|
||||
border-bottom-color: #667eea;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<h1>🛡️ 防火墙管理中心</h1>
|
||||
<p>监控和管理网络安全策略,保护您的系统免受恶意攻击</p>
|
||||
|
||||
<!-- Statistics -->
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">127</div>
|
||||
<div class="stat-label">活跃规则</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">89</div>
|
||||
<div class="stat-label">入站规则</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">38</div>
|
||||
<div class="stat-label">出站规则</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">15</div>
|
||||
<div class="stat-label">阻止的连接</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="controls">
|
||||
<div class="search-box">
|
||||
<span>🔍</span>
|
||||
<input type="text" placeholder="搜索防火墙规则..." id="searchInput">
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-primary" onclick="openModal('add')">
|
||||
➕ 新建规则
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="refreshRules()">
|
||||
🔄 刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rules Table -->
|
||||
<div class="table-container">
|
||||
<table class="table" id="rulesTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>方向</th>
|
||||
<th>操作</th>
|
||||
<th>协议</th>
|
||||
<th>端口</th>
|
||||
<th>程序</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="rulesTableBody">
|
||||
<!-- 动态生成的内容 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal" id="ruleModal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="modalTitle">新建防火墙规则</h2>
|
||||
<button class="close-btn" onclick="closeModal()">×</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>
|
||||
Reference in new issue
Block a user