diff --git a/public/components.js b/public/components.js
index cc73745..50ee30a 100644
--- a/public/components.js
+++ b/public/components.js
@@ -2579,10 +2579,13 @@ class TopologyGraphComponent extends Component {
this.simulation.alpha(0.3).restart();
}
- // Add click handlers for node selection
+ // Add click handlers for node selection and member card overlay
node.on('click', (event, d) => {
this.viewModel.selectNode(d.id);
this.updateSelection(d.id);
+
+ // Show member card overlay
+ this.showMemberCardOverlay(d);
});
// Add hover effects
@@ -2820,6 +2823,52 @@ class TopologyGraphComponent extends Component {
container.innerHTML = '
';
}
+ showMemberCardOverlay(nodeData) {
+ // Create overlay container if it doesn't exist
+ let overlayContainer = document.getElementById('member-card-overlay');
+ if (!overlayContainer) {
+ overlayContainer = document.createElement('div');
+ overlayContainer.id = 'member-card-overlay';
+ overlayContainer.className = 'member-card-overlay';
+ document.body.appendChild(overlayContainer);
+ }
+
+ // Create and show the overlay component
+ if (!this.memberOverlayComponent) {
+ const overlayVM = new ViewModel();
+ this.memberOverlayComponent = new MemberCardOverlayComponent(overlayContainer, overlayVM, this.eventBus);
+ this.memberOverlayComponent.mount();
+ }
+
+ // Convert node data to member data format
+ const memberData = {
+ ip: nodeData.ip,
+ hostname: nodeData.hostname,
+ status: this.normalizeStatus(nodeData.status),
+ latency: nodeData.latency,
+ labels: nodeData.resources || {}
+ };
+
+ this.memberOverlayComponent.show(memberData);
+ }
+
+ // Normalize status from topology format to member card format
+ normalizeStatus(status) {
+ if (!status) return 'unknown';
+
+ const normalized = status.toLowerCase();
+ switch (normalized) {
+ case 'active':
+ return 'active';
+ case 'inactive':
+ return 'inactive';
+ case 'dead':
+ return 'offline';
+ default:
+ return 'unknown';
+ }
+ }
+
// Override render method to display the graph
render() {
console.log('TopologyGraphComponent: render called');
@@ -2839,4 +2888,175 @@ class TopologyGraphComponent extends Component {
}
+}
+
+// Member Card Overlay Component for displaying member details in topology view
+class MemberCardOverlayComponent extends Component {
+ constructor(container, viewModel, eventBus) {
+ super(container, viewModel, eventBus);
+ this.isVisible = false;
+ this.currentMember = null;
+ }
+
+ mount() {
+ super.mount();
+ this.setupEventListeners();
+ }
+
+ setupEventListeners() {
+ // Close overlay when clicking outside or pressing escape
+ this.addEventListener(document, 'click', (e) => {
+ if (this.isVisible && !this.container.contains(e.target)) {
+ this.hide();
+ }
+ });
+
+ this.addEventListener(document, 'keydown', (e) => {
+ if (e.key === 'Escape' && this.isVisible) {
+ this.hide();
+ }
+ });
+ }
+
+ show(memberData) {
+ this.currentMember = memberData;
+ this.isVisible = true;
+
+ const memberCardHTML = this.renderMemberCard(memberData);
+ this.setHTML('', memberCardHTML);
+
+ // Add visible class for animation
+ setTimeout(() => {
+ this.container.classList.add('visible');
+ }, 10);
+
+ // Setup member card interactions
+ this.setupMemberCardInteractions();
+ }
+
+
+
+ hide() {
+ this.isVisible = false;
+ this.container.classList.remove('visible');
+ this.currentMember = null;
+ }
+
+ renderMemberCard(member) {
+ const statusClass = member.status === 'active' ? 'status-online' :
+ member.status === 'inactive' ? 'status-inactive' : 'status-offline';
+ const statusText = member.status === 'active' ? 'Online' :
+ member.status === 'inactive' ? 'Inactive' :
+ member.status === 'offline' ? 'Offline' : 'Unknown';
+ const statusIcon = member.status === 'active' ? '🟢' :
+ member.status === 'inactive' ? '🟠' : '🔴';
+
+ return `
+
+
+
+
+
+
+
+
Loading detailed information...
+
+
+
+
+ `;
+ }
+
+ setupMemberCardInteractions() {
+ // Close button
+ const closeBtn = this.findElement('.member-overlay-close');
+ if (closeBtn) {
+ this.addEventListener(closeBtn, 'click', () => {
+ this.hide();
+ });
+ }
+
+ // Setup member card expansion - automatically expand when shown
+ setTimeout(async () => {
+ const memberCard = this.findElement('.member-card');
+ if (memberCard) {
+ const memberDetails = memberCard.querySelector('.member-details');
+ const memberIp = memberCard.dataset.memberIp;
+
+ // Automatically expand the card to show details
+ await this.expandCard(memberCard, memberIp, memberDetails);
+ }
+ }, 100);
+ }
+
+ async expandCard(card, memberIp, memberDetails) {
+ try {
+ // Create node details view model and component
+ const nodeDetailsVM = new NodeDetailsViewModel();
+ const nodeDetailsComponent = new NodeDetailsComponent(memberDetails, nodeDetailsVM, this.eventBus);
+
+ // Load node details
+ await nodeDetailsVM.loadNodeDetails(memberIp);
+
+ // Mount the component
+ nodeDetailsComponent.mount();
+
+ // Update UI
+ card.classList.add('expanded');
+ const expandIcon = card.querySelector('.expand-icon');
+ if (expandIcon) {
+ expandIcon.classList.add('expanded');
+ }
+
+ } catch (error) {
+ console.error('Failed to expand card:', error);
+ memberDetails.innerHTML = `
+
+ Error loading node details:
+ ${error.message}
+
+ `;
+ }
+ }
+
+ collapseCard(card, expandIcon) {
+ card.classList.remove('expanded');
+ if (expandIcon) {
+ expandIcon.classList.remove('expanded');
+ }
+
+ // Reset member details to loading state
+ const memberDetails = card.querySelector('.member-details');
+ if (memberDetails) {
+ memberDetails.innerHTML = 'Loading detailed information...
';
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/public/styles.css b/public/styles.css
index 9f06172..5310f13 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -2383,6 +2383,201 @@ p {
height: 100%;
}
+/* Member Card Overlay Styles */
+.member-card-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10000;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 0.3s ease;
+}
+
+.member-card-overlay.visible {
+ opacity: 1;
+ visibility: visible;
+}
+
+.member-overlay-content {
+ background: linear-gradient(135deg, #1c2a38 0%, #283746 50%, #1a252f 100%);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ border-radius: 16px;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
+ max-width: 800px;
+ width: 90%;
+ max-height: 90vh;
+ overflow: hidden;
+ transform: scale(0.9) translateY(20px);
+ transition: all 0.3s ease;
+}
+
+.member-card-overlay.visible .member-overlay-content {
+ transform: scale(1) translateY(0);
+}
+
+.member-overlay-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ padding: 24px 24px 16px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.member-overlay-title h3 {
+ margin: 0 0 8px 0;
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: #ecf0f1;
+}
+
+.member-overlay-subtitle {
+ font-size: 1rem;
+ color: rgba(255, 255, 255, 0.7);
+ font-family: 'Courier New', monospace;
+}
+
+.member-overlay-close {
+ background: none;
+ border: none;
+ color: rgba(255, 255, 255, 0.6);
+ cursor: pointer;
+ padding: 8px;
+ border-radius: 8px;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.member-overlay-close:hover {
+ background: rgba(255, 255, 255, 0.1);
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.member-overlay-close svg {
+ width: 20px;
+ height: 20px;
+}
+
+.member-overlay-body {
+ padding: 24px;
+}
+
+.member-overlay-section {
+ margin-bottom: 24px;
+}
+
+/* Member card container within overlay */
+.member-overlay-body {
+ padding: 0;
+ overflow: auto;
+ max-height: calc(90vh - 120px); /* Account for header */
+}
+
+/* Ensure member cards render properly in overlay */
+.member-overlay-body .member-card {
+ margin: 0;
+ border-radius: 0;
+ border: none;
+ box-shadow: none;
+ background: transparent;
+}
+
+.member-overlay-body .member-card .member-header {
+ padding: 20px 24px 16px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.member-overlay-body .member-card .member-details {
+ padding: 20px 24px;
+}
+
+/* Hide expand icon in overlay since card is always expanded */
+.member-overlay-body .member-card .expand-icon {
+ display: none;
+}
+
+/* Ensure expanded state is visually clear */
+.member-overlay-body .member-card.expanded .member-details {
+ display: block;
+}
+
+/* Highlight animation for member cards */
+.member-card.highlighted {
+ animation: highlight-pulse 2s ease-in-out;
+}
+
+@keyframes highlight-pulse {
+ 0%, 100% {
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
+ }
+ 50% {
+ box-shadow: 0 8px 32px rgba(59, 130, 246, 0.4), 0 4px 16px rgba(0, 0, 0, 0.3);
+ }
+}
+
+/* Responsive design for overlay */
+@media (max-width: 768px) {
+ .member-overlay-content {
+ width: 95%;
+ max-width: none;
+ margin: 20px;
+ }
+
+ .member-overlay-header {
+ padding: 20px 20px 12px;
+ }
+
+ .member-overlay-body {
+ padding: 20px;
+ }
+
+ .member-overlay-actions {
+ flex-direction: column;
+ }
+
+ .member-overlay-title h3 {
+ font-size: 1.3rem;
+ }
+}
+
+/* Test page styles */
+.test-section {
+ background: rgba(255, 255, 255, 0.05);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 12px;
+ padding: 24px;
+ margin: 24px 0;
+}
+
+.test-section h2 {
+ color: #ecf0f1;
+ margin-top: 0;
+}
+
+#test-overlay-btn {
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+#test-overlay-btn:hover {
+ background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%);
+ transform: translateY(-1px);
+}
+
/* Loading and error states */
.loading, .error, .no-data {
display: flex;
diff --git a/public/test-member-overlay.html b/public/test-member-overlay.html
new file mode 100644
index 0000000..e18c82e
--- /dev/null
+++ b/public/test-member-overlay.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+ Test Member Card Overlay
+
+
+
+
+
+
Test Member Card Overlay
+
+
+
Test Data
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/test-topology-overlay.html b/public/test-topology-overlay.html
new file mode 100644
index 0000000..abca027
--- /dev/null
+++ b/public/test-topology-overlay.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+ Test Topology with Member Card Overlay
+
+
+
+
+
+
Test Topology with Member Card Overlay
+
+
+
Topology View with Clickable Nodes
+
Click on any node in the topology to see the member card overlay.
+
+
+
+
+
+
Loading network topology...
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file