feat: improve topology view
This commit is contained in:
@@ -15,6 +15,9 @@ class TopologyGraphComponent extends Component {
|
||||
this.detailsDrawerContent = null;
|
||||
this.detailsDrawerBackdrop = null;
|
||||
this.activeDrawerComponent = null;
|
||||
|
||||
// Tooltip for labels on hover
|
||||
this.tooltipEl = null;
|
||||
}
|
||||
|
||||
// Determine desktop threshold
|
||||
@@ -101,6 +104,52 @@ class TopologyGraphComponent extends Component {
|
||||
if (this.detailsDrawerBackdrop) this.detailsDrawerBackdrop.classList.remove('visible');
|
||||
}
|
||||
|
||||
// Tooltip helpers
|
||||
ensureTooltip() {
|
||||
if (this.tooltipEl) return;
|
||||
const el = document.createElement('div');
|
||||
el.className = 'topology-tooltip';
|
||||
document.body.appendChild(el);
|
||||
this.tooltipEl = el;
|
||||
}
|
||||
|
||||
showTooltip(nodeData, pageX, pageY) {
|
||||
this.ensureTooltip();
|
||||
const labels = (nodeData && nodeData.labels) ? nodeData.labels : ((nodeData && nodeData.resources) ? nodeData.resources : null);
|
||||
if (!labels || Object.keys(labels).length === 0) {
|
||||
this.hideTooltip();
|
||||
return;
|
||||
}
|
||||
const chips = Object.entries(labels)
|
||||
.map(([k, v]) => `<span class=\"label-chip\">${this.escapeHtml(String(k))}: ${this.escapeHtml(String(v))}</span>`)
|
||||
.join('');
|
||||
this.tooltipEl.innerHTML = `<div class=\"member-labels\">${chips}</div>`;
|
||||
this.positionTooltip(pageX, pageY);
|
||||
this.tooltipEl.classList.add('visible');
|
||||
}
|
||||
|
||||
positionTooltip(pageX, pageY) {
|
||||
if (!this.tooltipEl) return;
|
||||
const offset = 12;
|
||||
let left = pageX + offset;
|
||||
let top = pageY + offset;
|
||||
const { innerWidth, innerHeight } = window;
|
||||
const rect = this.tooltipEl.getBoundingClientRect();
|
||||
if (left + rect.width > innerWidth - 8) left = pageX - rect.width - offset;
|
||||
if (top + rect.height > innerHeight - 8) top = pageY - rect.height - offset;
|
||||
this.tooltipEl.style.left = `${Math.max(8, left)}px`;
|
||||
this.tooltipEl.style.top = `${Math.max(8, top)}px`;
|
||||
}
|
||||
|
||||
moveTooltip(pageX, pageY) {
|
||||
if (!this.tooltipEl || !this.tooltipEl.classList.contains('visible')) return;
|
||||
this.positionTooltip(pageX, pageY);
|
||||
}
|
||||
|
||||
hideTooltip() {
|
||||
if (this.tooltipEl) this.tooltipEl.classList.remove('visible');
|
||||
}
|
||||
|
||||
updateDimensions(container) {
|
||||
// Get the container's actual dimensions
|
||||
const rect = container.getBoundingClientRect();
|
||||
@@ -377,15 +426,25 @@ class TopologyGraphComponent extends Component {
|
||||
node.append('text')
|
||||
.text(d => d.ip)
|
||||
.attr('x', 15)
|
||||
.attr('y', 20)
|
||||
.attr('y', 22)
|
||||
.attr('font-size', '11px')
|
||||
.attr('fill', 'var(--text-secondary)');
|
||||
|
||||
// App label (between IP and Status)
|
||||
node.append('text')
|
||||
.text(d => (d.labels && d.labels.app) ? String(d.labels.app) : '')
|
||||
.attr('x', 15)
|
||||
.attr('y', 38)
|
||||
.attr('font-size', '11px')
|
||||
.attr('fill', 'var(--text-secondary)')
|
||||
.attr('font-weight', '500')
|
||||
.attr('display', d => (d.labels && d.labels.app) ? null : 'none');
|
||||
|
||||
// Status text
|
||||
node.append('text')
|
||||
.text(d => d.status)
|
||||
.attr('x', 15)
|
||||
.attr('y', 35)
|
||||
.attr('y', 56)
|
||||
.attr('font-size', '11px')
|
||||
.attr('fill', d => this.getNodeColor(d.status))
|
||||
.attr('font-weight', '600');
|
||||
@@ -448,12 +507,18 @@ class TopologyGraphComponent extends Component {
|
||||
d3.select(event.currentTarget).select('circle')
|
||||
.attr('r', d => this.getNodeRadius(d.status) + 4)
|
||||
.attr('stroke-width', 3);
|
||||
this.showTooltip(d, event.pageX, event.pageY);
|
||||
});
|
||||
|
||||
node.on('mouseout', (event, d) => {
|
||||
d3.select(event.currentTarget).select('circle')
|
||||
.attr('r', d => this.getNodeRadius(d.status))
|
||||
.attr('stroke-width', 2);
|
||||
this.hideTooltip();
|
||||
});
|
||||
|
||||
node.on('mousemove', (event, d) => {
|
||||
this.moveTooltip(event.pageX, event.pageY);
|
||||
});
|
||||
|
||||
link.on('mouseover', (event, d) => {
|
||||
@@ -696,7 +761,7 @@ class TopologyGraphComponent extends Component {
|
||||
hostname: nodeData.hostname,
|
||||
status: this.normalizeStatus(nodeData.status),
|
||||
latency: nodeData.latency,
|
||||
labels: nodeData.resources || {}
|
||||
labels: (nodeData.labels && typeof nodeData.labels === 'object') ? nodeData.labels : (nodeData.resources || {})
|
||||
};
|
||||
|
||||
this.memberOverlayComponent.show(memberData);
|
||||
|
||||
@@ -536,6 +536,8 @@ class TopologyViewModel extends ViewModel {
|
||||
ip: member.ip,
|
||||
status: member.status || 'UNKNOWN',
|
||||
latency: member.latency || 0,
|
||||
// Preserve both legacy 'resources' and preferred 'labels'
|
||||
labels: (member.labels && typeof member.labels === 'object') ? member.labels : (member.resources || {}),
|
||||
resources: member.resources || {},
|
||||
x: Math.random() * 1200 + 100, // Better spacing for 1400px width
|
||||
y: Math.random() * 800 + 100 // Better spacing for 1000px height
|
||||
|
||||
Reference in New Issue
Block a user