1233 lines
46 KiB
HTML
1233 lines
46 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>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.3.4/vue.global.prod.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
color: #333;
|
|
}
|
|
|
|
.app-container {
|
|
padding: 20px;
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.header {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 16px;
|
|
padding: 24px 32px;
|
|
margin-bottom: 24px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.header-content {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.header h1 {
|
|
color: #2d3748;
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.header-stats {
|
|
display: flex;
|
|
gap: 24px;
|
|
}
|
|
|
|
.stat-item {
|
|
text-align: center;
|
|
padding: 12px 20px;
|
|
background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
|
color: white;
|
|
border-radius: 12px;
|
|
min-width: 80px;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 0.875rem;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.controls {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 16px;
|
|
padding: 20px 32px;
|
|
margin-bottom: 24px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 16px;
|
|
}
|
|
|
|
.filter-group {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
.filter-select, .search-input {
|
|
padding: 10px 16px;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
transition: all 0.3s ease;
|
|
background: white;
|
|
}
|
|
|
|
.filter-select:focus, .search-input:focus {
|
|
outline: none;
|
|
border-color: #4f46e5;
|
|
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
|
|
}
|
|
|
|
.search-input {
|
|
min-width: 250px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 12px 24px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 25px rgba(79, 70, 229, 0.3);
|
|
}
|
|
|
|
.btn-success {
|
|
background: linear-gradient(135deg, #10b981, #059669);
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: linear-gradient(135deg, #ef4444, #dc2626);
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #f1f5f9;
|
|
color: #64748b;
|
|
border: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.btn:hover {
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.rules-container {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 16px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.rules-header {
|
|
padding: 20px 32px;
|
|
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
|
|
border-bottom: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.rules-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 120px 100px 150px 120px 100px 140px;
|
|
gap: 16px;
|
|
font-weight: 600;
|
|
color: #475569;
|
|
}
|
|
|
|
.rule-item {
|
|
display: grid;
|
|
grid-template-columns: 1fr 120px 100px 150px 120px 100px 140px;
|
|
gap: 16px;
|
|
padding: 20px 32px;
|
|
border-bottom: 1px solid #f1f5f9;
|
|
transition: all 0.3s ease;
|
|
align-items: center;
|
|
}
|
|
|
|
.rule-item:hover {
|
|
background: rgba(79, 70, 229, 0.05);
|
|
transform: translateX(4px);
|
|
}
|
|
|
|
.rule-name {
|
|
font-weight: 600;
|
|
color: #2d3748;
|
|
}
|
|
|
|
.rule-program {
|
|
font-size: 0.875rem;
|
|
color: #64748b;
|
|
}
|
|
|
|
.status-badge {
|
|
padding: 4px 12px;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
}
|
|
|
|
.status-enabled {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.status-disabled {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.action-badge {
|
|
padding: 4px 12px;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
}
|
|
|
|
.action-allow {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.action-block {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.direction-badge {
|
|
padding: 4px 12px;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
}
|
|
|
|
.direction-inbound {
|
|
background: #dbeafe;
|
|
color: #1e40af;
|
|
}
|
|
|
|
.direction-outbound {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.rule-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.icon-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.icon-btn:hover {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.icon-btn.edit {
|
|
background: #f0f9ff;
|
|
color: #0369a1;
|
|
}
|
|
|
|
.icon-btn.delete {
|
|
background: #fef2f2;
|
|
color: #dc2626;
|
|
}
|
|
|
|
.icon-btn.toggle {
|
|
background: #f7fee7;
|
|
color: #65a30d;
|
|
}
|
|
|
|
.modal {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
backdrop-filter: blur(4px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.modal.show {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
|
|
.modal-content {
|
|
background: white;
|
|
border-radius: 16px;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
max-width: 800px;
|
|
width: 90%;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
transform: scale(0.9);
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.modal.show .modal-content {
|
|
transform: scale(1);
|
|
}
|
|
|
|
.modal-header {
|
|
padding: 24px 32px;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.modal-title {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: #2d3748;
|
|
}
|
|
|
|
.close-btn {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: none;
|
|
background: #f1f5f9;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.close-btn:hover {
|
|
background: #e2e8f0;
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 32px;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.form-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 20px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.form-label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-weight: 600;
|
|
color: #374151;
|
|
}
|
|
|
|
.form-input, .form-select, .form-textarea {
|
|
width: 100%;
|
|
padding: 12px 16px;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.form-input:focus, .form-select:focus, .form-textarea:focus {
|
|
outline: none;
|
|
border-color: #4f46e5;
|
|
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
|
|
}
|
|
|
|
.form-textarea {
|
|
resize: vertical;
|
|
min-height: 80px;
|
|
}
|
|
|
|
.checkbox-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.checkbox {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
.service-list {
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
.service-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px;
|
|
border-radius: 6px;
|
|
transition: background 0.2s ease;
|
|
}
|
|
|
|
.service-item:hover {
|
|
background: #e2e8f0;
|
|
}
|
|
|
|
.modal-footer {
|
|
padding: 24px 32px;
|
|
border-top: 1px solid #e2e8f0;
|
|
display: flex;
|
|
gap: 12px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
border-bottom: 2px solid #e2e8f0;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.tab {
|
|
padding: 12px 24px;
|
|
cursor: pointer;
|
|
border-bottom: 3px solid transparent;
|
|
transition: all 0.3s ease;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tab.active {
|
|
color: #4f46e5;
|
|
border-bottom-color: #4f46e5;
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
@media (max-width: 1200px) {
|
|
.rules-grid, .rule-item {
|
|
grid-template-columns: 1fr 100px 80px 120px 100px 120px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.controls {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.filter-group {
|
|
justify-content: center;
|
|
}
|
|
|
|
.rules-grid, .rule-item {
|
|
grid-template-columns: 1fr;
|
|
gap: 8px;
|
|
}
|
|
|
|
.rule-item {
|
|
padding: 16px 20px;
|
|
}
|
|
|
|
.modal-content {
|
|
width: 95%;
|
|
margin: 20px;
|
|
}
|
|
|
|
.form-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app" class="app-container">
|
|
<!-- Header -->
|
|
<div class="header">
|
|
<div class="header-content">
|
|
<h1>
|
|
<i data-lucide="shield"></i>
|
|
防火墙管理看板
|
|
</h1>
|
|
<div class="header-stats">
|
|
<div class="stat-item">
|
|
<div class="stat-number">{{ enabledRules }}</div>
|
|
<div class="stat-label">已启用</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-number">{{ disabledRules }}</div>
|
|
<div class="stat-label">已禁用</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-number">{{ totalRules }}</div>
|
|
<div class="stat-label">总规则</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Controls -->
|
|
<div class="controls">
|
|
<div class="filter-group">
|
|
<select v-model="filterDirection" class="filter-select">
|
|
<option value="">所有方向</option>
|
|
<option value="inbound">入站</option>
|
|
<option value="outbound">出站</option>
|
|
</select>
|
|
<select v-model="filterAction" class="filter-select">
|
|
<option value="">所有操作</option>
|
|
<option value="allow">允许</option>
|
|
<option value="block">阻止</option>
|
|
</select>
|
|
<select v-model="filterStatus" class="filter-select">
|
|
<option value="">所有状态</option>
|
|
<option value="enabled">已启用</option>
|
|
<option value="disabled">已禁用</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<input
|
|
type="text"
|
|
placeholder="搜索规则名称..."
|
|
v-model="searchText"
|
|
class="search-input"
|
|
>
|
|
<button class="btn btn-primary" @click="openModal()">
|
|
<i data-lucide="plus"></i>
|
|
新增规则
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rules Container -->
|
|
<div class="rules-container">
|
|
<div class="rules-header">
|
|
<div class="rules-grid">
|
|
<div>规则名称</div>
|
|
<div>方向</div>
|
|
<div>操作</div>
|
|
<div>协议/端口</div>
|
|
<div>状态</div>
|
|
<div>作用域</div>
|
|
<div>操作</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-for="rule in filteredRules" :key="rule.id" class="rule-item">
|
|
<div>
|
|
<div class="rule-name">{{ rule.name }}</div>
|
|
<div class="rule-program">{{ rule.program }}</div>
|
|
</div>
|
|
<div>
|
|
<span :class="'direction-badge direction-' + rule.direction">
|
|
{{ rule.direction === 'inbound' ? '入站' : '出站' }}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span :class="'action-badge action-' + rule.action">
|
|
{{ rule.action === 'allow' ? '允许' : '阻止' }}
|
|
</span>
|
|
</div>
|
|
<div>{{ rule.protocol }}/{{ rule.ports }}</div>
|
|
<div>
|
|
<span :class="'status-badge status-' + rule.status">
|
|
{{ rule.status === 'enabled' ? '已启用' : '已禁用' }}
|
|
</span>
|
|
</div>
|
|
<div>{{ rule.scope }}</div>
|
|
<div class="rule-actions">
|
|
<button class="icon-btn edit" @click="editRule(rule)" title="编辑">
|
|
<i data-lucide="edit"></i>
|
|
</button>
|
|
<button class="icon-btn toggle" @click="toggleRule(rule)" title="切换状态">
|
|
<i :data-lucide="rule.status === 'enabled' ? 'pause' : 'play'"></i>
|
|
</button>
|
|
<button class="icon-btn delete" @click="deleteRule(rule.id)" title="删除">
|
|
<i data-lucide="trash-2"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal -->
|
|
<div class="modal" :class="{ show: showModal }" @click.self="closeModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">{{ editingRule ? '编辑规则' : '新增规则' }}</h2>
|
|
<button class="close-btn" @click="closeModal">
|
|
<i data-lucide="x"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="modal-body">
|
|
<div class="tabs">
|
|
<div class="tab" :class="{ active: activeTab === 'general' }" @click="activeTab = 'general'">
|
|
常规
|
|
</div>
|
|
<div class="tab" :class="{ active: activeTab === 'programs' }" @click="activeTab = 'programs'">
|
|
程序和服务
|
|
</div>
|
|
<div class="tab" :class="{ active: activeTab === 'protocols' }" @click="activeTab = 'protocols'">
|
|
协议和端口
|
|
</div>
|
|
<div class="tab" :class="{ active: activeTab === 'scope' }" @click="activeTab = 'scope'">
|
|
作用域
|
|
</div>
|
|
<div class="tab" :class="{ active: activeTab === 'advanced' }" @click="activeTab = 'advanced'">
|
|
高级
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 常规选项卡 -->
|
|
<div class="tab-content" :class="{ active: activeTab === 'general' }">
|
|
<div class="form-group">
|
|
<label class="form-label">规则名称</label>
|
|
<input type="text" v-model="formData.name" class="form-input" placeholder="输入规则名称">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">描述</label>
|
|
<textarea v-model="formData.description" class="form-textarea" placeholder="规则描述(可选)"></textarea>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">方向</label>
|
|
<select v-model="formData.direction" class="form-select">
|
|
<option value="inbound">入站</option>
|
|
<option value="outbound">出站</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">操作</label>
|
|
<select v-model="formData.action" class="form-select">
|
|
<option value="allow">允许连接</option>
|
|
<option value="block">阻止连接</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="checkbox-group">
|
|
<input type="checkbox" v-model="formData.enabled" class="checkbox" id="enabled">
|
|
<label for="enabled">启用此规则</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 程序和服务选项卡 -->
|
|
<div class="tab-content" :class="{ active: activeTab === 'programs' }">
|
|
<div class="form-group">
|
|
<label class="form-label">应用程序</label>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.programType" value="all" name="programType" id="all-programs">
|
|
<label for="all-programs">所有程序</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.programType" value="specific" name="programType" id="specific-program">
|
|
<label for="specific-program">此程序路径:</label>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
v-model="formData.programPath"
|
|
class="form-input"
|
|
placeholder="C:\Program Files\..."
|
|
:disabled="formData.programType !== 'specific'"
|
|
>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">服务</label>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.serviceType" value="all" name="serviceType" id="all-services">
|
|
<label for="all-services">应用于所有程序和服务</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.serviceType" value="specific" name="serviceType" id="specific-services">
|
|
<label for="specific-services">仅应用于这些服务:</label>
|
|
</div>
|
|
|
|
<div v-if="formData.serviceType === 'specific'" class="service-list">
|
|
<div v-for="service in availableServices" :key="service.name" class="service-item">
|
|
<input
|
|
type="checkbox"
|
|
:value="service.name"
|
|
v-model="formData.selectedServices"
|
|
:id="'service-' + service.name"
|
|
>
|
|
<label :for="'service-' + service.name">{{ service.displayName }}</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 协议和端口选项卡 -->
|
|
<div class="tab-content" :class="{ active: activeTab === 'protocols' }">
|
|
<div class="form-group">
|
|
<label class="form-label">协议类型</label>
|
|
<select v-model="formData.protocolType" class="form-select">
|
|
<option value="any">任何</option>
|
|
<option value="tcp">TCP</option>
|
|
<option value="udp">UDP</option>
|
|
<option value="icmpv4">ICMPv4</option>
|
|
<option value="icmpv6">ICMPv6</option>
|
|
<option value="custom">自定义</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div v-if="formData.protocolType === 'custom'" class="form-group">
|
|
<label class="form-label">协议号</label>
|
|
<input type="number" v-model="formData.protocolNumber" class="form-input" placeholder="0-255">
|
|
</div>
|
|
|
|
<div v-if="['tcp', 'udp'].includes(formData.protocolType)" class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">本地端口</label>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.localPortType" value="all" name="localPortType" id="all-local-ports">
|
|
<label for="all-local-ports">所有端口</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.localPortType" value="specific" name="localPortType" id="specific-local-ports">
|
|
<label for="specific-local-ports">特定端口:</label>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
v-model="formData.localPorts"
|
|
class="form-input"
|
|
placeholder="80,443,8000-8080"
|
|
:disabled="formData.localPortType !== 'specific'"
|
|
>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">远程端口</label>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.remotePortType" value="all" name="remotePortType" id="all-remote-ports">
|
|
<label for="all-remote-ports">所有端口</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.remotePortType" value="specific" name="remotePortType" id="specific-remote-ports">
|
|
<label for="specific-remote-ports">特定端口:</label>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
v-model="formData.remotePorts"
|
|
class="form-input"
|
|
placeholder="80,443,8000-8080"
|
|
:disabled="formData.remotePortType !== 'specific'"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 作用域选项卡 -->
|
|
<div class="tab-content" :class="{ active: activeTab === 'scope' }">
|
|
<div class="form-group">
|
|
<label class="form-label">本地IP地址</label>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.localAddressType" value="any" name="localAddressType" id="any-local-address">
|
|
<label for="any-local-address">任何IP地址</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.localAddressType" value="specific" name="localAddressType" id="specific-local-address">
|
|
<label for="specific-local-address">这些IP地址:</label>
|
|
</div>
|
|
<textarea
|
|
v-model="formData.localAddresses"
|
|
class="form-textarea"
|
|
placeholder="192.168.1.1 10.0.0.0/8 172.16.0.0-172.31.255.255"
|
|
:disabled="formData.localAddressType !== 'specific'"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">远程IP地址</label>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.remoteAddressType" value="any" name="remoteAddressType" id="any-remote-address">
|
|
<label for="any-remote-address">任何IP地址</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.remoteAddressType" value="specific" name="remoteAddressType" id="specific-remote-address">
|
|
<label for="specific-remote-address">这些IP地址:</label>
|
|
</div>
|
|
<textarea
|
|
v-model="formData.remoteAddresses"
|
|
class="form-textarea"
|
|
placeholder="8.8.8.8 192.168.0.0/16 203.0.113.0-203.0.113.255"
|
|
:disabled="formData.remoteAddressType !== 'specific'"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 高级选项卡 -->
|
|
<div class="tab-content" :class="{ active: activeTab === 'advanced' }">
|
|
<div class="form-group">
|
|
<label class="form-label">配置文件</label>
|
|
<div class="checkbox-group">
|
|
<input type="checkbox" v-model="formData.profiles.domain" id="profile-domain">
|
|
<label for="profile-domain">域</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="checkbox" v-model="formData.profiles.private" id="profile-private">
|
|
<label for="profile-private">专用</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="checkbox" v-model="formData.profiles.public" id="profile-public">
|
|
<label for="profile-public">公用</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">边缘遍历</label>
|
|
<select v-model="formData.edgeTraversal" class="form-select">
|
|
<option value="no">阻止边缘遍历</option>
|
|
<option value="allow">允许边缘遍历</option>
|
|
<option value="defer">延迟到用户</option>
|
|
<option value="defer-app">延迟到应用程序</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">授权用户</label>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.userType" value="any" name="userType" id="any-user">
|
|
<label for="any-user">任何用户</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="radio" v-model="formData.userType" value="specific" name="userType" id="specific-users">
|
|
<label for="specific-users">仅这些用户:</label>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
v-model="formData.authorizedUsers"
|
|
class="form-input"
|
|
placeholder="DOMAIN\Username, Everyone, ..."
|
|
:disabled="formData.userType !== 'specific'"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" @click="closeModal">取消</button>
|
|
<button class="btn btn-success" @click="saveRule">
|
|
{{ editingRule ? '保存' : '创建' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const { createApp } = Vue;
|
|
|
|
createApp({
|
|
data() {
|
|
return {
|
|
showModal: false,
|
|
editingRule: null,
|
|
activeTab: 'general',
|
|
searchText: '',
|
|
filterDirection: '',
|
|
filterAction: '',
|
|
filterStatus: '',
|
|
|
|
// 表单数据
|
|
formData: {
|
|
name: '',
|
|
description: '',
|
|
direction: 'inbound',
|
|
action: 'allow',
|
|
enabled: true,
|
|
programType: 'all',
|
|
programPath: '',
|
|
serviceType: 'all',
|
|
selectedServices: [],
|
|
protocolType: 'any',
|
|
protocolNumber: '',
|
|
localPortType: 'all',
|
|
localPorts: '',
|
|
remotePortType: 'all',
|
|
remotePorts: '',
|
|
localAddressType: 'any',
|
|
localAddresses: '',
|
|
remoteAddressType: 'any',
|
|
remoteAddresses: '',
|
|
profiles: {
|
|
domain: true,
|
|
private: true,
|
|
public: false
|
|
},
|
|
edgeTraversal: 'no',
|
|
userType: 'any',
|
|
authorizedUsers: ''
|
|
},
|
|
|
|
// 可用服务列表
|
|
availableServices: [
|
|
{ name: 'http', displayName: 'HTTP (80)' },
|
|
{ name: 'https', displayName: 'HTTPS (443)' },
|
|
{ name: 'ftp', displayName: 'FTP (21)' },
|
|
{ name: 'ssh', displayName: 'SSH (22)' },
|
|
{ name: 'telnet', displayName: 'Telnet (23)' },
|
|
{ name: 'smtp', displayName: 'SMTP (25)' },
|
|
{ name: 'dns', displayName: 'DNS (53)' },
|
|
{ name: 'dhcp', displayName: 'DHCP (67-68)' },
|
|
{ name: 'pop3', displayName: 'POP3 (110)' },
|
|
{ name: 'imap', displayName: 'IMAP (143)' },
|
|
{ name: 'snmp', displayName: 'SNMP (161)' },
|
|
{ name: 'ldap', displayName: 'LDAP (389)' },
|
|
{ name: 'smb', displayName: 'SMB (445)' },
|
|
{ name: 'rdp', displayName: 'RDP (3389)' }
|
|
],
|
|
|
|
// 防火墙规则数据
|
|
rules: [
|
|
{
|
|
id: 1,
|
|
name: 'Core Networking - DNS (UDP-Out)',
|
|
program: 'svchost.exe',
|
|
direction: 'outbound',
|
|
action: 'allow',
|
|
protocol: 'UDP',
|
|
ports: '53',
|
|
status: 'enabled',
|
|
scope: '任何'
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Windows Remote Management (HTTP-In)',
|
|
program: 'svchost.exe',
|
|
direction: 'inbound',
|
|
action: 'allow',
|
|
protocol: 'TCP',
|
|
ports: '5985',
|
|
status: 'enabled',
|
|
scope: '本地子网'
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'File and Printer Sharing (SMB-In)',
|
|
program: 'System',
|
|
direction: 'inbound',
|
|
action: 'allow',
|
|
protocol: 'TCP',
|
|
ports: '445',
|
|
status: 'disabled',
|
|
scope: '专用'
|
|
},
|
|
{
|
|
id: 4,
|
|
name: 'Remote Desktop - User Mode (TCP-In)',
|
|
program: 'svchost.exe',
|
|
direction: 'inbound',
|
|
action: 'allow',
|
|
protocol: 'TCP',
|
|
ports: '3389',
|
|
status: 'enabled',
|
|
scope: '任何'
|
|
},
|
|
{
|
|
id: 5,
|
|
name: 'Block Suspicious Outbound',
|
|
program: 'malware.exe',
|
|
direction: 'outbound',
|
|
action: 'block',
|
|
protocol: 'TCP',
|
|
ports: '任何',
|
|
status: 'enabled',
|
|
scope: '任何'
|
|
},
|
|
{
|
|
id: 6,
|
|
name: 'Web Browser (HTTP/HTTPS-Out)',
|
|
program: 'chrome.exe',
|
|
direction: 'outbound',
|
|
action: 'allow',
|
|
protocol: 'TCP',
|
|
ports: '80,443',
|
|
status: 'enabled',
|
|
scope: '任何'
|
|
},
|
|
{
|
|
id: 7,
|
|
name: 'Email Client (SMTP-Out)',
|
|
program: 'outlook.exe',
|
|
direction: 'outbound',
|
|
action: 'allow',
|
|
protocol: 'TCP',
|
|
ports: '25,587',
|
|
status: 'disabled',
|
|
scope: '任何'
|
|
},
|
|
{
|
|
id: 8,
|
|
name: 'Windows Update (HTTP-Out)',
|
|
program: 'svchost.exe',
|
|
direction: 'outbound',
|
|
action: 'allow',
|
|
protocol: 'TCP',
|
|
ports: '80,443',
|
|
status: 'enabled',
|
|
scope: '任何'
|
|
}
|
|
]
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
filteredRules() {
|
|
let filtered = this.rules;
|
|
|
|
if (this.searchText) {
|
|
filtered = filtered.filter(rule =>
|
|
rule.name.toLowerCase().includes(this.searchText.toLowerCase()) ||
|
|
rule.program.toLowerCase().includes(this.searchText.toLowerCase())
|
|
);
|
|
}
|
|
|
|
if (this.filterDirection) {
|
|
filtered = filtered.filter(rule => rule.direction === this.filterDirection);
|
|
}
|
|
|
|
if (this.filterAction) {
|
|
filtered = filtered.filter(rule => rule.action === this.filterAction);
|
|
}
|
|
|
|
if (this.filterStatus) {
|
|
filtered = filtered.filter(rule => rule.status === this.filterStatus);
|
|
}
|
|
|
|
return filtered;
|
|
},
|
|
|
|
totalRules() {
|
|
return this.rules.length;
|
|
},
|
|
|
|
enabledRules() {
|
|
return this.rules.filter(rule => rule.status === 'enabled').length;
|
|
},
|
|
|
|
disabledRules() {
|
|
return this.rules.filter(rule => rule.status === 'disabled').length;
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
openModal(rule = null) {
|
|
this.editingRule = rule;
|
|
this.activeTab = 'general';
|
|
|
|
if (rule) {
|
|
// 编辑模式 - 填充表单数据
|
|
this.formData = {
|
|
name: rule.name,
|
|
description: '',
|
|
direction: rule.direction,
|
|
action: rule.action,
|
|
enabled: rule.status === 'enabled',
|
|
programType: 'all',
|
|
programPath: rule.program,
|
|
serviceType: 'all',
|
|
selectedServices: [],
|
|
protocolType: rule.protocol.toLowerCase(),
|
|
protocolNumber: '',
|
|
localPortType: 'specific',
|
|
localPorts: rule.ports,
|
|
remotePortType: 'all',
|
|
remotePorts: '',
|
|
localAddressType: 'any',
|
|
localAddresses: '',
|
|
remoteAddressType: 'any',
|
|
remoteAddresses: '',
|
|
profiles: {
|
|
domain: true,
|
|
private: true,
|
|
public: false
|
|
},
|
|
edgeTraversal: 'no',
|
|
userType: 'any',
|
|
authorizedUsers: ''
|
|
};
|
|
} else {
|
|
// 新增模式 - 重置表单
|
|
this.resetForm();
|
|
}
|
|
|
|
this.showModal = true;
|
|
},
|
|
|
|
closeModal() {
|
|
this.showModal = false;
|
|
this.editingRule = null;
|
|
this.resetForm();
|
|
},
|
|
|
|
resetForm() {
|
|
this.formData = {
|
|
name: '',
|
|
description: '',
|
|
direction: 'inbound',
|
|
action: 'allow',
|
|
enabled: true,
|
|
programType: 'all',
|
|
programPath: '',
|
|
serviceType: 'all',
|
|
selectedServices: [],
|
|
protocolType: 'any',
|
|
protocolNumber: '',
|
|
localPortType: 'all',
|
|
localPorts: '',
|
|
remotePortType: 'all',
|
|
remotePorts: '',
|
|
localAddressType: 'any',
|
|
localAddresses: '',
|
|
remoteAddressType: 'any',
|
|
remoteAddresses: '',
|
|
profiles: {
|
|
domain: true,
|
|
private: true,
|
|
public: false
|
|
},
|
|
edgeTraversal: 'no',
|
|
userType: 'any',
|
|
authorizedUsers: ''
|
|
};
|
|
},
|
|
|
|
saveRule() {
|
|
if (!this.formData.name.trim()) {
|
|
alert('请输入规则名称');
|
|
return;
|
|
}
|
|
|
|
const ruleData = {
|
|
name: this.formData.name,
|
|
program: this.formData.programType === 'specific' ? this.formData.programPath : 'System',
|
|
direction: this.formData.direction,
|
|
action: this.formData.action,
|
|
protocol: this.formData.protocolType.toUpperCase(),
|
|
ports: this.formData.localPortType === 'specific' ? this.formData.localPorts : '任何',
|
|
status: this.formData.enabled ? 'enabled' : 'disabled',
|
|
scope: '任何'
|
|
};
|
|
|
|
if (this.editingRule) {
|
|
// 编辑现有规则
|
|
const index = this.rules.findIndex(rule => rule.id === this.editingRule.id);
|
|
if (index !== -1) {
|
|
this.rules[index] = { ...this.rules[index], ...ruleData };
|
|
}
|
|
} else {
|
|
// 新增规则
|
|
const newRule = {
|
|
id: Math.max(...this.rules.map(r => r.id)) + 1,
|
|
...ruleData
|
|
};
|
|
this.rules.push(newRule);
|
|
}
|
|
|
|
this.closeModal();
|
|
},
|
|
|
|
editRule(rule) {
|
|
this.openModal(rule);
|
|
},
|
|
|
|
toggleRule(rule) {
|
|
rule.status = rule.status === 'enabled' ? 'disabled' : 'enabled';
|
|
},
|
|
|
|
deleteRule(ruleId) {
|
|
if (confirm('确定要删除这个规则吗?')) {
|
|
this.rules = this.rules.filter(rule => rule.id !== ruleId);
|
|
}
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
// 初始化Lucide图标
|
|
lucide.createIcons();
|
|
},
|
|
|
|
updated() {
|
|
// 更新时重新创建图标
|
|
lucide.createIcons();
|
|
}
|
|
}).mount('#app');
|
|
</script>
|
|
</body>
|
|
</html> |