From dc46fc6ca25fe592ecacffd7654f5a6349c9dd63 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Sat, 30 Aug 2025 15:57:25 +0200 Subject: [PATCH] feat: improve styling --- public/components.js | 82 +++++++++++++----------- public/demo-topology-data.js | 60 ++++++++++++++++-- public/styles.css | 119 ++++++++++++++++++++++++++++++----- 3 files changed, 204 insertions(+), 57 deletions(-) diff --git a/public/components.js b/public/components.js index 50ee30a..e84930a 100644 --- a/public/components.js +++ b/public/components.js @@ -2905,8 +2905,10 @@ class MemberCardOverlayComponent extends Component { setupEventListeners() { // Close overlay when clicking outside or pressing escape - this.addEventListener(document, 'click', (e) => { - if (this.isVisible && !this.container.contains(e.target)) { + this.addEventListener(this.container, 'click', (e) => { + if (!this.isVisible) return; + // Only close when clicking on the backdrop, not inside the dialog content + if (e.target === this.container) { this.hide(); } }); @@ -2934,8 +2936,10 @@ class MemberCardOverlayComponent extends Component { this.setupMemberCardInteractions(); } - - + + + + hide() { this.isVisible = false; this.container.classList.remove('visible'); @@ -2977,11 +2981,9 @@ class MemberCardOverlayComponent extends Component { Latency: ${member.latency ? member.latency + 'ms' : 'N/A'} - ${member.labels && Object.keys(member.labels).length ? ` -
- ${Object.entries(member.labels).map(([key, value]) => `${key}: ${value}`).join('')} + - ` : ''}
@@ -3008,7 +3010,7 @@ class MemberCardOverlayComponent extends Component { 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); } @@ -3024,39 +3026,49 @@ class MemberCardOverlayComponent extends Component { // Load node details await nodeDetailsVM.loadNodeDetails(memberIp); + // Update the labels in the member header with the actual node status data + const nodeStatus = nodeDetailsVM.get('nodeStatus'); + if (nodeStatus && nodeStatus.labels) { + const labelsContainer = card.querySelector('.member-labels'); + if (labelsContainer) { + // Update existing labels container and show it + labelsContainer.innerHTML = Object.entries(nodeStatus.labels) + .map(([key, value]) => `${key}: ${value}`) + .join(''); + labelsContainer.style.display = 'block'; + } else { + // Create new labels container if it doesn't exist + const memberInfo = card.querySelector('.member-info'); + if (memberInfo) { + const labelsDiv = document.createElement('div'); + labelsDiv.className = 'member-labels'; + labelsDiv.innerHTML = Object.entries(nodeStatus.labels) + .map(([key, value]) => `${key}: ${value}`) + .join(''); + + // Insert after latency + const latencyDiv = memberInfo.querySelector('.member-latency'); + if (latencyDiv) { + latencyDiv.parentNode.insertBefore(labelsDiv, latencyDiv.nextSibling); + } + } + } + } + // 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} -
- `; + console.error('Failed to expand member card:', error); + // Still show the UI even if details fail to load + card.classList.add('expanded'); + const details = card.querySelector('.member-details'); + if (details) { + details.innerHTML = '
Failed to load node details
'; + } } } - - 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/demo-topology-data.js b/public/demo-topology-data.js index a30332d..917e76f 100644 --- a/public/demo-topology-data.js +++ b/public/demo-topology-data.js @@ -17,7 +17,13 @@ window.demoMembersData = { api: [ { uri: "/api/node/status", method: "GET" }, { uri: "/api/tasks/status", method: "GET" } - ] + ], + labels: { + environment: "production", + region: "us-west", + role: "worker", + cluster: "spore-main" + } }, { hostname: "spore-node-2", @@ -35,7 +41,13 @@ window.demoMembersData = { api: [ { uri: "/api/node/status", method: "GET" }, { uri: "/api/tasks/status", method: "GET" } - ] + ], + labels: { + environment: "production", + region: "us-west", + role: "controller", + cluster: "spore-main" + } }, { hostname: "spore-node-3", @@ -52,7 +64,13 @@ window.demoMembersData = { }, api: [ { uri: "/api/node/status", method: "GET" } - ] + ], + labels: { + environment: "staging", + region: "us-west", + role: "worker", + cluster: "spore-main" + } }, { hostname: "spore-node-4", @@ -71,7 +89,13 @@ window.demoMembersData = { { uri: "/api/node/status", method: "GET" }, { uri: "/api/tasks/status", method: "GET" }, { uri: "/api/capabilities", method: "GET" } - ] + ], + labels: { + environment: "production", + region: "us-east", + role: "gateway", + cluster: "spore-main" + } }, { hostname: "spore-node-5", @@ -86,7 +110,8 @@ window.demoMembersData = { cpuFreqMHz: 0, flashChipSize: 1048576 }, - api: [] + api: [], + } ] }; @@ -120,5 +145,30 @@ window.demoApiClient = { ]; return { members: members.filter(m => m.ip !== ip) }; + }, + + async getNodeStatus(ip) { + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 300)); + + // Find the member by IP + const member = window.demoMembersData.members.find(m => m.ip === ip); + if (!member) { + throw new Error('Node not found'); + } + + // Return node status with labels + return { + ...member.resources, + api: member.api, + labels: { + environment: ip.includes('103') ? 'production' : 'production', + region: ip.includes('103') ? 'us-east' : 'us-west', + role: ip.includes('101') ? 'controller' : ip.includes('103') ? 'gateway' : 'worker', + cluster: 'spore-main', + nodeType: ip.includes('102') ? 'staging' : 'production', + location: ip.includes('103') ? 'datacenter-2' : 'datacenter-1' + } + }; } }; \ No newline at end of file diff --git a/public/styles.css b/public/styles.css index 5310f13..d7cf09d 100644 --- a/public/styles.css +++ b/public/styles.css @@ -229,17 +229,17 @@ p { margin-top: 0.5rem; } -.label-chip { - display: inline-flex; - align-items: center; - font-size: 0.75rem; - padding: 0.15rem 0.45rem; - border-radius: 9999px; - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.15); - color: rgba(255, 255, 255, 0.9); - white-space: nowrap; -} + .label-chip { + display: inline-flex; + align-items: center; + font-size: 0.75rem; + padding: 0.25rem 0.6rem; + border-radius: 9999px; + background: rgba(30, 58, 138, 0.35); + border: 1px solid rgba(59, 130, 246, 0.4); + color: #dbeafe; + white-space: nowrap; + } .member-card::before { content: ''; @@ -2164,9 +2164,9 @@ p { display: inline-flex; align-items: center; gap: 0.35rem; - padding-right: 0.25rem; - background: rgba(139, 92, 246, 0.15); - border: 1px solid rgba(139, 92, 246, 0.35); + padding-right: 0.35rem; + background: rgba(30, 58, 138, 0.35); + border: 1px solid rgba(59, 130, 246, 0.55); } .label-chip .chip-remove { @@ -2491,12 +2491,12 @@ p { } .member-overlay-body .member-card .member-header { - padding: 20px 24px 16px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding: 20px 24px 0px; + border-bottom: none; } .member-overlay-body .member-card .member-details { - padding: 20px 24px; + padding: 0px 24px; } /* Hide expand icon in overlay since card is always expanded */ @@ -2509,6 +2509,91 @@ p { display: block; } +/* Disable hover effects on topology dialog member cards */ +.member-overlay-body .member-card:hover { + transform: none !important; + box-shadow: none !important; +} + +.member-overlay-body .member-card::before { + display: none !important; +} + +.member-overlay-body .member-card .member-header:hover { + background: none !important; +} + +/* Label chips styling for overlay */ +.member-overlay-body .member-labels { + margin-top: 16px; +} + +/* unified with .label-chip */ +.member-overlay-body .label-chip { + margin-right: 10px; + background: rgba(30, 58, 138, 0.35); + color: #dbeafe; + padding: 0.25rem 0.6rem; + border-radius: 9999px; + font-size: 0.75rem; + border: 1px solid rgba(59, 130, 246, 0.4); + font-family: inherit; + white-space: nowrap; + display: inline-flex; +} + +/* Labels section styling in node details */ +.detail-section { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.detail-section-title { + color: rgba(255, 255, 255, 0.8); + font-size: 1rem; + font-weight: 600; + margin-bottom: 16px; + text-transform: capitalize; +} + +/* Resource and API chips styling */ +.member-resources, .member-api { + margin-top: 16px; +} + +.resources-title, .api-title { + color: rgba(255, 255, 255, 0.7); + font-size: 0.9rem; + margin-bottom: 12px; + font-weight: 500; +} + +.resources-container, .api-container { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.resource-chip, .api-chip { + background: rgba(59, 130, 246, 0.2); + color: #60a5fa; + padding: 4px 12px; + border-radius: 16px; + font-size: 0.8rem; + border: 1px solid rgba(59, 130, 246, 0.3); + font-family: 'Courier New', monospace; + white-space: nowrap; +} + + + +.api-chip { + background: rgba(16, 185, 129, 0.2); + color: #10b981; + border-color: rgba(16, 185, 129, 0.3); +} + /* Highlight animation for member cards */ .member-card.highlighted { animation: highlight-pulse 2s ease-in-out;