A:防火墙增删功能
This commit is contained in:
1 parent
4f4a3b70c5
commit
a99fa53b8c
5 files changed
+489
-164
No files matched your search
@@ -47,3 +47,74 @@ GET http://localhost:8268/api/firewall/rules
|
|||||||
# 4 = Public (公共)
|
# 4 = Public (公共)
|
||||||
# 7 = All (所有,即1+2+4)
|
# 7 = All (所有,即1+2+4)
|
||||||
|
|
||||||
|
### ✅ 新增防火墙规则
|
||||||
|
POST http://localhost:你的端口/api/firewall/rules
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "MyRule",
|
||||||
|
"description": "Allow my application",
|
||||||
|
"applicationName": "C:\\Program Files\\MyApp\\app.exe",
|
||||||
|
"localAddresses": "*",
|
||||||
|
"remoteAddresses": "*",
|
||||||
|
"protocol": "TCP", // 可选值: "TCP", "UDP", "ICMPv4", "ICMPv6"
|
||||||
|
"icmpTypesAndCodes": "", // 仅当 protocol 为 ICMP 时使用
|
||||||
|
"localPorts": "8080", // 可为 "80", "80,443", "*" 等
|
||||||
|
"remotePorts": "*",
|
||||||
|
"direction": "Inbound", // 可选值: "Inbound", "Outbound"
|
||||||
|
"enabled": true,
|
||||||
|
"profiles": "Domain,Private,Public", // 可组合,逗号分隔
|
||||||
|
"action": "Allow", // 可选值: "Allow", "Block"
|
||||||
|
"grouping": "MyAppGroup",
|
||||||
|
"interfaceTypes": "All", // 可选: "All", "Wireless", "LAN", etc.
|
||||||
|
"edgeTraversal": false
|
||||||
|
}
|
||||||
|
|
||||||
|
### 响应示例:
|
||||||
|
# {
|
||||||
|
# "success": true,
|
||||||
|
# "message": "防火墙规则创建成功",
|
||||||
|
# "data": null
|
||||||
|
# }
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✏️ 编辑防火墙规则
|
||||||
|
PUT http://localhost:你的端口/api/firewall/rules
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"currentName": "MyRule",
|
||||||
|
"newName": "MyRuleUpdated",
|
||||||
|
"description": "Updated rule",
|
||||||
|
"enabled": true,
|
||||||
|
"applicationName": "C:\\Program Files\\MyApp\\new_app.exe",
|
||||||
|
"protocol": "UDP",
|
||||||
|
"localPorts": "9999",
|
||||||
|
"remotePorts": "*",
|
||||||
|
"localAddresses": "*",
|
||||||
|
"remoteAddresses": "*",
|
||||||
|
"profiles": "Private,Public",
|
||||||
|
"action": "Block",
|
||||||
|
"grouping": "NewGroup",
|
||||||
|
"edgeTraversal": false
|
||||||
|
}
|
||||||
|
|
||||||
|
### 响应示例:
|
||||||
|
# {
|
||||||
|
# "success": true,
|
||||||
|
# "message": "防火墙规则更新成功",
|
||||||
|
# "data": null
|
||||||
|
# }
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ 删除防火墙规则
|
||||||
|
DELETE http://localhost:你的端口/api/firewall/rules?name=MyRuleUpdated
|
||||||
|
|
||||||
|
### 响应示例:
|
||||||
|
# {
|
||||||
|
# "success": true,
|
||||||
|
# "message": "防火墙规则删除成功",
|
||||||
|
# "data": null
|
||||||
|
# }
|
||||||
@@ -6,7 +6,7 @@ import type { subRoute, subRouteList } from '@/types/devIndex'
|
|||||||
import app from '@/constants/app'
|
import app from '@/constants/app'
|
||||||
import { ref, onMounted, provide } from 'vue'
|
import { ref, onMounted, provide } from 'vue'
|
||||||
import { NButton } from 'naive-ui'
|
import { NButton } from 'naive-ui'
|
||||||
import { NConfigProvider, NGlobalStyle, NMessageProvider, NLoadingBarProvider } from 'naive-ui'
|
import { NConfigProvider, NGlobalStyle, NMessageProvider, NLoadingBarProvider, NDialogProvider } from 'naive-ui'
|
||||||
import { useThemeStore } from '@/stores/theme'
|
import { useThemeStore } from '@/stores/theme'
|
||||||
import NetworkConnectionPanel1 from '@/components/NetworkConnectionPanel1.vue'
|
import NetworkConnectionPanel1 from '@/components/NetworkConnectionPanel1.vue'
|
||||||
import MainLayout from './layouts/MainLayout.vue'
|
import MainLayout from './layouts/MainLayout.vue'
|
||||||
@@ -146,8 +146,9 @@ const getLayout = (route) => {
|
|||||||
:theme="themeStore.theme"
|
:theme="themeStore.theme"
|
||||||
>
|
>
|
||||||
<n-message-provider>
|
<n-message-provider>
|
||||||
<n-loading-bar-provider>
|
<n-dialog-provider>
|
||||||
<n-global-style />
|
<n-loading-bar-provider>
|
||||||
|
<n-global-style />
|
||||||
|
|
||||||
<!-- 可拖动的导航栏 -->
|
<!-- 可拖动的导航栏 -->
|
||||||
<div
|
<div
|
||||||
@@ -188,7 +189,8 @@ const getLayout = (route) => {
|
|||||||
<!-- <UnifiedConnectionsList4 />-->
|
<!-- <UnifiedConnectionsList4 />-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
</n-loading-bar-provider>
|
</n-loading-bar-provider>
|
||||||
|
</n-dialog-provider>
|
||||||
</n-message-provider>
|
</n-message-provider>
|
||||||
</n-config-provider>
|
</n-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -240,16 +240,16 @@ import {
|
|||||||
FolderOpenOutline,
|
FolderOpenOutline,
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
import { RuleDirection, RuleAction, ProtocolType, FirewallProfile } from '@/types/firewall'
|
import { RuleDirection, RuleAction, ProtocolType, FirewallProfile } from '@/types/firewall'
|
||||||
import type { DisplayRule } from '@/types/firewall'
|
import type { FirewallRuleShow } from '@/types/firewallFrond'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean
|
modelValue: boolean
|
||||||
editRule?: DisplayRule | null
|
editRule?: FirewallRuleShow | null
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:modelValue': [value: boolean]
|
'update:modelValue': [value: boolean]
|
||||||
save: [rule: DisplayRule]
|
save: [rule: FirewallRuleShow]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
@@ -265,7 +265,7 @@ const activeTab = ref('basic')
|
|||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const defaultFormData: DisplayRule = {
|
const defaultFormData: FirewallRuleShow = {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
@@ -278,7 +278,7 @@ const defaultFormData: DisplayRule = {
|
|||||||
profiles: ['域', '专用'],
|
profiles: ['域', '专用'],
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = ref<DisplayRule>({ ...defaultFormData })
|
const formData = ref<FirewallRuleShow>({ ...defaultFormData })
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const formRules: FormRules = {
|
const formRules: FormRules = {
|
||||||
@@ -349,15 +349,14 @@ const handleSave = async () => {
|
|||||||
|
|
||||||
// 生成ID(如果是新规则)
|
// 生成ID(如果是新规则)
|
||||||
if (!formData.value.id) {
|
if (!formData.value.id) {
|
||||||
formData.value.id = Date.now().toString()
|
formData.value.id = crypto.randomUUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟保存延迟
|
// 触发保存事件,父组件处理实际的API调用
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
|
|
||||||
emit('save', { ...formData.value })
|
emit('save', { ...formData.value })
|
||||||
message.success(isEdit.value ? '规则更新成功' : '规则创建成功')
|
|
||||||
handleClose()
|
// 注意:这里不再自动关闭弹窗和显示成功消息
|
||||||
|
// 成功/失败的处理将由父组件通过调用 handleSaveSuccess/handleSaveError 来控制
|
||||||
} catch (errors) {
|
} catch (errors) {
|
||||||
message.error('请检查表单中的错误')
|
message.error('请检查表单中的错误')
|
||||||
console.log(errors)
|
console.log(errors)
|
||||||
@@ -366,6 +365,24 @@ const handleSave = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 父组件调用此方法表示保存成功
|
||||||
|
const handleSaveSuccess = () => {
|
||||||
|
message.success(isEdit.value ? '规则更新成功' : '规则创建成功')
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 父组件调用此方法表示保存失败
|
||||||
|
const handleSaveError = (errorMessage: string) => {
|
||||||
|
message.error(errorMessage)
|
||||||
|
// 不关闭弹窗,让用户可以重新尝试
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
handleSaveSuccess,
|
||||||
|
handleSaveError
|
||||||
|
})
|
||||||
|
|
||||||
const browseApplication = () => {
|
const browseApplication = () => {
|
||||||
// 这里可以集成文件选择器或调用 Electron API
|
// 这里可以集成文件选择器或调用 Electron API
|
||||||
message.info('请使用文件浏览器选择应用程序')
|
message.info('请使用文件浏览器选择应用程序')
|
||||||
|
|||||||
@@ -45,3 +45,45 @@ export interface FirewallRulesParam {
|
|||||||
port?: number
|
port?: number
|
||||||
search?: string
|
search?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建防火墙规则请求
|
||||||
|
*/
|
||||||
|
export interface CreateFirewallRuleRequest {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
applicationName?: string
|
||||||
|
localAddresses?: string
|
||||||
|
remoteAddresses?: string
|
||||||
|
protocol?: string
|
||||||
|
icmpTypesAndCodes?: string
|
||||||
|
localPorts?: string
|
||||||
|
remotePorts?: string
|
||||||
|
direction: string
|
||||||
|
enabled: boolean
|
||||||
|
profiles: string
|
||||||
|
action: string
|
||||||
|
grouping?: string
|
||||||
|
interfaceTypes?: string
|
||||||
|
edgeTraversal?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新防火墙规则请求
|
||||||
|
*/
|
||||||
|
export interface UpdateFirewallRuleRequest {
|
||||||
|
currentName: string
|
||||||
|
newName?: string
|
||||||
|
description?: string
|
||||||
|
enabled?: boolean
|
||||||
|
applicationName?: string
|
||||||
|
protocol?: string
|
||||||
|
localPorts?: string
|
||||||
|
remotePorts?: string
|
||||||
|
localAddresses?: string
|
||||||
|
remoteAddresses?: string
|
||||||
|
profiles?: string
|
||||||
|
action?: string
|
||||||
|
grouping?: string
|
||||||
|
edgeTraversal?: boolean
|
||||||
|
}
|
||||||
@@ -170,7 +170,9 @@
|
|||||||
<span>暂无数据</span>
|
<span>暂无数据</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="false" class="data-info">
|
<div v-if="false" class="data-info">
|
||||||
<span>总共 {{ totalCount }} 条数据(已加载 {{ loadedRanges.size * pageSize }} 条)</span>
|
<span
|
||||||
|
>总共 {{ totalCount }} 条数据(已加载 {{ loadedRanges.size * pageSize }} 条)</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 虚拟滚动列表 -->
|
<!-- 虚拟滚动列表 -->
|
||||||
@@ -181,7 +183,7 @@
|
|||||||
:item-size="80"
|
:item-size="80"
|
||||||
:buffer="200"
|
:buffer="200"
|
||||||
key-field="id"
|
key-field="id"
|
||||||
style="height: 500px;"
|
style="height: 500px"
|
||||||
@scroll="onScroll"
|
@scroll="onScroll"
|
||||||
@resize="onResize"
|
@resize="onResize"
|
||||||
@visible="onVisibleRangeUpdate"
|
@visible="onVisibleRangeUpdate"
|
||||||
@@ -189,88 +191,92 @@
|
|||||||
>
|
>
|
||||||
<div :key="item.id" class="scroller-item-wrapper">
|
<div :key="item.id" class="scroller-item-wrapper">
|
||||||
<!-- 占位符项 -->
|
<!-- 占位符项 -->
|
||||||
<div v-if="item.isPlaceholder" class="rule-item loading-placeholder">
|
<div
|
||||||
|
v-if="item.isPlaceholder"
|
||||||
|
class="rule-item loading-placeholder"
|
||||||
|
:class="{ 'deleted-item': item.isDeleted }"
|
||||||
|
>
|
||||||
<div class="loading-content">
|
<div class="loading-content">
|
||||||
<span>加载中...</span>
|
<span>{{ item.isDeleted ? '规则已删除' : '加载中...' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 已加载的数据项 -->
|
<!-- 已加载的数据项 -->
|
||||||
<div v-else class="rule-item" :class="{ selected: isItemSelected(item) }">
|
<div v-else class="rule-item" :class="{ selected: isItemSelected(item) }">
|
||||||
<div class="rule-cell checkbox-cell">
|
<div class="rule-cell checkbox-cell">
|
||||||
<n-checkbox
|
<n-checkbox
|
||||||
:checked="isItemSelected(item)"
|
:checked="isItemSelected(item)"
|
||||||
@update:checked="(checked) => handleRuleCheck(item.id, checked)"
|
@update:checked="(checked) => handleRuleCheck(item.id, checked)"
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rule-cell name-cell">
|
|
||||||
<div class="rule-name">{{ item.name }}</div>
|
|
||||||
<div class="rule-description">{{ item.description }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rule-cell status-cell">
|
|
||||||
<n-tag :type="item.enabled ? 'success' : 'default'" size="small">
|
|
||||||
{{ item.enabled ? '启用' : '禁用' }}
|
|
||||||
</n-tag>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rule-cell direction-cell">
|
|
||||||
<div class="direction-content">
|
|
||||||
<n-icon
|
|
||||||
:component="item.direction === 'inbound' ? ArrowDownOutline : ArrowUpOutline"
|
|
||||||
size="16"
|
|
||||||
/>
|
/>
|
||||||
<span>{{ item.direction === 'inbound' ? '入站' : '出站' }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rule-cell action-cell">
|
<div class="rule-cell name-cell">
|
||||||
<n-tag :type="item.action === 'allow' ? 'success' : 'error'" size="small">
|
<div class="rule-name">{{ item.name }}</div>
|
||||||
{{ item.action === 'allow' ? '允许' : '阻止' }}
|
<div class="rule-description">{{ item.description }}</div>
|
||||||
</n-tag>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rule-cell program-cell">
|
<div class="rule-cell status-cell">
|
||||||
<div class="program-text" :title="item.program">{{ item.program }}</div>
|
<n-tag :type="item.enabled ? 'success' : 'default'" size="small">
|
||||||
</div>
|
{{ item.enabled ? '启用' : '禁用' }}
|
||||||
|
|
||||||
<div class="rule-cell protocol-cell">
|
|
||||||
<span>{{ item.protocol }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rule-cell port-cell">
|
|
||||||
<span>{{ item.port }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rule-cell profiles-cell">
|
|
||||||
<div class="profiles-container">
|
|
||||||
<n-tag
|
|
||||||
v-for="profile in item.profiles"
|
|
||||||
:key="profile"
|
|
||||||
type="info"
|
|
||||||
size="small"
|
|
||||||
class="profile-tag"
|
|
||||||
>
|
|
||||||
{{ profile }}
|
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rule-cell actions-cell">
|
<div class="rule-cell direction-cell">
|
||||||
<div class="action-buttons">
|
<div class="direction-content">
|
||||||
<n-button size="small" quaternary @click="editRule(item)">
|
<n-icon
|
||||||
<template #icon>
|
:component="item.direction === 'inbound' ? ArrowDownOutline : ArrowUpOutline"
|
||||||
<n-icon :component="CreateOutline" size="14" />
|
size="16"
|
||||||
</template>
|
/>
|
||||||
</n-button>
|
<span>{{ item.direction === 'inbound' ? '入站' : '出站' }}</span>
|
||||||
<n-button size="small" quaternary type="error" @click="deleteRule(item.id)">
|
</div>
|
||||||
<template #icon>
|
</div>
|
||||||
<n-icon :component="TrashOutline" size="14" />
|
|
||||||
</template>
|
<div class="rule-cell action-cell">
|
||||||
</n-button>
|
<n-tag :type="item.action === 'allow' ? 'success' : 'error'" size="small">
|
||||||
|
{{ item.action === 'allow' ? '允许' : '阻止' }}
|
||||||
|
</n-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-cell program-cell">
|
||||||
|
<div class="program-text" :title="item.program">{{ item.program }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-cell protocol-cell">
|
||||||
|
<span>{{ item.protocol }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-cell port-cell">
|
||||||
|
<span>{{ item.port }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-cell profiles-cell">
|
||||||
|
<div class="profiles-container">
|
||||||
|
<n-tag
|
||||||
|
v-for="profile in item.profiles"
|
||||||
|
:key="profile"
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
class="profile-tag"
|
||||||
|
>
|
||||||
|
{{ profile }}
|
||||||
|
</n-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-cell actions-cell">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<n-button size="small" quaternary @click="editRule(item)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="CreateOutline" size="14" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" quaternary type="error" @click="deleteRule(item.id)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="TrashOutline" size="14" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RecycleScroller>
|
</RecycleScroller>
|
||||||
@@ -279,7 +285,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 防火墙规则表单 -->
|
<!-- 防火墙规则表单 -->
|
||||||
<FirewallRuleForm v-model="showRuleForm" :edit-rule="currentEditRule" @save="handleSaveRule" />
|
<FirewallRuleForm
|
||||||
|
ref="ruleFormRef"
|
||||||
|
v-model="showRuleForm"
|
||||||
|
:edit-rule="currentEditRule"
|
||||||
|
@save="handleSaveRule"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -295,6 +306,7 @@ import {
|
|||||||
NCheckbox,
|
NCheckbox,
|
||||||
NSelect,
|
NSelect,
|
||||||
useMessage,
|
useMessage,
|
||||||
|
useDialog,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { RecycleScroller, DynamicScroller } from 'vue-virtual-scroller'
|
import { RecycleScroller, DynamicScroller } from 'vue-virtual-scroller'
|
||||||
import FirewallRuleForm from '@/components/FirewallRuleForm.vue'
|
import FirewallRuleForm from '@/components/FirewallRuleForm.vue'
|
||||||
@@ -313,20 +325,31 @@ import {
|
|||||||
RefreshOutline,
|
RefreshOutline,
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
import { httpClient } from '@/utils/http'
|
import { httpClient } from '@/utils/http'
|
||||||
import type { FirewallStatus, FirewallRule, RuleDirection, RuleAction, ProtocolType } from '@/types/firewall'
|
import type {
|
||||||
|
FirewallStatus,
|
||||||
|
FirewallRule,
|
||||||
|
RuleDirection,
|
||||||
|
RuleAction,
|
||||||
|
ProtocolType,
|
||||||
|
} from '@/types/firewall'
|
||||||
import { ApiResponse } from '@/types/http'
|
import { ApiResponse } from '@/types/http'
|
||||||
import StringUtils from '@/utils/stringUtils'
|
import StringUtils from '@/utils/stringUtils'
|
||||||
import {
|
import {
|
||||||
FirewallRuleResponse,
|
FirewallRuleResponse,
|
||||||
FirewallRuleShow,
|
FirewallRuleShow,
|
||||||
FirewallRulesParam,
|
FirewallRulesParam,
|
||||||
|
CreateFirewallRuleRequest,
|
||||||
|
UpdateFirewallRuleRequest,
|
||||||
} from '@/types/firewallFrond'
|
} from '@/types/firewallFrond'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
// 界面状态
|
// 界面状态
|
||||||
const showRuleForm = ref(false)
|
const showRuleForm = ref(false)
|
||||||
const currentEditRule = ref(null)
|
const currentEditRule = ref(null)
|
||||||
|
const ruleFormRef = ref() // 表单组件引用
|
||||||
|
const originalRuleName = ref<string>('') // 用于跟踪编辑时的原始规则名称
|
||||||
const firewallEnabled = ref(true)
|
const firewallEnabled = ref(true)
|
||||||
const checkedRowKeys = ref<string[]>([])
|
const checkedRowKeys = ref<string[]>([])
|
||||||
|
|
||||||
@@ -416,10 +439,11 @@ const loadedPages = new Set<number>()
|
|||||||
// 防火墙规则列表 - 预分配所有位置,包含null占位符
|
// 防火墙规则列表 - 预分配所有位置,包含null占位符
|
||||||
const firewallRule = ref<(FirewallRuleShow | null)[]>([])
|
const firewallRule = ref<(FirewallRuleShow | null)[]>([])
|
||||||
const loadedRanges = new Set<string>() // 例如:"100-149"
|
const loadedRanges = new Set<string>() // 例如:"100-149"
|
||||||
|
const deletedIndices = new Set<number>() // 跟踪已删除项目的索引
|
||||||
|
|
||||||
// 当前已加载的有效数据(非null非占位符)
|
// 当前已加载的有效数据(非null非占位符)
|
||||||
const currentLoadedRules = computed(() => {
|
const currentLoadedRules = computed(() => {
|
||||||
return firewallRule.value.filter(rule => rule !== null) as FirewallRuleShow[]
|
return firewallRule.value.filter((rule) => rule !== null) as FirewallRuleShow[]
|
||||||
})
|
})
|
||||||
|
|
||||||
// 为RecycleScroller准备的数据,每个项目都有唯一的id
|
// 为RecycleScroller准备的数据,每个项目都有唯一的id
|
||||||
@@ -441,19 +465,20 @@ const recyclerItems = computed(() => {
|
|||||||
port: '',
|
port: '',
|
||||||
profiles: [] as string[],
|
profiles: [] as string[],
|
||||||
isPlaceholder: true as const,
|
isPlaceholder: true as const,
|
||||||
index: index
|
isDeleted: deletedIndices.has(index), // 标记是否为删除的项目
|
||||||
|
index: index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 类型守卫函数
|
// 类型守卫函数
|
||||||
function isPlaceholder(item: any): item is { isPlaceholder: true } {
|
function isPlaceholder(item: unknown): item is { isPlaceholder: true } {
|
||||||
return item && item.isPlaceholder === true
|
return item && item.isPlaceholder === true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 智能选中状态检查,避免虚拟滚动的选中状态闪烁
|
// 智能选中状态检查,避免虚拟滚动的选中状态闪烁
|
||||||
function isItemSelected(item: any): boolean {
|
function isItemSelected(item: unknown): boolean {
|
||||||
// 如果是占位符,永远不选中
|
// 如果是占位符,永远不选中
|
||||||
if (isPlaceholder(item)) {
|
if (isPlaceholder(item)) {
|
||||||
return false
|
return false
|
||||||
@@ -492,14 +517,90 @@ function convertFirewallRuleToShow(rule: FirewallRule): FirewallRuleShow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将前端表单数据转换为API创建请求格式
|
||||||
|
function convertToCreateRequest(formRule: FirewallRuleShow): CreateFirewallRuleRequest {
|
||||||
|
return {
|
||||||
|
name: formRule.name,
|
||||||
|
description: formRule.description || '',
|
||||||
|
applicationName: formRule.program === '任意' ? '' : formRule.program,
|
||||||
|
localAddresses: '*',
|
||||||
|
remoteAddresses: '*',
|
||||||
|
protocol: formRule.protocol === '任意' ? 'TCP' : formRule.protocol,
|
||||||
|
icmpTypesAndCodes: '',
|
||||||
|
localPorts: formRule.port === '任意' ? '*' : formRule.port,
|
||||||
|
remotePorts: '*',
|
||||||
|
direction: formRule.direction === 'inbound' ? 'Inbound' : 'Outbound',
|
||||||
|
enabled: formRule.enabled,
|
||||||
|
profiles: formRule.profiles
|
||||||
|
.map((p) => {
|
||||||
|
switch (p) {
|
||||||
|
case '域':
|
||||||
|
return 'Domain'
|
||||||
|
case '专用':
|
||||||
|
return 'Private'
|
||||||
|
case '公用':
|
||||||
|
return 'Public'
|
||||||
|
default:
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(','),
|
||||||
|
action: formRule.action === 'allow' ? 'Allow' : 'Block',
|
||||||
|
grouping: '',
|
||||||
|
interfaceTypes: 'All',
|
||||||
|
edgeTraversal: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将前端表单数据转换为API更新请求格式
|
||||||
|
function convertToUpdateRequest(
|
||||||
|
formRule: FirewallRuleShow,
|
||||||
|
originalName: string,
|
||||||
|
): UpdateFirewallRuleRequest {
|
||||||
|
return {
|
||||||
|
currentName: originalName,
|
||||||
|
newName: formRule.name !== originalName ? formRule.name : undefined,
|
||||||
|
description: formRule.description || '',
|
||||||
|
enabled: formRule.enabled,
|
||||||
|
applicationName: formRule.program === '任意' ? '' : formRule.program,
|
||||||
|
protocol: formRule.protocol === '任意' ? 'TCP' : formRule.protocol,
|
||||||
|
localPorts: formRule.port === '任意' ? '*' : formRule.port,
|
||||||
|
remotePorts: '*',
|
||||||
|
localAddresses: '*',
|
||||||
|
remoteAddresses: '*',
|
||||||
|
profiles: formRule.profiles
|
||||||
|
.map((p) => {
|
||||||
|
switch (p) {
|
||||||
|
case '域':
|
||||||
|
return 'Domain'
|
||||||
|
case '专用':
|
||||||
|
return 'Private'
|
||||||
|
case '公用':
|
||||||
|
return 'Public'
|
||||||
|
default:
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(','),
|
||||||
|
action: formRule.action === 'allow' ? 'Allow' : 'Block',
|
||||||
|
grouping: '',
|
||||||
|
edgeTraversal: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 协议名称转换
|
// 协议名称转换
|
||||||
function getProtocolName(protocol: ProtocolType): string {
|
function getProtocolName(protocol: ProtocolType): string {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case 6: return 'TCP'
|
case 6:
|
||||||
case 17: return 'UDP'
|
return 'TCP'
|
||||||
case 1: return 'ICMPV4'
|
case 17:
|
||||||
case 256: return '任意'
|
return 'UDP'
|
||||||
default: return String(protocol)
|
case 1:
|
||||||
|
return 'ICMPV4'
|
||||||
|
case 256:
|
||||||
|
return '任意'
|
||||||
|
default:
|
||||||
|
return String(protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,11 +663,16 @@ function buildQueryParams(baseParams: Partial<FirewallRulesParam> = {}): Firewal
|
|||||||
// 协议值转换
|
// 协议值转换
|
||||||
function getProtocolValue(protocol: string): number {
|
function getProtocolValue(protocol: string): number {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case 'TCP': return 6
|
case 'TCP':
|
||||||
case 'UDP': return 17
|
return 6
|
||||||
case 'ICMPV4': return 1
|
case 'UDP':
|
||||||
case '任意': return 256
|
return 17
|
||||||
default: return 256
|
case 'ICMPV4':
|
||||||
|
return 1
|
||||||
|
case '任意':
|
||||||
|
return 256
|
||||||
|
default:
|
||||||
|
return 256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,11 +685,11 @@ async function loadRange(startIndex: number, limit = pageSize) {
|
|||||||
|
|
||||||
const rangeKey = `${startIndex}-${startIndex + limit - 1}`
|
const rangeKey = `${startIndex}-${startIndex + limit - 1}`
|
||||||
if (loadedRanges.has(rangeKey)) {
|
if (loadedRanges.has(rangeKey)) {
|
||||||
console.log('范围已加载,跳过:', rangeKey)
|
//console.log('范围已加载,跳过:', rangeKey)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('开始加载数据范围:', rangeKey)
|
//console.log('开始加载数据范围:', rangeKey)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -592,14 +698,14 @@ async function loadRange(startIndex: number, limit = pageSize) {
|
|||||||
limit: limit,
|
limit: limit,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('请求参数:', params)
|
//console.log('请求参数:', params)
|
||||||
const res = await getFirewallRules(params)
|
const res = await getFirewallRules(params)
|
||||||
if (!res) {
|
if (!res) {
|
||||||
console.log('请求返回空结果')
|
console.log('请求返回空结果')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('收到数据:', res.rules.length, '条,总数:', res.totalCount)
|
//console.log('收到数据:', res.rules.length, '条,总数:', res.totalCount)
|
||||||
|
|
||||||
// 确保数组已预分配
|
// 确保数组已预分配
|
||||||
if (firewallRule.value.length === 0 && res.totalCount > 0) {
|
if (firewallRule.value.length === 0 && res.totalCount > 0) {
|
||||||
@@ -613,12 +719,12 @@ async function loadRange(startIndex: number, limit = pageSize) {
|
|||||||
const index = startIndex + i
|
const index = startIndex + i
|
||||||
if (index < firewallRule.value.length) {
|
if (index < firewallRule.value.length) {
|
||||||
firewallRule.value[index] = convertFirewallRuleToShow(res.rules[i])
|
firewallRule.value[index] = convertFirewallRuleToShow(res.rules[i])
|
||||||
console.log(`填充数据到位置 ${index}:`, firewallRule.value[index]?.name)
|
//console.log(`填充数据到位置 ${index}:`, firewallRule.value[index]?.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedRanges.add(rangeKey)
|
loadedRanges.add(rangeKey)
|
||||||
console.log('范围加载完成:', rangeKey, '已加载范围数:', loadedRanges.size)
|
//console.log('范围加载完成:', rangeKey, '已加载范围数:', loadedRanges.size)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载数据失败:', error)
|
console.error('加载数据失败:', error)
|
||||||
message.error('数据加载失败')
|
message.error('数据加载失败')
|
||||||
@@ -634,6 +740,7 @@ async function resetAndLoadFirst() {
|
|||||||
initialLoading.value = true
|
initialLoading.value = true
|
||||||
firewallRule.value = []
|
firewallRule.value = []
|
||||||
loadedRanges.clear()
|
loadedRanges.clear()
|
||||||
|
deletedIndices.clear() // 清理删除标记
|
||||||
|
|
||||||
// 先获取总数
|
// 先获取总数
|
||||||
const params = buildQueryParams({
|
const params = buildQueryParams({
|
||||||
@@ -673,7 +780,7 @@ async function resetAndLoadFirst() {
|
|||||||
/**
|
/**
|
||||||
* 滚动事件处理 - 检测是否需要加载更多数据
|
* 滚动事件处理 - 检测是否需要加载更多数据
|
||||||
*/
|
*/
|
||||||
function onVisibleRangeUpdate(event?: any) {
|
function onVisibleRangeUpdate(event?: unknown) {
|
||||||
console.log('可见范围更新触发:', event)
|
console.log('可见范围更新触发:', event)
|
||||||
|
|
||||||
// 检查事件参数格式
|
// 检查事件参数格式
|
||||||
@@ -727,7 +834,7 @@ function onVisibleRangeUpdate(event?: any) {
|
|||||||
* 普通滚动事件处理
|
* 普通滚动事件处理
|
||||||
*/
|
*/
|
||||||
function onScroll(event: Event) {
|
function onScroll(event: Event) {
|
||||||
console.log('滚动事件触发')
|
// console.log('滚动事件触发')
|
||||||
|
|
||||||
// 尝试基于滚动位置估算可见范围
|
// 尝试基于滚动位置估算可见范围
|
||||||
const target = event.target as HTMLElement
|
const target = event.target as HTMLElement
|
||||||
@@ -735,30 +842,30 @@ function onScroll(event: Event) {
|
|||||||
const { scrollTop, scrollHeight, clientHeight } = target
|
const { scrollTop, scrollHeight, clientHeight } = target
|
||||||
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight
|
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight
|
||||||
|
|
||||||
console.log('滚动位置信息:', {
|
// console.log('滚动位置信息:', {
|
||||||
scrollTop,
|
// scrollTop,
|
||||||
scrollHeight,
|
// scrollHeight,
|
||||||
clientHeight,
|
// clientHeight,
|
||||||
scrollPercentage: Math.round(scrollPercentage * 100) + '%'
|
// scrollPercentage: Math.round(scrollPercentage * 100) + '%'
|
||||||
})
|
// })
|
||||||
|
|
||||||
// 估算当前可见的项目范围
|
// 估算当前可见的项目范围
|
||||||
const itemHeight = 80 // item-size
|
const itemHeight = 80 // item-size
|
||||||
const visibleStartIndex = Math.floor(scrollTop / itemHeight)
|
const visibleStartIndex = Math.floor(scrollTop / itemHeight)
|
||||||
const visibleEndIndex = Math.ceil((scrollTop + clientHeight) / itemHeight)
|
const visibleEndIndex = Math.ceil((scrollTop + clientHeight) / itemHeight)
|
||||||
|
|
||||||
console.log('估算的可见范围:', { visibleStartIndex, visibleEndIndex })
|
// console.log('估算的可见范围:', { visibleStartIndex, visibleEndIndex })
|
||||||
|
|
||||||
// 计算需要加载的数据段
|
// 计算需要加载的数据段
|
||||||
const alignedStart = Math.floor(visibleStartIndex / pageSize) * pageSize
|
const alignedStart = Math.floor(visibleStartIndex / pageSize) * pageSize
|
||||||
const alignedEnd = Math.ceil((visibleEndIndex + 1) / pageSize) * pageSize
|
const alignedEnd = Math.ceil((visibleEndIndex + 1) / pageSize) * pageSize
|
||||||
|
|
||||||
console.log('基于滚动位置的加载范围:', alignedStart, alignedEnd)
|
// console.log('基于滚动位置的加载范围:', alignedStart, alignedEnd)
|
||||||
|
|
||||||
// 加载可见范围内的数据段
|
// 加载可见范围内的数据段
|
||||||
for (let i = alignedStart; i < alignedEnd; i += pageSize) {
|
for (let i = alignedStart; i < alignedEnd; i += pageSize) {
|
||||||
if (i < totalCount.value) {
|
if (i < totalCount.value) {
|
||||||
console.log('滚动位置触发加载范围:', i, 'to', i + pageSize - 1)
|
// console.log('滚动位置触发加载范围:', i, 'to', i + pageSize - 1)
|
||||||
loadRange(i, pageSize)
|
loadRange(i, pageSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -776,11 +883,15 @@ function onResize() {
|
|||||||
resetAndLoadFirst()
|
resetAndLoadFirst()
|
||||||
|
|
||||||
// 监听搜索和过滤条件变化,重新加载数据
|
// 监听搜索和过滤条件变化,重新加载数据
|
||||||
watch([searchQuery, filters], () => {
|
watch(
|
||||||
// 搜索条件变化时清理选中状态,避免状态不一致
|
[searchQuery, filters],
|
||||||
checkedRowKeys.value = []
|
() => {
|
||||||
resetAndLoadFirst()
|
// 搜索条件变化时清理选中状态,避免状态不一致
|
||||||
}, { deep: true })
|
checkedRowKeys.value = []
|
||||||
|
resetAndLoadFirst()
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
// 全选相关计算属性
|
// 全选相关计算属性
|
||||||
const allSelected = computed({
|
const allSelected = computed({
|
||||||
@@ -789,30 +900,30 @@ const allSelected = computed({
|
|||||||
if (currentValidRules.length === 0) {
|
if (currentValidRules.length === 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查当前已加载数据中有多少被选中
|
// 检查当前已加载数据中有多少被选中
|
||||||
const selectedInCurrentData = currentValidRules.filter(rule =>
|
const selectedInCurrentData = currentValidRules.filter((rule) =>
|
||||||
checkedRowKeys.value.includes(rule.id)
|
checkedRowKeys.value.includes(rule.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
return selectedInCurrentData.length === currentValidRules.length
|
return selectedInCurrentData.length === currentValidRules.length
|
||||||
},
|
},
|
||||||
set: (value: boolean) => {
|
set: (value: boolean) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
// 选中当前已加载的所有有效数据
|
// 选中当前已加载的所有有效数据
|
||||||
const currentValidRules = currentLoadedRules.value
|
const currentValidRules = currentLoadedRules.value
|
||||||
const currentIds = currentValidRules.map(rule => rule.id)
|
const currentIds = currentValidRules.map((rule) => rule.id)
|
||||||
|
|
||||||
// 合并现有选中和当前页选中(去重)
|
// 合并现有选中和当前页选中(去重)
|
||||||
const mergedIds = [...new Set([...checkedRowKeys.value, ...currentIds])]
|
const mergedIds = [...new Set([...checkedRowKeys.value, ...currentIds])]
|
||||||
checkedRowKeys.value = mergedIds
|
checkedRowKeys.value = mergedIds
|
||||||
} else {
|
} else {
|
||||||
// 取消选中当前已加载的数据
|
// 取消选中当前已加载的数据
|
||||||
const currentValidRules = currentLoadedRules.value
|
const currentValidRules = currentLoadedRules.value
|
||||||
const currentIds = new Set(currentValidRules.map(rule => rule.id))
|
const currentIds = new Set(currentValidRules.map((rule) => rule.id))
|
||||||
|
|
||||||
// 只保留不在当前页的选中项
|
// 只保留不在当前页的选中项
|
||||||
checkedRowKeys.value = checkedRowKeys.value.filter(id => !currentIds.has(id))
|
checkedRowKeys.value = checkedRowKeys.value.filter((id) => !currentIds.has(id))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -822,12 +933,12 @@ const indeterminate = computed(() => {
|
|||||||
if (currentValidRules.length === 0) {
|
if (currentValidRules.length === 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查当前已加载数据中有多少被选中
|
// 检查当前已加载数据中有多少被选中
|
||||||
const selectedInCurrentData = currentValidRules.filter(rule =>
|
const selectedInCurrentData = currentValidRules.filter((rule) =>
|
||||||
checkedRowKeys.value.includes(rule.id)
|
checkedRowKeys.value.includes(rule.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
return selectedInCurrentData.length > 0 && selectedInCurrentData.length < currentValidRules.length
|
return selectedInCurrentData.length > 0 && selectedInCurrentData.length < currentValidRules.length
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -838,23 +949,57 @@ const openRuleForm = (rule: unknown = null) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const editRule = (rule: unknown) => {
|
const editRule = (rule: unknown) => {
|
||||||
currentEditRule.value = { ...rule }
|
const ruleToEdit = rule as FirewallRuleShow
|
||||||
|
currentEditRule.value = { ...ruleToEdit }
|
||||||
|
originalRuleName.value = ruleToEdit.name // 记录原始名称用于API调用
|
||||||
showRuleForm.value = true
|
showRuleForm.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteRule = async (id: string) => {
|
const deleteRule = async (id: string) => {
|
||||||
// TODO: 实现删除规则API调用
|
// 从本地数据中找到对应的规则
|
||||||
console.log('删除规则:', id)
|
const ruleToDelete = firewallRule.value.find((rule) => rule?.id === id)
|
||||||
|
if (!ruleToDelete) {
|
||||||
// 从本地数据中移除(临时方案,应该调用API)
|
message.error('找不到要删除的规则')
|
||||||
const index = firewallRule.value.findIndex((rule) => rule?.id === id)
|
return
|
||||||
if (index !== -1) {
|
|
||||||
firewallRule.value[index] = null // 设为null而不是删除,保持索引不变
|
|
||||||
totalCount.value = Math.max(0, totalCount.value - 1)
|
|
||||||
// 从选中项中移除
|
|
||||||
checkedRowKeys.value = checkedRowKeys.value.filter((key) => key !== id)
|
|
||||||
message.success('规则删除成功')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示确认对话框
|
||||||
|
dialog.warning({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `您确定要删除防火墙规则"${ruleToDelete.name}"吗?此操作不可撤销。`,
|
||||||
|
positiveText: '确认删除',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
console.log('删除规则:', ruleToDelete.name)
|
||||||
|
|
||||||
|
// 调用删除API
|
||||||
|
const res: ApiResponse<null> = await httpClient.delete(
|
||||||
|
`/firewall/rules?name=${encodeURIComponent(ruleToDelete.name)}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!res.success) {
|
||||||
|
const errorMsg = StringUtils.isBlank(res.message) ? '删除规则失败!' : res.message
|
||||||
|
message.error(errorMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从本地数据中移除
|
||||||
|
const index = firewallRule.value.findIndex((rule) => rule?.id === id)
|
||||||
|
if (index !== -1) {
|
||||||
|
firewallRule.value[index] = null // 设为null而不是删除,保持索引不变
|
||||||
|
deletedIndices.add(index) // 标记这个索引为已删除
|
||||||
|
totalCount.value = Math.max(0, totalCount.value - 1)
|
||||||
|
// 从选中项中移除
|
||||||
|
checkedRowKeys.value = checkedRowKeys.value.filter((key) => key !== id)
|
||||||
|
message.success('规则删除成功')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除规则失败:', error)
|
||||||
|
message.error('删除规则时发生错误')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRuleCheck = (id: string, checked: boolean) => {
|
const handleRuleCheck = (id: string, checked: boolean) => {
|
||||||
@@ -886,24 +1031,60 @@ const clearFilters = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSaveRule = async (rule: unknown) => {
|
const handleSaveRule = async (rule: unknown) => {
|
||||||
// TODO: 实现保存规则API调用
|
try {
|
||||||
console.log('保存规则:', rule)
|
const formRule = rule as FirewallRuleShow
|
||||||
|
console.log('保存规则:', formRule)
|
||||||
|
|
||||||
if (rule.id) {
|
if (formRule.id && originalRuleName.value) {
|
||||||
// 编辑现有规则
|
// 编辑现有规则
|
||||||
const index = firewallRule.value.findIndex((r) => r?.id === rule.id)
|
console.log('更新规则:', originalRuleName.value, '→', formRule.name)
|
||||||
if (index !== -1) {
|
|
||||||
firewallRule.value[index] = rule as FirewallRuleShow
|
const updateRequest = convertToUpdateRequest(formRule, originalRuleName.value)
|
||||||
message.success('规则更新成功')
|
const res: ApiResponse<null> = await httpClient.put('/firewall/rules', updateRequest)
|
||||||
|
|
||||||
|
if (!res.success) {
|
||||||
|
const errorMsg = StringUtils.isBlank(res.message) ? '更新规则失败!' : res.message
|
||||||
|
// 调用表单组件的错误处理方法,不关闭弹窗
|
||||||
|
ruleFormRef.value?.handleSaveError(errorMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新本地数据
|
||||||
|
const index = firewallRule.value.findIndex((r) => r?.id === formRule.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
firewallRule.value[index] = formRule
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用表单组件的成功处理方法,关闭弹窗
|
||||||
|
ruleFormRef.value?.handleSaveSuccess()
|
||||||
|
} else {
|
||||||
|
// 创建新规则
|
||||||
|
console.log('创建新规则:', formRule.name)
|
||||||
|
|
||||||
|
const createRequest = convertToCreateRequest(formRule)
|
||||||
|
const res: ApiResponse<null> = await httpClient.post('/firewall/rules', createRequest)
|
||||||
|
|
||||||
|
if (!res.success) {
|
||||||
|
const errorMsg = StringUtils.isBlank(res.message) ? '创建规则失败!' : res.message
|
||||||
|
// 调用表单组件的错误处理方法,不关闭弹窗
|
||||||
|
ruleFormRef.value?.handleSaveError(errorMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用表单组件的成功处理方法,关闭弹窗
|
||||||
|
ruleFormRef.value?.handleSaveSuccess()
|
||||||
|
// 重新加载数据以获取新的总数和数据
|
||||||
|
resetAndLoadFirst()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 添加新规则 - 这里需要重新加载数据,因为数组大小固定
|
|
||||||
message.success('规则创建成功,正在刷新数据...')
|
|
||||||
resetAndLoadFirst() // 重新加载以获取新的总数和数据
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置状态
|
// 重置状态
|
||||||
currentEditRule.value = null
|
currentEditRule.value = null
|
||||||
|
originalRuleName.value = ''
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存规则失败:', error)
|
||||||
|
// 调用表单组件的错误处理方法,不关闭弹窗
|
||||||
|
ruleFormRef.value?.handleSaveError('保存规则时发生错误')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -1790,7 +1971,9 @@ const handleSaveRule = async (rule: unknown) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 调试信息样式 */
|
/* 调试信息样式 */
|
||||||
.loading-info, .no-data, .data-info {
|
.loading-info,
|
||||||
|
.no-data,
|
||||||
|
.data-info {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
@@ -1814,6 +1997,16 @@ const handleSaveRule = async (rule: unknown) => {
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-placeholder.deleted-item {
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
border-left: 3px solid var(--accent-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-placeholder.deleted-item .loading-content {
|
||||||
|
color: var(--accent-error);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.loading-content {
|
.loading-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in new issue
Block a user