diff --git a/src/api/ops/collection.ts b/src/api/ops/collection.ts index 3e6b725..cb45f7e 100644 --- a/src/api/ops/collection.ts +++ b/src/api/ops/collection.ts @@ -71,6 +71,12 @@ export interface CollectorNode { capabilities: string version?: string queue_depth: number + spool_bytes: number + max_concurrency: number + execution_timeout_seconds: number + running_tasks: number + pending_tasks: number + oldest_pending_at?: string last_heartbeat_at?: string last_error?: string enrollment_expires_at?: string @@ -83,6 +89,8 @@ export interface CollectorNodeInput { region_code?: string description?: string capabilities: string + max_concurrency: number + execution_timeout_seconds: number enabled?: boolean } @@ -98,6 +106,7 @@ export interface CollectionBinding { target: string port: number interval_seconds: number + inventory_interval_seconds: number retry_policy: string enabled: boolean state: string @@ -117,20 +126,21 @@ export interface CollectionBindingInput { target: string port: number interval_seconds: number + inventory_interval_seconds: number retry_policy?: string enabled?: boolean } export interface HostAgentRegistration { - id: number - agent_id: string - resource_uid: string - claimed_resource_uid: string - hostname: string - version: string - status: 'confirmed' | 'pending_confirmation' - last_error: string - last_seen_at: string + id: number + agent_id: string + resource_uid: string + claimed_resource_uid: string + hostname: string + version: string + status: 'confirmed' | 'pending_confirmation' + last_error: string + last_seen_at: string } export const fetchCredentials = (params?: Record) => @@ -158,16 +168,22 @@ export const deleteCollectorNode = (id: number) => request.delete(`${PREFIX}/col export const rotateCollectorNodeToken = (id: number) => request.post(`${PREFIX}/collector-nodes/${id}/installation-token`) export const fetchHostAgentRegistrations = (params?: Record) => - request.get<{ code: number; details?: PageResult; message?: string }>(`${PREFIX}/host-agents`, { params }) + request.get<{ code: number; details?: PageResult; message?: string }>(`${PREFIX}/host-agents`, { params }) export const confirmHostAgentRegistration = (id: number, resourceUid: string) => - request.post(`${PREFIX}/host-agents/${id}/confirm`, { resource_uid: resourceUid }) + request.post(`${PREFIX}/host-agents/${id}/confirm`, { resource_uid: resourceUid }) export const fetchCollectionBindings = (params?: Record) => request.get<{ code: number; details?: PageResult; message?: string }>(`${PREFIX}/bindings`, { params }) export const createCollectionBinding = (data: CollectionBindingInput) => request.post(`${PREFIX}/bindings`, data) export const updateCollectionBinding = (id: number, data: CollectionBindingInput) => request.put(`${PREFIX}/bindings/${id}`, data) +export const batchUpdateCollectionBindings = (data: { + ids: number[] + interval_seconds?: number + inventory_interval_seconds?: number + enabled?: boolean +}) => request.put(`${PREFIX}/bindings/batch`, data) export const deleteCollectionBinding = (id: number) => request.delete(`${PREFIX}/bindings/${id}`) export const fetchDiscoveryRuns = (params?: Record) => diff --git a/src/api/ops/netarchTopo.ts b/src/api/ops/netarchTopo.ts index fd3cb33..3a84e3f 100644 --- a/src/api/ops/netarchTopo.ts +++ b/src/api/ops/netarchTopo.ts @@ -26,6 +26,18 @@ export interface Topology { updated_at?: string } +export interface TopologyDiscoveryRun { + id: number + topology_id: number + status: 'running' | 'succeeded' | 'failed' + total_tasks: number + completed_tasks: number + failed_tasks: number + started_at: string + completed_at?: string + last_error?: string +} + /** 链路对象 */ export interface Link { id?: number @@ -188,7 +200,11 @@ export const updateTopology = (data: Partial & { id: number }) => requ export const deleteTopology = (id: number) => request.delete(`/DC-Control/v1/topologies/${id}`) /** 触发拓扑发现 */ -export const discoverTopology = (id: number) => request.post(`/DC-Control/v1/topologies/${id}/discover`) +export const discoverTopology = (id: number) => + request.post<{ code: number; details?: TopologyDiscoveryRun; message?: string }>(`/DC-Control/v1/topologies/${id}/discover`) + +export const fetchLatestTopologyDiscoveryRun = (id: number) => + request.get<{ code: number; details?: TopologyDiscoveryRun | null; message?: string }>(`/DC-Control/v1/topologies/${id}/discovery-run`) /** 获取拓扑视图(包含拓扑和链路) */ export const fetchTopologyView = (id: number) => request.get(`/DC-Control/v1/topologies/${id}/view`) @@ -216,10 +232,9 @@ export const createNode = (topologyId: number, data: Partial) => /** 将已有统一设备引用加入手工拓扑 */ export const addResourceNodes = (topologyId: number, resource_uids: string[]) => - request.post<{ code: number; details?: { message: string; added: number } }>( - `/DC-Control/v1/topologies/${topologyId}/resource-nodes`, - { resource_uids } - ) + request.post<{ code: number; details?: { message: string; added: number } }>(`/DC-Control/v1/topologies/${topologyId}/resource-nodes`, { + resource_uids, + }) /** 更新节点 */ export const updateNode = (topologyId: number, nodeId: string, data: Partial) => diff --git a/src/views/ops/pages/collection/bindings/index.vue b/src/views/ops/pages/collection/bindings/index.vue index fb2c0e6..6a506e2 100644 --- a/src/views/ops/pages/collection/bindings/index.vue +++ b/src/views/ops/pages/collection/bindings/index.vue @@ -1,17 +1,64 @@ @@ -19,7 +66,12 @@ import { onMounted, reactive, ref } from 'vue' import { Message } from '@arco-design/web-vue' import type { TableColumnData } from '@arco-design/web-vue' -import { deleteCollectionBinding, fetchCollectionBindings, type CollectionBinding } from '@/api/ops/collection' +import { + batchUpdateCollectionBindings, + deleteCollectionBinding, + fetchCollectionBindings, + type CollectionBinding, +} from '@/api/ops/collection' import BindingFormModal from '../components/BindingFormModal.vue' const rows = ref([]) @@ -27,22 +79,90 @@ const current = ref(null) const visible = ref(false) const loading = ref(false) const keyword = ref('') +const selectedKeys = ref([]) +const batchVisible = ref(false) +const batchSaving = ref(false) +const batchForm = reactive<{ interval_seconds?: number; inventory_interval_seconds?: number; enabled?: boolean }>({}) const pagination = reactive({ current: 1, pageSize: 20, total: 0 }) const columns: TableColumnData[] = [ - { title: '绑定名称', dataIndex: 'name' }, { title: '设备 UID', dataIndex: 'resource_uid', ellipsis: true, tooltip: true }, { title: '采集节点', slotName: 'node' }, - { title: '凭证', slotName: 'credential' }, { title: '厂商模板', slotName: 'template' }, { title: '目标', slotName: 'target', minWidth: 210 }, - { title: '间隔(秒)', dataIndex: 'interval_seconds', width: 100 }, { title: '状态', slotName: 'state', width: 100 }, { title: '操作', slotName: 'actions', width: 140, fixed: 'right' }, + { title: '绑定名称', dataIndex: 'name' }, + { title: '设备 UID', dataIndex: 'resource_uid', ellipsis: true, tooltip: true }, + { title: '采集节点', slotName: 'node' }, + { title: '凭证', slotName: 'credential' }, + { title: '厂商模板', slotName: 'template' }, + { title: '目标', slotName: 'target', minWidth: 210 }, + { title: '实时指标间隔(秒)', dataIndex: 'interval_seconds', width: 150 }, + { title: '资产清单间隔(秒)', slotName: 'inventoryInterval', width: 150 }, + { title: '状态', slotName: 'state', width: 100 }, + { title: '操作', slotName: 'actions', width: 140, fixed: 'right' }, ] -const displayTarget = (record: CollectionBinding) => record.target.includes('://') ? `${record.target}(端口 ${record.port})` : `${record.protocol}://${record.target}:${record.port}` -const loadData = async () => { loading.value = true; try { const res = await fetchCollectionBindings({ page: pagination.current, size: pagination.pageSize, keyword: keyword.value || undefined }); rows.value = res.details?.data || []; pagination.total = res.details?.total || 0 } finally { loading.value = false } } -const onPageChange = (page: number) => { pagination.current = page; loadData() } -const openCreate = () => { current.value = null; visible.value = true } -const openEdit = (row: CollectionBinding) => { current.value = row; visible.value = true } -const remove = async (row: CollectionBinding) => { const res: any = await deleteCollectionBinding(row.id); if (res.code === 0) { Message.success('已删除'); await loadData() } else Message.error(res.message || '删除失败') } +const displayTarget = (record: CollectionBinding) => + record.target.includes('://') ? `${record.target}(端口 ${record.port})` : `${record.protocol}://${record.target}:${record.port}` +const loadData = async () => { + loading.value = true + try { + const res = await fetchCollectionBindings({ page: pagination.current, size: pagination.pageSize, keyword: keyword.value || undefined }) + rows.value = res.details?.data || [] + pagination.total = res.details?.total || 0 + } finally { + loading.value = false + } +} +const onPageChange = (page: number) => { + pagination.current = page + loadData() +} +const openCreate = () => { + current.value = null + visible.value = true +} +const openEdit = (row: CollectionBinding) => { + current.value = row + visible.value = true +} +const openBatch = () => { + Object.assign(batchForm, { interval_seconds: undefined, inventory_interval_seconds: undefined, enabled: undefined }) + batchVisible.value = true +} +const saveBatch = async () => { + if (!selectedKeys.value.length) return + if (batchForm.interval_seconds === undefined && batchForm.inventory_interval_seconds === undefined && batchForm.enabled === undefined) { + Message.warning('请至少填写一项修改内容') + return false + } + batchSaving.value = true + try { + const res: any = await batchUpdateCollectionBindings({ ids: selectedKeys.value, ...batchForm }) + if (res.code !== 0) { + Message.error(res.message || '批量修改失败') + return false + } + Message.success(`已修改 ${res.details?.updated || selectedKeys.value.length} 条绑定`) + batchVisible.value = false + selectedKeys.value = [] + await loadData() + } finally { + batchSaving.value = false + } +} +const remove = async (row: CollectionBinding) => { + const res: any = await deleteCollectionBinding(row.id) + if (res.code === 0) { + Message.success('已删除') + await loadData() + } else Message.error(res.message || '删除失败') +} onMounted(loadData) diff --git a/src/views/ops/pages/collection/components/BindingFormModal.vue b/src/views/ops/pages/collection/components/BindingFormModal.vue index 4798b56..7bee2e3 100644 --- a/src/views/ops/pages/collection/components/BindingFormModal.vue +++ b/src/views/ops/pages/collection/components/BindingFormModal.vue @@ -1,18 +1,78 @@