1497 lines
52 KiB
HTML
1497 lines
52 KiB
HTML
<!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> |