dashboard服务器详情页增加防火墙标签页并实现功能
This commit is contained in:
parent
330da4985e
commit
a46a1f6db8
606
dashboard/src2/components/JsiteServerFirewallRules.vue
Normal file
606
dashboard/src2/components/JsiteServerFirewallRules.vue
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- 标题和操作按钮 -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
@click="refreshRules"
|
||||||
|
:loading="$resources.firewallRules.loading"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<RefreshCwIcon class="h-4 w-4 mr-1" />
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="selectedRules.length > 0"
|
||||||
|
@click="batchDeleteRules"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="text-red-600 hover:text-red-700"
|
||||||
|
>
|
||||||
|
<Trash2Icon class="h-4 w-4 mr-1" />
|
||||||
|
删除选中 ({{ selectedRules.length }})
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
@click="addNewRow"
|
||||||
|
variant="solid"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<PlusIcon class="h-4 w-4 mr-1" />
|
||||||
|
添加规则
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="$resources.firewallRules.loading" class="flex items-center justify-center py-12">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<Loader2Icon class="h-5 w-5 animate-spin text-gray-500" />
|
||||||
|
<span class="text-gray-500">正在加载防火墙规则...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 错误状态 -->
|
||||||
|
<div v-else-if="$resources.firewallRules.error" class="rounded-md border border-red-200 bg-red-50 p-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<AlertCircleIcon class="h-5 w-5 text-red-400 mr-2" />
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-red-800">加载失败</h3>
|
||||||
|
<p class="text-sm text-red-700 mt-1">{{ $resources.firewallRules.error }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 防火墙规则列表 -->
|
||||||
|
<div v-else-if="!$resources.firewallRules.loading && !$resources.firewallRules.error" class="space-y-4">
|
||||||
|
<div class="overflow-hidden rounded-md border border-gray-200">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="rounded border-gray-300"
|
||||||
|
:checked="isAllSelected"
|
||||||
|
:indeterminate="isIndeterminate"
|
||||||
|
@change="toggleSelectAll"
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
规则ID
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
协议
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
端口范围
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
来源IP
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
备注
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
操作
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
|
<!-- 现有规则 -->
|
||||||
|
<tr v-for="(rule, index) in firewallRules" :key="rule.rule_id || `temp-${index}`" class="hover:bg-gray-50">
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="rounded border-gray-300"
|
||||||
|
:checked="selectedRules.includes(String(rule.rule_id))"
|
||||||
|
@change="toggleRuleSelection(rule.rule_id)"
|
||||||
|
:disabled="rule.isNew"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
{{ rule.rule_id || '-' }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
|
<!-- 协议编辑 -->
|
||||||
|
<div v-if="rule.editing" class="flex items-center space-x-2">
|
||||||
|
<select
|
||||||
|
v-model="rule.protocol"
|
||||||
|
class="w-20 px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
>
|
||||||
|
<option value="TCP">TCP</option>
|
||||||
|
<option value="UDP">UDP</option>
|
||||||
|
<option value="ICMP">ICMP</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
|
||||||
|
:class="{
|
||||||
|
'bg-blue-100 text-blue-800': rule.protocol === 'TCP',
|
||||||
|
'bg-green-100 text-green-800': rule.protocol === 'UDP',
|
||||||
|
'bg-yellow-100 text-yellow-800': rule.protocol === 'ICMP',
|
||||||
|
'bg-gray-100 text-gray-800': !rule.protocol
|
||||||
|
}">
|
||||||
|
{{ rule.protocol || '未知' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
|
<!-- 端口范围编辑 -->
|
||||||
|
<div v-if="rule.editing" class="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
v-model="rule.port"
|
||||||
|
type="text"
|
||||||
|
class="w-24 px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="端口范围"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sm text-gray-900">
|
||||||
|
{{ rule.port || '-' }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
|
<div class="text-sm text-gray-900 max-w-xs truncate" :title="rule.source_ip">
|
||||||
|
{{ rule.source_ip || '0.0.0.0/0' }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
|
<!-- 备注编辑 -->
|
||||||
|
<div v-if="rule.editing" class="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
v-model="rule.remark"
|
||||||
|
type="text"
|
||||||
|
class="w-32 px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="备注"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sm text-gray-500 max-w-xs truncate" :title="rule.remark">
|
||||||
|
{{ rule.remark || '-' }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm">
|
||||||
|
<div class="flex items-center space-x-1">
|
||||||
|
<Button
|
||||||
|
v-if="rule.editing"
|
||||||
|
@click="saveRule(rule)"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="text-green-600 hover:text-green-700"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="rule.editing"
|
||||||
|
@click="cancelEdit(rule)"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="text-gray-600 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="!rule.editing && !rule.isNew"
|
||||||
|
@click="deleteRule(rule)"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="text-red-600 hover:text-red-700"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div v-if="pagination.total > pagination.limit" class="flex flex-col items-center space-y-4">
|
||||||
|
<div class="text-sm text-gray-700">
|
||||||
|
显示第 {{ (pagination.pageno - 1) * pagination.limit + 1 }} -
|
||||||
|
{{ Math.min(pagination.pageno * pagination.limit, pagination.total) }} 条,
|
||||||
|
共 {{ pagination.total }} 条记录
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<Button
|
||||||
|
@click="changePage(pagination.pageno - 1)"
|
||||||
|
:disabled="pagination.pageno <= 1"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="px-3"
|
||||||
|
>
|
||||||
|
←
|
||||||
|
</Button>
|
||||||
|
<div class="flex items-center space-x-1">
|
||||||
|
<Button
|
||||||
|
v-for="page in getVisiblePages()"
|
||||||
|
:key="page"
|
||||||
|
@click="changePage(page)"
|
||||||
|
:class="[
|
||||||
|
'px-3 py-1 text-sm rounded',
|
||||||
|
page === pagination.pageno
|
||||||
|
? '!bg-[#1fc76f] text-white'
|
||||||
|
: 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
|
||||||
|
]"
|
||||||
|
:disabled="page === pagination.pageno"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
@click="changePage(pagination.pageno + 1)"
|
||||||
|
:disabled="pagination.pageno >= pagination.pagecount"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="px-3"
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-else-if="!$resources.firewallRules.loading && !$resources.firewallRules.error && firewallRules.length === 0" class="text-center py-12">
|
||||||
|
<ShieldIcon class="mx-auto h-12 w-12 text-gray-400" />
|
||||||
|
<h3 class="mt-2 text-sm font-medium text-gray-900">暂无防火墙规则</h3>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">开始添加您的第一个防火墙规则</p>
|
||||||
|
<div class="mt-6">
|
||||||
|
<Button @click="addNewRow" variant="solid">
|
||||||
|
<PlusIcon class="h-4 w-4 mr-1" />
|
||||||
|
添加规则
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getCachedDocumentResource, Badge, Button, createResource } from 'jingrow-ui';
|
||||||
|
import { h, defineAsyncComponent } from 'vue';
|
||||||
|
import { toast } from 'vue-sonner';
|
||||||
|
import { getToastErrorMessage } from '../utils/toast';
|
||||||
|
import { renderDialog, confirmDialog } from '../utils/components';
|
||||||
|
import RefreshCwIcon from '~icons/lucide/refresh-cw';
|
||||||
|
import PlusIcon from '~icons/lucide/plus';
|
||||||
|
import Loader2Icon from '~icons/lucide/loader-2';
|
||||||
|
import AlertCircleIcon from '~icons/lucide/alert-circle';
|
||||||
|
import EditIcon from '~icons/lucide/edit';
|
||||||
|
import Trash2Icon from '~icons/lucide/trash-2';
|
||||||
|
import ShieldIcon from '~icons/lucide/shield';
|
||||||
|
import SettingsIcon from '~icons/lucide/settings';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'JsiteServerFirewallRules',
|
||||||
|
props: ['server'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
firewallRules: [],
|
||||||
|
pagination: {
|
||||||
|
pageno: 1,
|
||||||
|
limit: 10,
|
||||||
|
total: 0,
|
||||||
|
pagecount: 0
|
||||||
|
},
|
||||||
|
selectedRules: [] // 用于存储选中的规则ID
|
||||||
|
};
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
firewallRules() {
|
||||||
|
return {
|
||||||
|
url: 'jcloud.api.aliyun_server_light.get_aliyun_firewall_rules',
|
||||||
|
params: {
|
||||||
|
instance_id: this.$jsiteServer.pg?.instance_id,
|
||||||
|
region_id: this.$jsiteServer.pg?.region
|
||||||
|
},
|
||||||
|
auto: true,
|
||||||
|
onSuccess: (response) => {
|
||||||
|
if (response.success && response.data) {
|
||||||
|
// 直接使用API返回的防火墙规则数据
|
||||||
|
const firewallRules = response.data.firewall_rules || [];
|
||||||
|
this.firewallRules = firewallRules.map(rule => ({
|
||||||
|
rule_id: rule.rule_id,
|
||||||
|
protocol: rule.protocol,
|
||||||
|
port: rule.port,
|
||||||
|
source_ip: rule.source_ip,
|
||||||
|
remark: rule.remark,
|
||||||
|
editing: false,
|
||||||
|
isNew: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 简单分页处理
|
||||||
|
const total = this.firewallRules.length;
|
||||||
|
const limit = this.pagination.limit;
|
||||||
|
const pageno = this.pagination.pageno;
|
||||||
|
const start = (pageno - 1) * limit;
|
||||||
|
const end = start + limit;
|
||||||
|
|
||||||
|
this.pagination = {
|
||||||
|
pageno: pageno,
|
||||||
|
limit: limit,
|
||||||
|
total: total,
|
||||||
|
pagecount: Math.ceil(total / limit)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 对当前页的数据进行切片
|
||||||
|
this.firewallRules = this.firewallRules.slice(start, end);
|
||||||
|
} else {
|
||||||
|
this.error = response.message || '获取防火墙规则失败';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
this.error = getToastErrorMessage(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 刷新规则
|
||||||
|
refreshRules() {
|
||||||
|
this.$resources.firewallRules.reload();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 切换页面
|
||||||
|
changePage(page) {
|
||||||
|
if (page >= 1 && page <= this.pagination.pagecount) {
|
||||||
|
this.pagination.pageno = page;
|
||||||
|
this.$resources.firewallRules.reload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 添加新行
|
||||||
|
addNewRow() {
|
||||||
|
const newRule = {
|
||||||
|
rule_id: null,
|
||||||
|
protocol: 'TCP',
|
||||||
|
port: '',
|
||||||
|
source_ip: '0.0.0.0/0',
|
||||||
|
remark: '',
|
||||||
|
editing: true,
|
||||||
|
isNew: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加新规则到最前面
|
||||||
|
this.firewallRules.unshift(newRule);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 保存规则
|
||||||
|
async saveRule(rule) {
|
||||||
|
if (!rule.protocol || !rule.port) {
|
||||||
|
toast.error('协议和端口不能为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = createResource({
|
||||||
|
url: 'jcloud.api.aliyun_server_light.create_aliyun_firewall_rule',
|
||||||
|
params: {
|
||||||
|
instance_id: this.$jsiteServer.pg.instance_id,
|
||||||
|
rule_protocol: rule.protocol,
|
||||||
|
port: rule.port,
|
||||||
|
remark: rule.remark,
|
||||||
|
region_id: this.$jsiteServer.pg.region
|
||||||
|
},
|
||||||
|
onSuccess: (response) => {
|
||||||
|
if (response.success) {
|
||||||
|
toast.success('防火墙规则保存成功');
|
||||||
|
rule.editing = false;
|
||||||
|
rule.isNew = false;
|
||||||
|
// 重新加载规则列表
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$resources.firewallRules.reload();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
toast.error(response.message || '保存防火墙规则失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(getToastErrorMessage(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request.submit();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('保存防火墙规则失败');
|
||||||
|
console.error('保存防火墙规则失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 取消编辑
|
||||||
|
cancelEdit(rule) {
|
||||||
|
if (rule.isNew) {
|
||||||
|
// 如果是新增规则,直接删除
|
||||||
|
const index = this.firewallRules.indexOf(rule);
|
||||||
|
if (index > -1) {
|
||||||
|
this.firewallRules.splice(index, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果是编辑中的规则,恢复其原始值
|
||||||
|
if (rule._original) {
|
||||||
|
Object.assign(rule, rule._original);
|
||||||
|
delete rule._original;
|
||||||
|
}
|
||||||
|
rule.editing = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除规则
|
||||||
|
deleteRule(rule) {
|
||||||
|
confirmDialog({
|
||||||
|
title: '删除防火墙规则',
|
||||||
|
message: `确定要删除这条防火墙规则吗?\n协议: ${rule.protocol}\n端口: ${rule.port}`,
|
||||||
|
primaryAction: {
|
||||||
|
label: '确定',
|
||||||
|
variant: 'solid',
|
||||||
|
class: 'bg-black text-white hover:bg-gray-800',
|
||||||
|
onClick: ({ hide }) => {
|
||||||
|
this.performDeleteRule(rule, hide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 执行删除规则
|
||||||
|
async performDeleteRule(rule, hide) {
|
||||||
|
try {
|
||||||
|
const request = createResource({
|
||||||
|
url: 'jcloud.api.aliyun_server_light.delete_aliyun_firewall_rules',
|
||||||
|
params: {
|
||||||
|
instance_id: this.$jsiteServer.pg.instance_id,
|
||||||
|
region_id: this.$jsiteServer.pg.region,
|
||||||
|
rule_ids: [rule.rule_id]
|
||||||
|
},
|
||||||
|
onSuccess: (response) => {
|
||||||
|
if (response.success) {
|
||||||
|
toast.success('防火墙规则删除成功');
|
||||||
|
hide();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$resources.firewallRules.reload();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
toast.error(response.message || '删除防火墙规则失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(getToastErrorMessage(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request.submit();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('删除防火墙规则失败');
|
||||||
|
console.error('删除防火墙规则失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 批量删除规则
|
||||||
|
async batchDeleteRules() {
|
||||||
|
if (this.selectedRules.length === 0) {
|
||||||
|
toast.info('请选择要删除的规则');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmDialog({
|
||||||
|
title: '批量删除防火墙规则',
|
||||||
|
message: `确定要删除选中的 ${this.selectedRules.length} 条防火墙规则吗?`,
|
||||||
|
primaryAction: {
|
||||||
|
label: '确定',
|
||||||
|
variant: 'solid',
|
||||||
|
class: 'bg-black text-white hover:bg-gray-800',
|
||||||
|
onClick: ({ hide }) => {
|
||||||
|
this.performBatchDeleteRules(this.selectedRules, hide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 执行批量删除规则
|
||||||
|
async performBatchDeleteRules(ruleIds, hide) {
|
||||||
|
const validRuleIds = ruleIds.filter(id => id);
|
||||||
|
|
||||||
|
if (validRuleIds.length === 0) {
|
||||||
|
toast.error('没有有效的规则可以删除');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = createResource({
|
||||||
|
url: 'jcloud.api.aliyun_server_light.delete_aliyun_firewall_rules',
|
||||||
|
params: {
|
||||||
|
instance_id: this.$jsiteServer.pg.instance_id,
|
||||||
|
region_id: this.$jsiteServer.pg.region,
|
||||||
|
rule_ids: validRuleIds
|
||||||
|
},
|
||||||
|
onSuccess: (response) => {
|
||||||
|
if (response.success) {
|
||||||
|
toast.success(response.message || `批量删除 ${validRuleIds.length} 条防火墙规则成功`);
|
||||||
|
hide();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$resources.firewallRules.reload();
|
||||||
|
}, 1000);
|
||||||
|
this.selectedRules = []; // 清空选中
|
||||||
|
} else {
|
||||||
|
toast.error(response.message || '批量删除防火墙规则失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(getToastErrorMessage(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request.submit();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('批量删除防火墙规则失败');
|
||||||
|
console.error('批量删除防火墙规则失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 切换规则选中状态
|
||||||
|
toggleRuleSelection(ruleId) {
|
||||||
|
if (!ruleId) return;
|
||||||
|
|
||||||
|
const ruleIdStr = String(ruleId);
|
||||||
|
const index = this.selectedRules.indexOf(ruleIdStr);
|
||||||
|
if (index > -1) {
|
||||||
|
this.selectedRules.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
this.selectedRules.push(ruleIdStr);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 全选/取消全选
|
||||||
|
toggleSelectAll(event) {
|
||||||
|
const checkbox = event.target;
|
||||||
|
if (checkbox.checked) {
|
||||||
|
this.selectedRules = this.firewallRules
|
||||||
|
.filter(rule => rule.rule_id && !rule.isNew)
|
||||||
|
.map(rule => String(rule.rule_id));
|
||||||
|
} else {
|
||||||
|
this.selectedRules = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取可见的页码
|
||||||
|
getVisiblePages() {
|
||||||
|
const current = this.pagination.pageno;
|
||||||
|
const total = this.pagination.pagecount;
|
||||||
|
const pages = [];
|
||||||
|
|
||||||
|
if (total <= 5) {
|
||||||
|
for (let i = 1; i <= total; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const start = Math.max(1, current - 2);
|
||||||
|
const end = Math.min(total, current + 2);
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
$jsiteServer() {
|
||||||
|
return getCachedDocumentResource('Jsite Server', this.server);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 判断是否全选
|
||||||
|
isAllSelected() {
|
||||||
|
const validRules = this.firewallRules.filter(rule => rule.rule_id && !rule.isNew);
|
||||||
|
return validRules.length > 0 && this.selectedRules.length === validRules.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 判断是否半选
|
||||||
|
isIndeterminate() {
|
||||||
|
const validRules = this.firewallRules.filter(rule => rule.rule_id && !rule.isNew);
|
||||||
|
return this.selectedRules.length > 0 && this.selectedRules.length < validRules.length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 组件会自动加载防火墙规则,因为resources设置了auto: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -272,6 +272,15 @@ export default {
|
|||||||
props: jsiteServer => {
|
props: jsiteServer => {
|
||||||
return { server: jsiteServer.pg?.name };
|
return { server: jsiteServer.pg?.name };
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '防火墙',
|
||||||
|
route: 'firewall',
|
||||||
|
type: 'Component',
|
||||||
|
component: defineAsyncComponent(() => import('../components/JsiteServerFirewallRules.vue')),
|
||||||
|
props: jsiteServer => {
|
||||||
|
return { server: jsiteServer.pg?.name };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user