refactor: reuse snmp oid editor in device collect form

This commit is contained in:
zxr
2026-07-05 23:02:09 +08:00
parent aa6db97457
commit 58365870ef

View File

@@ -140,62 +140,11 @@
</a-form-item>
</a-col>
</a-row>
<a-form-item field="snmp_oids">
<template #label>
<div class="snmp-oid-label">
<span>SNMP OID配置</span>
<div class="snmp-oid-actions">
<a-button size="mini" type="text" @click="switchSnmpOidEditMode">
{{ snmpOidEditMode === 'table' ? 'JSON' : '表格' }}
</a-button>
<a-button v-if="snmpOidEditMode === 'table'" size="mini" type="primary" @click="addSnmpOidRow">
添加
</a-button>
</div>
</div>
</template>
<template v-if="snmpOidEditMode === 'table'">
<a-table :data="snmpOidRows" :pagination="false" size="small" class="snmp-oid-table">
<template #columns>
<a-table-column title="OID" data-index="oid" :width="220">
<template #cell="{ record }">
<a-input v-model="record.oid" @input="handleSnmpOidRowChange" />
</template>
</a-table-column>
<a-table-column title="指标名称" data-index="metric_name" :width="150">
<template #cell="{ record }">
<a-input v-model="record.metric_name" @input="handleSnmpOidRowChange" />
</template>
</a-table-column>
<a-table-column title="指标unit" data-index="metric_unit" :width="120">
<template #cell="{ record }">
<a-input v-model="record.metric_unit" @input="handleSnmpOidRowChange" />
</template>
</a-table-column>
<a-table-column title="类型" data-index="type" :width="100">
<template #cell="{ record }">
<a-input v-model="record.type" @input="handleSnmpOidRowChange" />
</template>
</a-table-column>
<a-table-column title="操作" :width="80">
<template #cell="{ rowIndex }">
<a-button size="mini" type="text" status="danger" @click="removeSnmpOidRow(rowIndex)">
删除
</a-button>
</template>
</a-table-column>
</template>
</a-table>
</template>
<a-textarea
v-else
v-model="formData.snmp_oids"
:rows="5"
placeholder='可留空使用默认模板,或填写如 [{"oid":"1.3.6.1.2.1.1.3.0","metric_name":"sys_uptime"}]'
/>
</a-form-item>
<SnmpOidEditor
ref="snmpOidEditorRef"
v-model="formData.snmp_oids"
:reset-key="snmpOidEditorResetKey"
/>
</template>
<a-row :gutter="20">
@@ -243,6 +192,7 @@ import {
} from '@/api/ops/room-device'
import { fetchPolicyOptions, type PolicyOptionItem } from '@/api/ops/alertPolicy'
import { fetchRoomOptions, type RoomOptionItem } from '@/api/ops/room'
import SnmpOidEditor from '../../components/SnmpOidEditor.vue'
interface Props {
visible: boolean
@@ -256,22 +206,12 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits(['update:visible', 'success'])
const formRef = ref<FormInstance>()
const snmpOidEditorRef = ref<InstanceType<typeof SnmpOidEditor>>()
const snmpOidEditorResetKey = ref(0)
const confirmLoading = ref(false)
const policyOptions = ref<PolicyOptionItem[]>([])
const roomOptions = ref<RoomOptionItem[]>([])
type SnmpOidEditMode = 'table' | 'json'
interface SnmpOidRow {
oid: string
metric_name: string
metric_unit: string
type: string
}
const snmpOidEditMode = ref<SnmpOidEditMode>('table')
const snmpOidRows = ref<SnmpOidRow[]>([])
const isEdit = computed(() => !!props.record?.id)
const formData = reactive({
@@ -307,96 +247,6 @@ const rules = {
device_category: [{ required: true, message: '请选择设备分类' }],
}
const createEmptySnmpOidRow = (): SnmpOidRow => ({
oid: '',
metric_name: '',
metric_unit: '',
type: '',
})
const snmpOidTableFields = ['oid', 'metric_name', 'metric_unit', 'type']
const normalizeSnmpOidRows = (rows: SnmpOidRow[]) =>
rows
.map((row) => ({
oid: row.oid.trim(),
metric_name: row.metric_name.trim(),
metric_unit: row.metric_unit.trim(),
type: row.type.trim(),
}))
.filter((row) => row.oid || row.metric_name || row.metric_unit || row.type)
const findIncompleteSnmpOidRowIndex = (rows: SnmpOidRow[]) =>
rows.findIndex((row) => {
const normalizedRow = {
oid: row.oid.trim(),
metric_name: row.metric_name.trim(),
metric_unit: row.metric_unit.trim(),
type: row.type.trim(),
}
const hasAnyValue =
normalizedRow.oid || normalizedRow.metric_name || normalizedRow.metric_unit || normalizedRow.type
return Boolean(hasAnyValue && (!normalizedRow.oid || !normalizedRow.metric_name))
})
const parseSnmpOidJsonArray = (raw: string) => {
const text = raw.trim()
if (!text) {
return []
}
const parsed = JSON.parse(text)
if (!Array.isArray(parsed)) {
throw new Error('SNMP OID 配置必须是 JSON 数组')
}
return parsed
}
const hasSnmpOidTableUnsupportedFields = (raw: string) => {
const rows = parseSnmpOidJsonArray(raw)
return rows.some((item) => {
if (!item || typeof item !== 'object' || Array.isArray(item)) {
return true
}
return Object.keys(item).some((key) => !snmpOidTableFields.includes(key))
})
}
const findInvalidSnmpOidJsonRowIndex = (rows: unknown[]) =>
rows.findIndex((item) => {
if (!item || typeof item !== 'object' || Array.isArray(item)) {
return true
}
const row = item as Record<string, unknown>
return !String(row.oid ?? '').trim() || !String(row.metric_name ?? '').trim()
})
const stringifySnmpOidRows = (rows: SnmpOidRow[]) => {
const normalizedRows = normalizeSnmpOidRows(rows)
return normalizedRows.length ? JSON.stringify(normalizedRows, null, 2) : ''
}
const parseSnmpOidRows = (raw: string): SnmpOidRow[] => {
const text = raw.trim()
if (!text) {
return []
}
const parsed = parseSnmpOidJsonArray(text)
return parsed.map((item) => ({
oid: String(item?.oid ?? '').trim(),
metric_name: String(item?.metric_name ?? '').trim(),
metric_unit: String(item?.metric_unit ?? '').trim(),
type: String(item?.type ?? '').trim(),
}))
}
const syncSnmpOidRowsFromJson = () => {
snmpOidRows.value = parseSnmpOidRows(formData.snmp_oids || '')
}
const syncSnmpOidJsonFromRows = () => {
formData.snmp_oids = stringifySnmpOidRows(snmpOidRows.value)
}
const loadPolicyOptions = async () => {
try {
const response: any = await fetchPolicyOptions({ enabled: true })
@@ -460,18 +310,7 @@ watch(
collect_interval: props.record.collect_interval || 60,
policy_ids: props.record.policy_ids || [],
})
snmpOidEditMode.value = 'table'
try {
if (hasSnmpOidTableUnsupportedFields(formData.snmp_oids || '')) {
snmpOidEditMode.value = 'json'
snmpOidRows.value = []
} else {
syncSnmpOidRowsFromJson()
}
} catch {
snmpOidEditMode.value = 'json'
snmpOidRows.value = []
}
snmpOidEditorResetKey.value += 1
} else {
Object.assign(formData, {
name: '',
@@ -499,84 +338,12 @@ watch(
collect_interval: 60,
policy_ids: [],
})
snmpOidEditMode.value = 'table'
snmpOidRows.value = []
snmpOidEditorResetKey.value += 1
}
}
}
)
const addSnmpOidRow = () => {
if (snmpOidEditMode.value !== 'table') {
return
}
snmpOidRows.value.push(createEmptySnmpOidRow())
syncSnmpOidJsonFromRows()
}
const removeSnmpOidRow = (index: number) => {
snmpOidRows.value.splice(index, 1)
syncSnmpOidJsonFromRows()
}
const handleSnmpOidRowChange = () => {
syncSnmpOidJsonFromRows()
}
const switchSnmpOidEditMode = () => {
if (snmpOidEditMode.value === 'table') {
syncSnmpOidJsonFromRows()
snmpOidEditMode.value = 'json'
return
}
try {
if (hasSnmpOidTableUnsupportedFields(formData.snmp_oids)) {
Message.warning('当前 SNMP OID JSON 包含表格不支持的字段,请在 JSON 模式下编辑')
return
}
syncSnmpOidRowsFromJson()
snmpOidEditMode.value = 'table'
} catch {
Message.warning('SNMP OID 配置必须是合法 JSON 数组,修正后才能切换到表格模式')
}
}
const validateSnmpOidConfigBeforeSubmit = () => {
if (snmpOidEditMode.value === 'table') {
const invalidIndex = findIncompleteSnmpOidRowIndex(snmpOidRows.value)
if (invalidIndex >= 0) {
Message.warning(`SNMP OID 第 ${invalidIndex + 1} 行请填写 OID 和指标名称`)
return false
}
const rows = normalizeSnmpOidRows(snmpOidRows.value)
formData.snmp_oids = rows.length ? JSON.stringify(rows, null, 2) : ''
return true
}
if (!formData.snmp_oids?.trim()) {
snmpOidRows.value = []
formData.snmp_oids = ''
return true
}
try {
const parsedRows = parseSnmpOidJsonArray(formData.snmp_oids)
const invalidJsonIndex = findInvalidSnmpOidJsonRowIndex(parsedRows)
if (invalidJsonIndex >= 0) {
Message.warning(`SNMP OID 第 ${invalidJsonIndex + 1} 行请填写 OID 和指标名称`)
return false
}
const rows = parseSnmpOidRows(formData.snmp_oids)
const normalizedRows = normalizeSnmpOidRows(rows)
snmpOidRows.value = normalizedRows
formData.snmp_oids = JSON.stringify(parsedRows, null, 2)
return true
} catch {
Message.warning('SNMP OID 配置必须是合法 JSON 数组')
return false
}
}
const handleOk = async () => {
try {
await formRef.value?.validate()
@@ -606,7 +373,7 @@ const handleOk = async () => {
Message.warning('SNMP v2c 模式下请填写 community')
return
}
if (!validateSnmpOidConfigBeforeSubmit()) {
if (!snmpOidEditorRef.value?.validate()) {
return
}
}
@@ -705,21 +472,3 @@ onMounted(() => {
loadRoomOptions()
})
</script>
<style scoped>
.snmp-oid-label {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.snmp-oid-actions {
display: flex;
gap: 8px;
}
.snmp-oid-table {
width: 100%;
}
</style>