143 lines
4.8 KiB
TypeScript
143 lines
4.8 KiB
TypeScript
import type { DiagramNode, DiagramEdge } from '@/types/network-diagram'
|
|
|
|
// Maps draw.io shape identifiers (substrings of style) → our device slugs
|
|
const DRAWIO_SHAPE_TO_SLUG: Array<[string, string]> = [
|
|
['cisco.routers.router', 'router'],
|
|
['cisco.routers', 'router'],
|
|
['cisco.switches.layer_3_switch', 'switch'],
|
|
['cisco.switches.workgroup_switch', 'switch'],
|
|
['cisco.switches', 'switch'],
|
|
['cisco.firewalls', 'firewall'],
|
|
['cisco.servers', 'server'],
|
|
['cisco.computers_and_peripherals.laptop', 'laptop'],
|
|
['cisco.computers_and_peripherals.ip_phone', 'phone'],
|
|
['cisco.computers_and_peripherals.pc', 'workstation'],
|
|
['cisco.computers_and_peripherals.printer', 'printer'],
|
|
['cisco.misc.access_point', 'access-point'],
|
|
['cisco.misc.cloud', 'cloud'],
|
|
['cisco.storage', 'nas'],
|
|
['shape=router', 'router'],
|
|
['shape=server', 'server'],
|
|
['shape=firewall', 'firewall'],
|
|
['shape=cloud', 'cloud'],
|
|
]
|
|
|
|
function styleToSlug(style: string): string {
|
|
const lower = style.toLowerCase()
|
|
for (const [pattern, slug] of DRAWIO_SHAPE_TO_SLUG) {
|
|
if (lower.includes(pattern)) return slug
|
|
}
|
|
return 'server'
|
|
}
|
|
|
|
function isGroup(style: string): boolean {
|
|
return style.includes('swimlane') || style.includes('container') || style.includes('group')
|
|
}
|
|
|
|
export interface DrawioImportResult {
|
|
nodes: DiagramNode[]
|
|
edges: DiagramEdge[]
|
|
warnings: string[]
|
|
}
|
|
|
|
export function parseDrawioXml(xmlString: string): DrawioImportResult {
|
|
const parser = new DOMParser()
|
|
const doc = parser.parseFromString(xmlString, 'application/xml')
|
|
|
|
const parseError = doc.querySelector('parsererror')
|
|
if (parseError) {
|
|
throw new Error('Invalid draw.io XML: ' + parseError.textContent?.slice(0, 200))
|
|
}
|
|
|
|
const cells = Array.from(doc.querySelectorAll('mxCell'))
|
|
const warnings: string[] = []
|
|
const nodes: DiagramNode[] = []
|
|
const edges: DiagramEdge[] = []
|
|
|
|
const geoMap = new Map<string, { x: number; y: number; width: number; height: number }>()
|
|
for (const cell of cells) {
|
|
const geo = cell.querySelector('mxGeometry')
|
|
if (geo) {
|
|
geoMap.set(cell.getAttribute('id') ?? '', {
|
|
x: parseFloat(geo.getAttribute('x') ?? '0'),
|
|
y: parseFloat(geo.getAttribute('y') ?? '0'),
|
|
width: parseFloat(geo.getAttribute('width') ?? '120'),
|
|
height: parseFloat(geo.getAttribute('height') ?? '120'),
|
|
})
|
|
}
|
|
}
|
|
|
|
const groupIds = new Set<string>()
|
|
|
|
for (const cell of cells) {
|
|
const id = cell.getAttribute('id') ?? ''
|
|
if (id === '0' || id === '1') continue
|
|
|
|
const isEdge = cell.getAttribute('edge') === '1'
|
|
const isVertex = cell.getAttribute('vertex') === '1'
|
|
const style = cell.getAttribute('style') ?? ''
|
|
const value = cell.getAttribute('value') ?? ''
|
|
const parent = cell.getAttribute('parent') ?? '1'
|
|
const geo = geoMap.get(id)
|
|
|
|
if (isEdge) {
|
|
const source = cell.getAttribute('source') ?? ''
|
|
const target = cell.getAttribute('target') ?? ''
|
|
if (!source || !target) {
|
|
warnings.push(`Edge "${id}" skipped — missing source or target`)
|
|
continue
|
|
}
|
|
edges.push({
|
|
id,
|
|
source,
|
|
target,
|
|
label: value || null,
|
|
connectionType: 'ethernet',
|
|
speed: null,
|
|
notes: null,
|
|
routing: null,
|
|
})
|
|
continue
|
|
}
|
|
|
|
if (isVertex && geo) {
|
|
if (isGroup(style)) {
|
|
groupIds.add(id)
|
|
nodes.push({
|
|
id,
|
|
type: 'subnet',
|
|
label: value || 'Group',
|
|
position: { x: geo.x, y: geo.y },
|
|
properties: {
|
|
hostname: null, ip: null, subnet: null, vendor: null,
|
|
model: null, role: null, vlan: null, notes: null, status: 'unknown',
|
|
},
|
|
nodeType: 'group',
|
|
style: { width: geo.width, height: geo.height },
|
|
})
|
|
} else {
|
|
const slug = styleToSlug(style)
|
|
const parentId = parent !== '1' && groupIds.has(parent) ? parent : undefined
|
|
nodes.push({
|
|
id,
|
|
type: slug,
|
|
label: value || slug,
|
|
position: { x: geo.x, y: geo.y },
|
|
properties: {
|
|
hostname: null, ip: null, subnet: null, vendor: null,
|
|
model: null, role: null, vlan: null, notes: null, status: 'unknown',
|
|
},
|
|
...(parentId ? { parentId } : {}),
|
|
style: { width: geo.width, height: geo.height },
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nodes.length === 0) {
|
|
warnings.push('No nodes were found in this draw.io file. Only basic shapes and Cisco stencil shapes are supported.')
|
|
}
|
|
|
|
return { nodes, edges, warnings }
|
|
}
|