A:增加防火墙UI设计

This commit is contained in:
jimmy committed 2025-07-17 20:48:06 +08:00
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;10.0.0.0/8&#10;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&#10;192.168.0.0/16&#10;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>