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