feat: improve styling
This commit is contained in:
@@ -2905,8 +2905,10 @@ class MemberCardOverlayComponent extends Component {
|
|||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
// Close overlay when clicking outside or pressing escape
|
// Close overlay when clicking outside or pressing escape
|
||||||
this.addEventListener(document, 'click', (e) => {
|
this.addEventListener(this.container, 'click', (e) => {
|
||||||
if (this.isVisible && !this.container.contains(e.target)) {
|
if (!this.isVisible) return;
|
||||||
|
// Only close when clicking on the backdrop, not inside the dialog content
|
||||||
|
if (e.target === this.container) {
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2936,6 +2938,8 @@ class MemberCardOverlayComponent extends Component {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
this.container.classList.remove('visible');
|
this.container.classList.remove('visible');
|
||||||
@@ -2977,11 +2981,9 @@ class MemberCardOverlayComponent extends Component {
|
|||||||
<span class="latency-label">Latency:</span>
|
<span class="latency-label">Latency:</span>
|
||||||
<span class="latency-value">${member.latency ? member.latency + 'ms' : 'N/A'}</span>
|
<span class="latency-value">${member.latency ? member.latency + 'ms' : 'N/A'}</span>
|
||||||
</div>
|
</div>
|
||||||
${member.labels && Object.keys(member.labels).length ? `
|
<div class="member-labels" style="display: none;">
|
||||||
<div class="member-labels">
|
<!-- Labels will be populated dynamically from node status API -->
|
||||||
${Object.entries(member.labels).map(([key, value]) => `<span class="label-chip">${key}: ${value}</span>`).join('')}
|
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="member-details">
|
<div class="member-details">
|
||||||
@@ -3024,39 +3026,49 @@ class MemberCardOverlayComponent extends Component {
|
|||||||
// Load node details
|
// Load node details
|
||||||
await nodeDetailsVM.loadNodeDetails(memberIp);
|
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]) => `<span class="label-chip">${key}: ${value}</span>`)
|
||||||
|
.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]) => `<span class="label-chip">${key}: ${value}</span>`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
// Insert after latency
|
||||||
|
const latencyDiv = memberInfo.querySelector('.member-latency');
|
||||||
|
if (latencyDiv) {
|
||||||
|
latencyDiv.parentNode.insertBefore(labelsDiv, latencyDiv.nextSibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mount the component
|
// Mount the component
|
||||||
nodeDetailsComponent.mount();
|
nodeDetailsComponent.mount();
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
card.classList.add('expanded');
|
card.classList.add('expanded');
|
||||||
const expandIcon = card.querySelector('.expand-icon');
|
|
||||||
if (expandIcon) {
|
|
||||||
expandIcon.classList.add('expanded');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to expand card:', error);
|
console.error('Failed to expand member card:', error);
|
||||||
memberDetails.innerHTML = `
|
// Still show the UI even if details fail to load
|
||||||
<div class="error">
|
card.classList.add('expanded');
|
||||||
<strong>Error loading node details:</strong><br>
|
const details = card.querySelector('.member-details');
|
||||||
${error.message}
|
if (details) {
|
||||||
</div>
|
details.innerHTML = '<div class="error">Failed to load node details</div>';
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = '<div class="loading-details">Loading detailed information...</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,13 @@ window.demoMembersData = {
|
|||||||
api: [
|
api: [
|
||||||
{ uri: "/api/node/status", method: "GET" },
|
{ uri: "/api/node/status", method: "GET" },
|
||||||
{ uri: "/api/tasks/status", method: "GET" }
|
{ uri: "/api/tasks/status", method: "GET" }
|
||||||
]
|
],
|
||||||
|
labels: {
|
||||||
|
environment: "production",
|
||||||
|
region: "us-west",
|
||||||
|
role: "worker",
|
||||||
|
cluster: "spore-main"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hostname: "spore-node-2",
|
hostname: "spore-node-2",
|
||||||
@@ -35,7 +41,13 @@ window.demoMembersData = {
|
|||||||
api: [
|
api: [
|
||||||
{ uri: "/api/node/status", method: "GET" },
|
{ uri: "/api/node/status", method: "GET" },
|
||||||
{ uri: "/api/tasks/status", method: "GET" }
|
{ uri: "/api/tasks/status", method: "GET" }
|
||||||
]
|
],
|
||||||
|
labels: {
|
||||||
|
environment: "production",
|
||||||
|
region: "us-west",
|
||||||
|
role: "controller",
|
||||||
|
cluster: "spore-main"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hostname: "spore-node-3",
|
hostname: "spore-node-3",
|
||||||
@@ -52,7 +64,13 @@ window.demoMembersData = {
|
|||||||
},
|
},
|
||||||
api: [
|
api: [
|
||||||
{ uri: "/api/node/status", method: "GET" }
|
{ uri: "/api/node/status", method: "GET" }
|
||||||
]
|
],
|
||||||
|
labels: {
|
||||||
|
environment: "staging",
|
||||||
|
region: "us-west",
|
||||||
|
role: "worker",
|
||||||
|
cluster: "spore-main"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hostname: "spore-node-4",
|
hostname: "spore-node-4",
|
||||||
@@ -71,7 +89,13 @@ window.demoMembersData = {
|
|||||||
{ uri: "/api/node/status", method: "GET" },
|
{ uri: "/api/node/status", method: "GET" },
|
||||||
{ uri: "/api/tasks/status", method: "GET" },
|
{ uri: "/api/tasks/status", method: "GET" },
|
||||||
{ uri: "/api/capabilities", method: "GET" }
|
{ uri: "/api/capabilities", method: "GET" }
|
||||||
]
|
],
|
||||||
|
labels: {
|
||||||
|
environment: "production",
|
||||||
|
region: "us-east",
|
||||||
|
role: "gateway",
|
||||||
|
cluster: "spore-main"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hostname: "spore-node-5",
|
hostname: "spore-node-5",
|
||||||
@@ -86,7 +110,8 @@ window.demoMembersData = {
|
|||||||
cpuFreqMHz: 0,
|
cpuFreqMHz: 0,
|
||||||
flashChipSize: 1048576
|
flashChipSize: 1048576
|
||||||
},
|
},
|
||||||
api: []
|
api: [],
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -120,5 +145,30 @@ window.demoApiClient = {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return { members: members.filter(m => m.ip !== ip) };
|
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'
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -229,17 +229,17 @@ p {
|
|||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-chip {
|
.label-chip {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
padding: 0.15rem 0.45rem;
|
padding: 0.25rem 0.6rem;
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
background: rgba(255, 255, 255, 0.08);
|
background: rgba(30, 58, 138, 0.35);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
border: 1px solid rgba(59, 130, 246, 0.4);
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: #dbeafe;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-card::before {
|
.member-card::before {
|
||||||
content: '';
|
content: '';
|
||||||
@@ -2164,9 +2164,9 @@ p {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
padding-right: 0.25rem;
|
padding-right: 0.35rem;
|
||||||
background: rgba(139, 92, 246, 0.15);
|
background: rgba(30, 58, 138, 0.35);
|
||||||
border: 1px solid rgba(139, 92, 246, 0.35);
|
border: 1px solid rgba(59, 130, 246, 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-chip .chip-remove {
|
.label-chip .chip-remove {
|
||||||
@@ -2491,12 +2491,12 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.member-overlay-body .member-card .member-header {
|
.member-overlay-body .member-card .member-header {
|
||||||
padding: 20px 24px 16px;
|
padding: 20px 24px 0px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-overlay-body .member-card .member-details {
|
.member-overlay-body .member-card .member-details {
|
||||||
padding: 20px 24px;
|
padding: 0px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide expand icon in overlay since card is always expanded */
|
/* Hide expand icon in overlay since card is always expanded */
|
||||||
@@ -2509,6 +2509,91 @@ p {
|
|||||||
display: block;
|
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 */
|
/* Highlight animation for member cards */
|
||||||
.member-card.highlighted {
|
.member-card.highlighted {
|
||||||
animation: highlight-pulse 2s ease-in-out;
|
animation: highlight-pulse 2s ease-in-out;
|
||||||
|
|||||||
Reference in New Issue
Block a user