Add full search parity and optimize map performance
Search: refactor into modular helpers, add IP/hostname/notification/ vendor/model/type search, smart auto-redirects for exact matches, ServiceNOW prefix detection, filter buttons with counts, share links with highlight support, and dark mode badge colors. Map: fix N+1 queries with eager loading (248->28 queries), switch to canvas-rendered circleMarkers for better performance with 500+ assets. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,8 @@ let imageOverlay = null
|
||||
const markers = ref([])
|
||||
const pickedPosition = ref(null)
|
||||
let pickerMarker = null
|
||||
let markerLayer = null
|
||||
let canvasRenderer = null
|
||||
|
||||
const filters = ref({
|
||||
machinetype: '',
|
||||
@@ -258,11 +260,15 @@ const visibleSubtypes = computed(() => {
|
||||
function initMap() {
|
||||
if (!mapContainer.value) return
|
||||
|
||||
// Use canvas renderer for performance - renders all markers on a single <canvas>
|
||||
canvasRenderer = L.canvas({ padding: 0.5 })
|
||||
|
||||
map = L.map(mapContainer.value, {
|
||||
crs: L.CRS.Simple,
|
||||
minZoom: -4,
|
||||
maxZoom: 2,
|
||||
attributionControl: false
|
||||
attributionControl: false,
|
||||
renderer: canvasRenderer
|
||||
})
|
||||
|
||||
const blueprintUrl = props.theme === 'light'
|
||||
@@ -397,21 +403,21 @@ function renderMarkers() {
|
||||
detailRoute = getDetailRoute(item)
|
||||
}
|
||||
|
||||
const icon = L.divIcon({
|
||||
html: `<div class="machine-marker-dot" style="background: ${color};"></div>`,
|
||||
iconSize: [12, 12],
|
||||
iconAnchor: [6, 6],
|
||||
popupAnchor: [0, -6],
|
||||
className: 'machine-marker'
|
||||
// Use circleMarker instead of divIcon marker - renders on canvas
|
||||
// for much better performance (no DOM element per marker)
|
||||
const marker = L.circleMarker([leafletY, leafletX], {
|
||||
radius: 6,
|
||||
fillColor: color,
|
||||
color: 'rgba(255,255,255,0.8)',
|
||||
weight: 2,
|
||||
fillOpacity: 1,
|
||||
renderer: canvasRenderer
|
||||
})
|
||||
|
||||
const marker = L.marker([leafletY, leafletX], { icon })
|
||||
|
||||
// Build tooltip content
|
||||
let tooltipLines = [`<strong>${displayName}</strong>`]
|
||||
|
||||
if (props.assetTypeMode) {
|
||||
// Asset mode tooltips
|
||||
tooltipLines.push(`<span style="color: #888;">${item.assettype || 'Unknown'}</span>`)
|
||||
|
||||
if (item.primaryip) {
|
||||
@@ -421,7 +427,6 @@ function renderMarkers() {
|
||||
tooltipLines.push(`<span style="color: #8cf;">${item.typedata.hostname}</span>`)
|
||||
}
|
||||
} else {
|
||||
// Legacy machine mode tooltips
|
||||
const category = item.category?.toLowerCase() || ''
|
||||
|
||||
if (typeName && typeName.toLowerCase() !== 'locationonly') {
|
||||
@@ -448,7 +453,6 @@ function renderMarkers() {
|
||||
}
|
||||
}
|
||||
|
||||
// Business unit
|
||||
if (item.businessunit) {
|
||||
tooltipLines.push(`<span style="color: #ccc;">${item.businessunit}</span>`)
|
||||
}
|
||||
@@ -561,7 +565,7 @@ function applyFilters() {
|
||||
visible = false
|
||||
}
|
||||
|
||||
marker.setOpacity(visible ? 1 : 0.15)
|
||||
marker.setStyle({ fillOpacity: visible ? 1 : 0.15, opacity: visible ? 1 : 0.15 })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -571,9 +575,9 @@ function debounceSearch() {
|
||||
searchTimeout = setTimeout(applyFilters, 300)
|
||||
}
|
||||
|
||||
watch(() => props.machines, () => {
|
||||
if (map) renderMarkers()
|
||||
}, { deep: true })
|
||||
watch(() => props.machines, (newVal, oldVal) => {
|
||||
if (map && newVal !== oldVal) renderMarkers()
|
||||
})
|
||||
|
||||
watch(() => props.theme, (newTheme) => {
|
||||
if (imageOverlay && map) {
|
||||
|
||||
Reference in New Issue
Block a user