A:UI设置重新布局
This commit is contained in:
1 parent
c5108ac91e
commit
60d6941074
11 files changed
-1497
No files matched your search
@@ -1,1497 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>防火墙管理中心</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #2563eb;
|
||||
--primary-dark: #1d4ed8;
|
||||
--secondary: #64748b;
|
||||
--success: #22c55e;
|
||||
--danger: #ef4444;
|
||||
--warning: #f59e0b;
|
||||
--background: #0f172a;
|
||||
--surface: #1e293b;
|
||||
--surface-light: #334155;
|
||||
--text: #f1f5f9;
|
||||
--text-secondary: #94a3b8;
|
||||
--border: #475569;
|
||||
--shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--background);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: var(--surface);
|
||||
padding: 20px 30px;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--border);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--primary), var(--success), var(--warning));
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
background: rgba(37, 99, 235, 0.05);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(37, 99, 235, 0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.stat-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px -8px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.stat-icon.active {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: var(--success);
|
||||
box-shadow: 0 0 20px rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
.stat-icon.total {
|
||||
background: rgba(37, 99, 235, 0.2);
|
||||
color: var(--primary);
|
||||
box-shadow: 0 0 20px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.stat-icon.blocked {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: var(--danger);
|
||||
box-shadow: 0 0 20px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 12px 20px 12px 45px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.search-box::before {
|
||||
content: "🔍";
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
.btn:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(37, 99, 235, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--surface-light);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-secondary.active {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 4px 15px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
/* Rules Table */
|
||||
.rules-container {
|
||||
background: var(--surface);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.rules-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.rules-table th {
|
||||
background: var(--surface-light);
|
||||
padding: 16px 20px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.rules-table td {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid rgba(71, 85, 105, 0.3);
|
||||
font-size: 14px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.rules-table tr {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.rules-table tr:hover {
|
||||
background: linear-gradient(90deg, rgba(37, 99, 235, 0.05), rgba(37, 99, 235, 0.1));
|
||||
cursor: pointer;
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.rules-table tr:hover td {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.rule-name {
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.rule-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
background: linear-gradient(135deg, rgba(37, 99, 235, 0.1), rgba(37, 99, 235, 0.2));
|
||||
border: 1px solid rgba(37, 99, 235, 0.2);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
display: inline-block;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-enabled {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: var(--success);
|
||||
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
.status-disabled {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: var(--danger);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.direction-badge {
|
||||
padding: 6px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.direction-in {
|
||||
background: rgba(37, 99, 235, 0.2);
|
||||
color: #60a5fa;
|
||||
border-color: rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.direction-out {
|
||||
background: rgba(245, 158, 11, 0.2);
|
||||
color: #fbbf24;
|
||||
border-color: rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.action-btn.edit {
|
||||
background: rgba(37, 99, 235, 0.2);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
z-index: 1000;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
.modal.active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--surface);
|
||||
border-radius: 20px;
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7);
|
||||
border: 1px solid var(--border);
|
||||
animation: slideUp 0.3s;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 30px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, var(--surface), var(--surface-light));
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #60a5fa, #3b82f6);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
background: var(--surface-light);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: var(--danger);
|
||||
transform: scale(1.1) rotate(90deg);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 25px;
|
||||
background: rgba(37, 99, 235, 0.03);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.form-section h3 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid rgba(37, 99, 235, 0.2);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
background: var(--background);
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 10px;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
select.form-control {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio-group, .checkbox-group {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.radio-item, .checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.radio-item:hover, .checkbox-item:hover {
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.radio-item input[type="radio"],
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid var(--border);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 12px 24px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: var(--text);
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: var(--primary);
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: var(--primary);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
padding: 20px;
|
||||
background: var(--background);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.port-range {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.service-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.service-item {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.service-item:hover {
|
||||
background: var(--surface-light);
|
||||
border-color: var(--primary);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 20px 30px;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 15px;
|
||||
background: rgba(37, 99, 235, 0.02);
|
||||
}
|
||||
|
||||
/* Notification */
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 15px 20px;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
z-index: 2000;
|
||||
animation: slideInRight 0.3s;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
.notification.info {
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(50px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--surface);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--surface-light);
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.filter-buttons {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rules-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rules-table th,
|
||||
.rules-table td {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<h1>防火墙管理中心</h1>
|
||||
<p style="color: var(--text-secondary);">实时监控和管理网络安全规则</p>
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon active">✓</div>
|
||||
<div>
|
||||
<div style="font-size: 24px; font-weight: 600;" id="activeRules">42</div>
|
||||
<div style="font-size: 12px; color: var(--text-secondary);">活动规则</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon total">🛡️</div>
|
||||
<div>
|
||||
<div style="font-size: 24px; font-weight: 600;" id="totalRules">68</div>
|
||||
<div style="font-size: 12px; color: var(--text-secondary);">总规则数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon blocked">🚫</div>
|
||||
<div>
|
||||
<div style="font-size: 24px; font-weight: 600;" id="blockedToday">1,247</div>
|
||||
<div style="font-size: 12px; color: var(--text-secondary);">今日阻止</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="controls">
|
||||
<div class="search-box">
|
||||
<input type="text" id="searchInput" placeholder="搜索规则名称、程序、端口...">
|
||||
</div>
|
||||
<div class="filter-buttons">
|
||||
<button class="btn btn-secondary active" onclick="filterRules('all')">全部</button>
|
||||
<button class="btn btn-secondary" onclick="filterRules('inbound')">入站</button>
|
||||
<button class="btn btn-secondary" onclick="filterRules('outbound')">出站</button>
|
||||
<button class="btn btn-primary" onclick="openModal()">
|
||||
<span>+</span> 新建规则
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rules Table -->
|
||||
<div class="rules-container">
|
||||
<table class="rules-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>状态</th>
|
||||
<th>方向</th>
|
||||
<th>程序</th>
|
||||
<th>协议/端口</th>
|
||||
<th>作用域</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="rulesTableBody">
|
||||
<!-- Rules will be populated here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal" id="ruleModal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="modalTitle">新建防火墙规则</h2>
|
||||
<button class="close-btn" onclick="closeModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="ruleForm">
|
||||
<!-- Basic Settings -->
|
||||
<div class="form-section">
|
||||
<h3>🔧 基本设置</h3>
|
||||
<div class="form-group">
|
||||
<label>规则名称 *</label>
|
||||
<input type="text" class="form-control" id="ruleName" placeholder="输入规则名称" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>描述(可选)</label>
|
||||
<input type="text" class="form-control" id="ruleDescription" placeholder="输入规则描述">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>方向</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="direction" value="inbound" checked>
|
||||
<span>入站</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="direction" value="outbound">
|
||||
<span>出站</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>操作</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="action" value="allow" checked>
|
||||
<span>允许连接</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="action" value="block">
|
||||
<span>阻止连接</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="action" value="secure">
|
||||
<span>允许安全连接</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Program Settings -->
|
||||
<div class="form-section">
|
||||
<h3>📱 程序设置</h3>
|
||||
<div class="tabs">
|
||||
<button type="button" class="tab active" onclick="switchTab('program', 'allPrograms')">所有程序</button>
|
||||
<button type="button" class="tab" onclick="switchTab('program', 'specificProgram')">指定程序</button>
|
||||
<button type="button" class="tab" onclick="switchTab('program', 'service')">服务</button>
|
||||
</div>
|
||||
<div class="tab-content active" id="allPrograms">
|
||||
<p style="color: var(--text-secondary);">此规则适用于所有程序</p>
|
||||
</div>
|
||||
<div class="tab-content" id="specificProgram">
|
||||
<div class="form-group">
|
||||
<label>程序路径</label>
|
||||
<input type="text" class="form-control" id="programPath" placeholder="浏览或输入程序路径">
|
||||
<button type="button" class="btn btn-secondary" style="margin-top: 10px;" onclick="browseProgramPath()">浏览...</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content" id="service">
|
||||
<div class="form-group">
|
||||
<label>选择服务</label>
|
||||
<div class="service-list">
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service1" value="windows_update">
|
||||
<label for="service1">Windows Update</label>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service2" value="remote_desktop">
|
||||
<label for="service2">远程桌面服务</label>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service3" value="file_sharing">
|
||||
<label for="service3">文件和打印机共享</label>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service4" value="sql_server">
|
||||
<label for="service4">SQL Server</label>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<input type="checkbox" id="service5" value="iis">
|
||||
<label for="service5">IIS</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Protocol and Ports -->
|
||||
<div class="form-section">
|
||||
<h3>🔌 协议和端口</h3>
|
||||
<div class="form-group">
|
||||
<label>协议类型</label>
|
||||
<select class="form-control" id="protocol" onchange="updatePortOptions()">
|
||||
<option value="any">任意</option>
|
||||
<option value="tcp">TCP</option>
|
||||
<option value="udp">UDP</option>
|
||||
<option value="icmpv4">ICMPv4</option>
|
||||
<option value="icmpv6">ICMPv6</option>
|
||||
<option value="custom">自定义</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="portSettings" style="display: none;">
|
||||
<label>端口</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="portType" value="all" checked onchange="togglePortRange()">
|
||||
<span>所有端口</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="portType" value="specific" onchange="togglePortRange()">
|
||||
<span>特定端口</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="port-range" style="margin-top: 10px; display: none;" id="portRange">
|
||||
<input type="text" class="form-control" id="portInput" placeholder="端口或范围 (如: 80, 443, 1000-2000)" style="flex: 1;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scope -->
|
||||
<div class="form-section">
|
||||
<h3>🌐 作用域</h3>
|
||||
<div class="form-group">
|
||||
<label>本地IP地址</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="localIP" value="any" checked onchange="toggleLocalIP()">
|
||||
<span>任何IP地址</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="localIP" value="specific" onchange="toggleLocalIP()">
|
||||
<span>指定IP地址</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="localIPInput" style="margin-top: 10px; display: none;">
|
||||
<input type="text" class="form-control" placeholder="输入IP地址或范围">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>远程IP地址</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="remoteIP" value="any" checked onchange="toggleRemoteIP()">
|
||||
<span>任何IP地址</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="remoteIP" value="specific" onchange="toggleRemoteIP()">
|
||||
<span>指定IP地址</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="remoteIPInput" style="margin-top: 10px; display: none;">
|
||||
<input type="text" class="form-control" placeholder="输入IP地址或范围">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced -->
|
||||
<div class="form-section">
|
||||
<h3>⚙️ 高级设置</h3>
|
||||
<div class="form-group">
|
||||
<label>配置文件</label>
|
||||
<div class="checkbox-group">
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="profile" value="domain" checked>
|
||||
<span>域</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="profile" value="private" checked>
|
||||
<span>专用</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="profile" value="public" checked>
|
||||
<span>公用</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>启用规则</label>
|
||||
<div class="checkbox-group">
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" id="enableRule" checked>
|
||||
<span>创建后立即启用此规则</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeModal()">取消</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveRule()">保存规则</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Global variables
|
||||
let rules = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Chrome 浏览器",
|
||||
status: "enabled",
|
||||
direction: "outbound",
|
||||
program: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
||||
protocol: "TCP",
|
||||
port: "80, 443",
|
||||
scope: "任意",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "远程桌面 - 用户模式 (TCP-In)",
|
||||
status: "enabled",
|
||||
direction: "inbound",
|
||||
program: "系统",
|
||||
protocol: "TCP",
|
||||
port: "3389",
|
||||
scope: "任意",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "文件和打印机共享 (SMB-In)",
|
||||
status: "disabled",
|
||||
direction: "inbound",
|
||||
program: "系统",
|
||||
protocol: "TCP",
|
||||
port: "445",
|
||||
scope: "本地子网",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Windows Update",
|
||||
status: "enabled",
|
||||
direction: "outbound",
|
||||
program: "svchost.exe",
|
||||
protocol: "TCP/UDP",
|
||||
port: "80, 443",
|
||||
scope: "任意",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "SQL Server",
|
||||
status: "enabled",
|
||||
direction: "inbound",
|
||||
program: "sqlservr.exe",
|
||||
protocol: "TCP",
|
||||
port: "1433",
|
||||
scope: "指定IP",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "阻止可疑端口",
|
||||
status: "enabled",
|
||||
direction: "inbound",
|
||||
program: "所有程序",
|
||||
protocol: "TCP",
|
||||
port: "135, 139, 445",
|
||||
scope: "任意",
|
||||
action: "阻止"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "SSH 服务",
|
||||
status: "enabled",
|
||||
direction: "inbound",
|
||||
program: "sshd.exe",
|
||||
protocol: "TCP",
|
||||
port: "22",
|
||||
scope: "指定IP",
|
||||
action: "允许"
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "HTTP 服务器",
|
||||
status: "enabled",
|
||||
direction: "inbound",
|
||||
program: "httpd.exe",
|
||||
protocol: "TCP",
|
||||
port: "80",
|
||||
scope: "任意",
|
||||
action: "允许"
|
||||
}
|
||||
];
|
||||
|
||||
let currentFilter = 'all';
|
||||
let editingRuleId = null;
|
||||
|
||||
// Initialize application
|
||||
function init() {
|
||||
renderRules(rules);
|
||||
updateStats();
|
||||
setupEventListeners();
|
||||
}
|
||||
|
||||
// Setup event listeners
|
||||
function setupEventListeners() {
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
searchInput.addEventListener('input', function(e) {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
const filteredRules = rules.filter(rule => {
|
||||
const matchesSearch = rule.name.toLowerCase().includes(searchTerm) ||
|
||||
rule.program.toLowerCase().includes(searchTerm) ||
|
||||
rule.port.toLowerCase().includes(searchTerm) ||
|
||||
rule.protocol.toLowerCase().includes(searchTerm);
|
||||
|
||||
const matchesFilter = currentFilter === 'all' || rule.direction === currentFilter;
|
||||
|
||||
return matchesSearch && matchesFilter;
|
||||
});
|
||||
renderRules(filteredRules);
|
||||
});
|
||||
|
||||
// Close modal when clicking outside
|
||||
document.getElementById('ruleModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent form submission
|
||||
document.getElementById('ruleForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
saveRule();
|
||||
});
|
||||
}
|
||||
|
||||
// Update statistics
|
||||
function updateStats() {
|
||||
const activeRules = rules.filter(rule => rule.status === 'enabled').length;
|
||||
const totalRules = rules.length;
|
||||
const blockedToday = Math.floor(Math.random() * 2000) + 1000; // Simulated data
|
||||
|
||||
document.getElementById('activeRules').textContent = activeRules;
|
||||
document.getElementById('totalRules').textContent = totalRules;
|
||||
document.getElementById('blockedToday').textContent = blockedToday.toLocaleString();
|
||||
}
|
||||
|
||||
// Render rules table
|
||||
function renderRules(rulesToRender) {
|
||||
const tbody = document.getElementById('rulesTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (rulesToRender.length === 0) {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td colspan="7" style="text-align: center; padding: 40px; color: var(--text-secondary);">
|
||||
没有找到匹配的规则
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
return;
|
||||
}
|
||||
|
||||
rulesToRender.forEach(rule => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<div class="rule-name">
|
||||
<div class="rule-icon">${rule.direction === 'inbound' ? '↓' : '↑'}</div>
|
||||
<span>${rule.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-badge status-${rule.status}">
|
||||
${rule.status === 'enabled' ? '已启用' : '已禁用'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="direction-badge direction-${rule.direction === 'inbound' ? 'in' : 'out'}">
|
||||
${rule.direction === 'inbound' ? '入站' : '出站'}
|
||||
</span>
|
||||
</td>
|
||||
<td>${rule.program.split('\\').pop()}</td>
|
||||
<td>${rule.protocol}${rule.port !== '任意' ? ': ' + rule.port : ''}</td>
|
||||
<td>${rule.scope}</td>
|
||||
<td>
|
||||
<div class="action-cell">
|
||||
<button class="action-btn edit" onclick="editRule(${rule.id})" title="编辑">
|
||||
✏️
|
||||
</button>
|
||||
<button class="action-btn delete" onclick="deleteRule(${rule.id})" title="删除">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter rules
|
||||
function filterRules(type) {
|
||||
currentFilter = type;
|
||||
|
||||
// Update button states
|
||||
const buttons = document.querySelectorAll('.filter-buttons .btn-secondary');
|
||||
buttons.forEach(btn => btn.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
|
||||
// Apply filter
|
||||
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
||||
let filtered = rules;
|
||||
|
||||
if (type !== 'all') {
|
||||
filtered = rules.filter(r => r.direction === type);
|
||||
}
|
||||
|
||||
if (searchTerm) {
|
||||
filtered = filtered.filter(rule => {
|
||||
return rule.name.toLowerCase().includes(searchTerm) ||
|
||||
rule.program.toLowerCase().includes(searchTerm) ||
|
||||
rule.port.toLowerCase().includes(searchTerm) ||
|
||||
rule.protocol.toLowerCase().includes(searchTerm);
|
||||
});
|
||||
}
|
||||
|
||||
renderRules(filtered);
|
||||
}
|
||||
|
||||
// Modal functions
|
||||
function openModal(ruleId = null) {
|
||||
editingRuleId = ruleId;
|
||||
const modal = document.getElementById('ruleModal');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
|
||||
if (ruleId) {
|
||||
const rule = rules.find(r => r.id === ruleId);
|
||||
modalTitle.textContent = '编辑防火墙规则';
|
||||
populateForm(rule);
|
||||
} else {
|
||||
modalTitle.textContent = '新建防火墙规则';
|
||||
resetForm();
|
||||
}
|
||||
|
||||
modal.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
const modal = document.getElementById('ruleModal');
|
||||
modal.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
editingRuleId = null;
|
||||
resetForm();
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
document.getElementById('ruleForm').reset();
|
||||
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
document.querySelector('.tab').classList.add('active');
|
||||
document.querySelector('.tab-content').classList.add('active');
|
||||
document.getElementById('portSettings').style.display = 'none';
|
||||
document.getElementById('portRange').style.display = 'none';
|
||||
}
|
||||
|
||||
function populateForm(rule) {
|
||||
document.getElementById('ruleName').value = rule.name;
|
||||
document.querySelector(`input[name="direction"][value="${rule.direction}"]`).checked = true;
|
||||
document.querySelector(`input[name="action"][value="${rule.action.toLowerCase()}"]`).checked = true;
|
||||
document.getElementById('enableRule').checked = rule.status === 'enabled';
|
||||
|
||||
// Set protocol and ports
|
||||
if (rule.protocol !== '任意') {
|
||||
const protocolSelect = document.getElementById('protocol');
|
||||
if (rule.protocol.includes('/')) {
|
||||
protocolSelect.value = 'custom';
|
||||
} else {
|
||||
protocolSelect.value = rule.protocol.toLowerCase();
|
||||
}
|
||||
updatePortOptions();
|
||||
|
||||
if (rule.port !== '任意') {
|
||||
document.querySelector('input[name="portType"][value="specific"]').checked = true;
|
||||
togglePortRange();
|
||||
document.getElementById('portInput').value = rule.port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab switching
|
||||
function switchTab(group, tabId) {
|
||||
// Remove active class from all tabs in this group
|
||||
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
|
||||
// Add active class to clicked tab and its content
|
||||
event.target.classList.add('active');
|
||||
document.getElementById(tabId).classList.add('active');
|
||||
}
|
||||
|
||||
// Protocol and port handling
|
||||
function updatePortOptions() {
|
||||
const protocol = document.getElementById('protocol').value;
|
||||
const portSettings = document.getElementById('portSettings');
|
||||
|
||||
if (protocol === 'tcp' || protocol === 'udp' || protocol === 'custom') {
|
||||
portSettings.style.display = 'block';
|
||||
} else {
|
||||
portSettings.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function togglePortRange() {
|
||||
const portType = document.querySelector('input[name="portType"]:checked').value;
|
||||
const portRange = document.getElementById('portRange');
|
||||
|
||||
if (portType === 'specific') {
|
||||
portRange.style.display = 'flex';
|
||||
} else {
|
||||
portRange.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLocalIP() {
|
||||
const ipType = document.querySelector('input[name="localIP"]:checked').value;
|
||||
const ipInput = document.getElementById('localIPInput');
|
||||
|
||||
if (ipType === 'specific') {
|
||||
ipInput.style.display = 'block';
|
||||
} else {
|
||||
ipInput.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRemoteIP() {
|
||||
const ipType = document.querySelector('input[name="remoteIP"]:checked').value;
|
||||
const ipInput = document.getElementById('remoteIPInput');
|
||||
|
||||
if (ipType === 'specific') {
|
||||
ipInput.style.display = 'block';
|
||||
} else {
|
||||
ipInput.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Rule management
|
||||
function saveRule() {
|
||||
const form = document.getElementById('ruleForm');
|
||||
const formData = new FormData(form);
|
||||
|
||||
// Validate required fields
|
||||
const ruleName = document.getElementById('ruleName').value.trim();
|
||||
if (!ruleName) {
|
||||
showNotification('请输入规则名称', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create rule object
|
||||
const rule = {
|
||||
id: editingRuleId || Date.now(),
|
||||
name: ruleName,
|
||||
description: document.getElementById('ruleDescription').value,
|
||||
status: document.getElementById('enableRule').checked ? 'enabled' : 'disabled',
|
||||
direction: document.querySelector('input[name="direction"]:checked').value,
|
||||
action: document.querySelector('input[name="action"]:checked').value,
|
||||
program: getProgramValue(),
|
||||
protocol: getProtocolValue(),
|
||||
port: getPortValue(),
|
||||
scope: getScopeValue()
|
||||
};
|
||||
|
||||
if (editingRuleId) {
|
||||
// Update existing rule
|
||||
const index = rules.findIndex(r => r.id === editingRuleId);
|
||||
if (index !== -1) {
|
||||
rules[index] = rule;
|
||||
showNotification('规则已更新', 'success');
|
||||
}
|
||||
} else {
|
||||
// Add new rule
|
||||
rules.push(rule);
|
||||
showNotification('规则已创建', 'success');
|
||||
}
|
||||
|
||||
renderRules(rules);
|
||||
updateStats();
|
||||
closeModal();
|
||||
}
|
||||
|
||||
function editRule(ruleId) {
|
||||
openModal(ruleId);
|
||||
}
|
||||
|
||||
function deleteRule(ruleId) {
|
||||
if (confirm('确定要删除这个规则吗?')) {
|
||||
rules = rules.filter(r => r.id !== ruleId);
|
||||
renderRules(rules);
|
||||
updateStats();
|
||||
showNotification('规则已删除', 'info');
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for form data
|
||||
function getProgramValue() {
|
||||
const activeTab = document.querySelector('.tab.active').textContent;
|
||||
if (activeTab === '所有程序') {
|
||||
return '所有程序';
|
||||
} else if (activeTab === '指定程序') {
|
||||
return document.getElementById('programPath').value || '指定程序';
|
||||
} else {
|
||||
const selectedServices = Array.from(document.querySelectorAll('#service input[type="checkbox"]:checked'))
|
||||
.map(cb => cb.nextElementSibling.textContent).join(', ');
|
||||
return selectedServices || '服务';
|
||||
}
|
||||
}
|
||||
|
||||
function getProtocolValue() {
|
||||
const protocol = document.getElementById('protocol').value;
|
||||
return protocol === 'any' ? '任意' : protocol.toUpperCase();
|
||||
}
|
||||
|
||||
function getPortValue() {
|
||||
const protocol = document.getElementById('protocol').value;
|
||||
if (protocol === 'any' || protocol === 'icmpv4' || protocol === 'icmpv6') {
|
||||
return '任意';
|
||||
}
|
||||
|
||||
const portType = document.querySelector('input[name="portType"]:checked');
|
||||
if (!portType || portType.value === 'all') {
|
||||
return '任意';
|
||||
}
|
||||
|
||||
return document.getElementById('portInput').value || '任意';
|
||||
}
|
||||
|
||||
function getScopeValue() {
|
||||
const localIP = document.querySelector('input[name="localIP"]:checked').value;
|
||||
const remoteIP = document.querySelector('input[name="remoteIP"]:checked').value;
|
||||
|
||||
if (localIP === 'any' && remoteIP === 'any') {
|
||||
return '任意';
|
||||
} else if (localIP === 'specific' || remoteIP === 'specific') {
|
||||
return '指定IP';
|
||||
} else {
|
||||
return '本地子网';
|
||||
}
|
||||
}
|
||||
|
||||
function browseProgramPath() {
|
||||
// Simulate file browser (in real implementation, this would open a file dialog)
|
||||
const path = prompt('请输入程序路径:', 'C:\\Program Files\\');
|
||||
if (path) {
|
||||
document.getElementById('programPath').value = path;
|
||||
}
|
||||
}
|
||||
|
||||
// Notification system
|
||||
function showNotification(message, type = 'info') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification ${type}`;
|
||||
notification.textContent = message;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(notification);
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Initialize application when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user