This commit is contained in:
2026-04-11 22:22:07 +08:00
parent c72af0bfa7
commit 5f4111aeb1
7 changed files with 65 additions and 93 deletions

View File

@@ -28,8 +28,12 @@
<a-row :gutter="20">
<a-col :span="12">
<a-form-item field="server_identity" label="服务器标识">
<a-input v-model="formData.server_identity" placeholder="请输入服务器标识" />
<a-form-item field="server_identity" label="服务器">
<a-select v-model="formData.server_identity" placeholder="请选择服务器" allow-search allow-clear>
<a-option v-for="server in serverOptions" :key="server.server_identity" :value="server.server_identity">
{{ server.name }} ({{ server.server_identity }})
</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
@@ -102,6 +106,7 @@ import { Message } from '@arco-design/web-vue'
import type { FormInstance } from '@arco-design/web-vue'
import { createSecurityService, updateSecurityService, SECURITY_TYPE_OPTIONS, type SecurityServiceFormData } from '@/api/ops/security'
import { fetchPolicyOptions, type PolicyOptionItem } from '@/api/ops/alertPolicy'
import { fetchServerList, type ServerItem } from '@/api/ops/server'
interface Props {
visible: boolean
@@ -117,6 +122,7 @@ const emit = defineEmits(['update:visible', 'success'])
const formRef = ref<FormInstance>()
const confirmLoading = ref(false)
const policyOptions = ref<PolicyOptionItem[]>([])
const serverOptions = ref<ServerItem[]>([])
const isEdit = computed(() => !!props.record?.id)
@@ -158,6 +164,20 @@ const loadPolicyOptions = async () => {
}
}
const loadServerOptions = async () => {
try {
const response: any = await fetchServerList({ page: 1, size: 1000 })
if (response && response.details) {
serverOptions.value = response.details.data || []
} else {
serverOptions.value = []
}
} catch (error) {
console.error('加载服务器列表失败:', error)
serverOptions.value = []
}
}
watch(
() => props.visible,
(val) => {
@@ -253,5 +273,6 @@ const handleCancel = () => {
onMounted(() => {
loadPolicyOptions()
loadServerOptions()
})
</script>

View File

@@ -30,10 +30,9 @@ export const columns = [
},
{
dataIndex: 'server_identity',
title: '服务器标识',
title: '服务器',
width: 150,
ellipsis: true,
tooltip: true,
slotName: 'serverIdentity',
},
{
dataIndex: 'enabled',

View File

@@ -25,6 +25,10 @@
</a-button>
</template>
<template #serverIdentity="{ record }">
<span>{{ getServerName(record.server_identity) }}</span>
</template>
<template #enabled="{ record }">
<a-tag :color="record.enabled ? 'green' : 'gray'">
{{ record.enabled ? '已启用' : '已禁用' }}
@@ -124,9 +128,11 @@ import {
type SecurityServiceItem,
type SecurityServiceListParams,
} from '@/api/ops/security'
import { fetchServerList, type ServerItem } from '@/api/ops/server'
const loading = ref(false)
const tableData = ref<SecurityServiceItem[]>([])
const serverOptions = ref<ServerItem[]>([])
const formDialogVisible = ref(false)
const quickConfigVisible = ref(false)
const detailVisible = ref(false)
@@ -277,6 +283,26 @@ const getStatusColor = (status: string) => {
return colorMap[status] || 'gray'
}
const getServerName = (serverIdentity: string) => {
if (!serverIdentity) return '-'
const server = serverOptions.value.find((s) => s.server_identity === serverIdentity)
return server ? `${server.name}` : serverIdentity
}
const loadServerOptions = async () => {
try {
const response: any = await fetchServerList({ page: 1, size: 1000 })
if (response && response.details) {
serverOptions.value = response.details.data || []
} else {
serverOptions.value = []
}
} catch (error) {
console.error('加载服务器列表失败:', error)
serverOptions.value = []
}
}
const formatTime = (time?: string) => {
if (!time) return '-'
const date = new Date(time)
@@ -289,6 +315,7 @@ const formatTime = (time?: string) => {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
loadServerOptions()
fetchSecurityServiceData()
</script>

View File

@@ -30,19 +30,10 @@
<a-doption value="server">服务器</a-doption>
<a-doption value="switch">交换机</a-doption>
<a-doption value="router">路由器</a-doption>
<a-doption value="firewall">防火墙</a-doption>
<a-doption value="storage">存储</a-doption>
</template>
</a-dropdown>
</a-button-group>
<a-tooltip v-if="props.onBatchImportAssets" content="按资产 ID 批量导入(绑定 ref_type=asset">
<a-button type="outline" size="small" @click="props.onBatchImportAssets">
<icon-import :size="18" />
<span class="btn-text">资产导入</span>
</a-button>
</a-tooltip>
<!-- 布局 -->
<a-button-group type="outline" size="small">
<a-dropdown trigger="click" @select="props.onLayout">
@@ -108,27 +99,23 @@ import {
IconRefresh,
IconDownload,
IconRotateLeft,
IconImport,
} from '@arco-design/web-vue/es/icon';
} from '@arco-design/web-vue/es/icon'
interface Props {
onZoomIn: () => void;
onZoomOut: () => void;
onFitView: () => void;
onAddDevice: (value: string | number | Record<string, any> | undefined) => void;
onLayout: (value: string | number | Record<string, any> | undefined) => void;
onEdgeStyle: (value: string | number | Record<string, any> | undefined) => void;
onRefresh: () => void;
onExport: () => void;
onReset?: () => void;
/** 打开「批量导入资产」对话框(无拓扑 ID 时不传) */
onBatchImportAssets?: () => void;
onZoomIn: () => void
onZoomOut: () => void
onFitView: () => void
onAddDevice: (value: string | number | Record<string, any> | undefined) => void
onLayout: (value: string | number | Record<string, any> | undefined) => void
onEdgeStyle: (value: string | number | Record<string, any> | undefined) => void
onRefresh: () => void
onExport: () => void
onReset?: () => void
}
const props = withDefaults(defineProps<Props>(), {
onReset: undefined,
onBatchImportAssets: undefined,
});
})
</script>
<style scoped lang="less">

View File

@@ -25,8 +25,6 @@ export const DEVICE_TYPE_CONFIG: Record<DeviceType, { icon: any; label: string;
/** 扩展设备类型配置(包含更多设备) */
export const EXTENDED_DEVICE_CONFIG = {
...DEVICE_TYPE_CONFIG,
firewall: { icon: IconShield, label: '防火墙', color: '#DC2626' },
storage: { icon: IconDatabase, label: '存储设备', color: '#7C3AED' },
mobile: { icon: IconDeviceMobile, label: '移动设备', color: '#EC4899' },
}

View File

@@ -27,7 +27,6 @@
@refresh="refreshTopology"
@export="exportTopology"
@reset="resetTopology"
:on-batch-import-assets="currentTopologyId ? openBatchImportAssets : undefined"
/>
<!-- Vue Flow 画布 -->
@@ -46,7 +45,7 @@
>
<background pattern-color="#aaa" :gap="16" />
<mini-map :node-color="getNodeColor" node-stroke-color="#555" />
<controls />
<!-- <controls /> -->
</vue-flow>
</div>
</div>
@@ -86,26 +85,6 @@
/>
<delete-confirm-dialog v-model:visible="deleteEdgeDialogOpen" node-name="链路" @confirm="handleDeleteEdgeConfirm" />
<a-modal
v-model:visible="batchImportAssetsOpen"
title="批量导入资产节点"
@ok="handleBatchImportAssetsConfirm"
@cancel="batchImportAssetsOpen = false"
>
<p class="text-muted" style="margin-bottom: 8px; font-size: 12px; color: var(--color-text-3)">
调用 DC-Control
<code>/topologies/:id/nodes/batch-import</code>
节点将绑定
<code>ref_type=asset</code>
</p>
<a-textarea
v-model="batchImportAssetIdsText"
placeholder="请输入资产 ID逗号或换行分隔例如1,2,3"
:auto-size="{ minRows: 4, maxRows: 8 }"
/>
</a-modal>
</div>
</template>
@@ -180,45 +159,6 @@ const edgeActionDialogOpen = ref(false)
const edgeEditDialogOpen = ref(false)
const deleteEdgeDialogOpen = ref(false)
const batchImportAssetsOpen = ref(false)
const batchImportAssetIdsText = ref('')
const openBatchImportAssets = () => {
batchImportAssetIdsText.value = ''
batchImportAssetsOpen.value = true
}
const handleBatchImportAssetsConfirm = async () => {
const id = currentTopologyId.value
if (!id) {
Message.warning('请先通过路由选择拓扑(?id=')
batchImportAssetsOpen.value = false
return
}
const raw = batchImportAssetIdsText.value
.split(/[\s,;]+/)
.map((s) => s.trim())
.filter(Boolean)
const assetIds = [...new Set(raw.map((x) => parseInt(x, 10)).filter((n) => !Number.isNaN(n)))]
if (assetIds.length === 0) {
Message.warning('请输入至少一个有效的资产 ID')
return
}
try {
const res: any = await TopoAPI.batchImportAssetNodes(id, assetIds)
if (res?.code === 0) {
Message.success(`已导入 ${res.details?.imported ?? assetIds.length} 个节点请求已提交`)
batchImportAssetsOpen.value = false
await refreshTopology()
} else {
Message.error(res?.message || '导入失败')
}
} catch (e) {
console.error(e)
Message.error('导入请求失败')
}
}
// 布局钩子
const { applyLayout } = useTopoLayout()

File diff suppressed because one or more lines are too long