feat: compact member cards
This commit is contained in:
@@ -333,11 +333,10 @@ class ClusterMembersComponent extends Component {
|
|||||||
const statusElement = card.querySelector('.member-status');
|
const statusElement = card.querySelector('.member-status');
|
||||||
if (statusElement) {
|
if (statusElement) {
|
||||||
const statusClass = member.status === 'active' ? 'status-online' : 'status-offline';
|
const statusClass = member.status === 'active' ? 'status-online' : 'status-offline';
|
||||||
const statusText = member.status === 'active' ? 'Online' : 'Offline';
|
|
||||||
const statusIcon = member.status === 'active' ? '🟢' : '🔴';
|
const statusIcon = member.status === 'active' ? '🟢' : '🔴';
|
||||||
|
|
||||||
statusElement.className = `member-status ${statusClass}`;
|
statusElement.className = `member-status ${statusClass}`;
|
||||||
statusElement.innerHTML = `${statusIcon} ${statusText}`;
|
statusElement.innerHTML = `${statusIcon}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update latency
|
// Update latency
|
||||||
@@ -347,7 +346,7 @@ class ClusterMembersComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update hostname if changed
|
// Update hostname if changed
|
||||||
const hostnameElement = card.querySelector('.member-name');
|
const hostnameElement = card.querySelector('.member-hostname');
|
||||||
if (hostnameElement && member.hostname !== hostnameElement.textContent) {
|
if (hostnameElement && member.hostname !== hostnameElement.textContent) {
|
||||||
hostnameElement.textContent = member.hostname || 'Unknown Device';
|
hostnameElement.textContent = member.hostname || 'Unknown Device';
|
||||||
}
|
}
|
||||||
@@ -447,18 +446,24 @@ class ClusterMembersComponent extends Component {
|
|||||||
<div class="member-card" data-member-ip="${member.ip}">
|
<div class="member-card" data-member-ip="${member.ip}">
|
||||||
<div class="member-header">
|
<div class="member-header">
|
||||||
<div class="member-info">
|
<div class="member-info">
|
||||||
<div class="member-name">${member.hostname || 'Unknown Device'}</div>
|
<div class="member-row-1">
|
||||||
<div class="member-ip">${member.ip || 'No IP'}</div>
|
<div class="status-hostname-group">
|
||||||
<div class="member-status ${statusClass}">
|
<div class="member-status ${statusClass}">
|
||||||
${statusIcon} ${statusText}
|
${statusIcon}
|
||||||
</div>
|
</div>
|
||||||
<div class="member-latency">
|
<div class="member-hostname">${member.hostname || 'Unknown Device'}</div>
|
||||||
<span class="latency-label">Latency:</span>
|
</div>
|
||||||
<span class="latency-value">${member.latency ? member.latency + 'ms' : 'N/A'}</span>
|
<div class="member-ip">${member.ip || 'No IP'}</div>
|
||||||
|
<div class="member-latency">
|
||||||
|
<span class="latency-label">Latency:</span>
|
||||||
|
<span class="latency-value">${member.latency ? member.latency + 'ms' : 'N/A'}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${member.labels && Object.keys(member.labels).length ? `
|
${member.labels && Object.keys(member.labels).length ? `
|
||||||
<div class="member-labels">
|
<div class="member-row-2">
|
||||||
${Object.entries(member.labels).map(([key, value]) => `<span class=\"label-chip\">${key}: ${value}</span>`).join('')}
|
<div class="member-labels">
|
||||||
|
${Object.entries(member.labels).map(([key, value]) => `<span class=\"label-chip\">${key}: ${value}</span>`).join('')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
@@ -3070,14 +3075,18 @@ class MemberCardOverlayComponent extends Component {
|
|||||||
<div class="member-card expanded" data-member-ip="${member.ip}">
|
<div class="member-card expanded" data-member-ip="${member.ip}">
|
||||||
<div class="member-header">
|
<div class="member-header">
|
||||||
<div class="member-info">
|
<div class="member-info">
|
||||||
<div class="member-name">${member.hostname || 'Unknown Device'}</div>
|
<div class="member-row-1">
|
||||||
<div class="member-ip">${member.ip || 'No IP'}</div>
|
<div class="status-hostname-group">
|
||||||
<div class="member-status ${statusClass}">
|
<div class="member-status ${statusClass}">
|
||||||
${statusIcon} ${statusText}
|
${statusIcon}
|
||||||
</div>
|
</div>
|
||||||
<div class="member-latency">
|
<div class="member-hostname">${member.hostname || 'Unknown Device'}</div>
|
||||||
<span class="latency-label">Latency:</span>
|
</div>
|
||||||
<span class="latency-value">${member.latency ? member.latency + 'ms' : 'N/A'}</span>
|
<div class="member-ip">${member.ip || 'No IP'}</div>
|
||||||
|
<div class="member-latency">
|
||||||
|
<span class="latency-label">Latency:</span>
|
||||||
|
<span class="latency-value">${member.latency ? member.latency + 'ms' : 'N/A'}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="member-labels" style="display: none;">
|
<div class="member-labels" style="display: none;">
|
||||||
<!-- Labels will be populated dynamically from node status API -->
|
<!-- Labels will be populated dynamically from node status API -->
|
||||||
|
|||||||
@@ -218,6 +218,21 @@ p {
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsive grid adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.members-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.members-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.member-card {
|
.member-card {
|
||||||
background: rgba(0, 0, 0, 0.2);
|
background: rgba(0, 0, 0, 0.2);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
@@ -236,7 +251,6 @@ p {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-chip {
|
.label-chip {
|
||||||
@@ -275,7 +289,6 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.member-card.expanded {
|
.member-card.expanded {
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
border-color: rgba(255, 255, 255, 0.2);
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
@@ -308,7 +321,6 @@ p {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-info {
|
.member-info {
|
||||||
@@ -401,28 +413,50 @@ p {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-name {
|
/* Compact member card layout */
|
||||||
font-size: 1.2rem;
|
.member-row-1 {
|
||||||
font-weight: 600;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-hostname-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-row-2 {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-hostname {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-ip {
|
.member-ip {
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-family: 'Courier New', monospace;
|
font-family: 'Courier New', monospace;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-status {
|
.member-status {
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
padding: 0.25rem 0.75rem;
|
align-items: center;
|
||||||
border-radius: 20px;
|
justify-content: center;
|
||||||
font-size: 0.8rem;
|
padding: 0.2rem;
|
||||||
font-weight: 500;
|
border-radius: 50%;
|
||||||
text-transform: uppercase;
|
font-size: 0.9rem;
|
||||||
letter-spacing: 0.5px;
|
flex-shrink: 0;
|
||||||
|
min-width: 1.2rem;
|
||||||
|
min-height: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-online {
|
.status-online {
|
||||||
@@ -437,12 +471,21 @@ p {
|
|||||||
border: 1px solid rgba(244, 67, 54, 0.5);
|
border: 1px solid rgba(244, 67, 54, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-inactive {
|
||||||
|
background: rgba(255, 152, 0, 0.3);
|
||||||
|
color: #ff9800;
|
||||||
|
border: 1px solid rgba(255, 152, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.member-latency {
|
.member-latency {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-top: 0.5rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.latency-label {
|
.latency-label {
|
||||||
@@ -2860,6 +2903,32 @@ p {
|
|||||||
.member-overlay-title h3 {
|
.member-overlay-title h3 {
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Compact layout adjustments for mobile */
|
||||||
|
.member-row-1 {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-hostname-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-status,
|
||||||
|
.member-hostname,
|
||||||
|
.member-ip,
|
||||||
|
.member-latency {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep status and hostname together on mobile */
|
||||||
|
.member-status {
|
||||||
|
min-width: 1.3rem;
|
||||||
|
min-height: 1.3rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compact mobile layout for overlay */
|
/* Compact mobile layout for overlay */
|
||||||
@@ -2886,6 +2955,26 @@ p {
|
|||||||
.member-overlay-body .member-card .member-details {
|
.member-overlay-body .member-card .member-details {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Further compact layout for very small screens */
|
||||||
|
.member-row-1 {
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-hostname {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-ip {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-status {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.15rem;
|
||||||
|
min-width: 1.1rem;
|
||||||
|
min-height: 1.1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Test page styles */
|
/* Test page styles */
|
||||||
@@ -3035,7 +3124,6 @@ p {
|
|||||||
|
|
||||||
/* Ensure proper scrolling for expanded member cards */
|
/* Ensure proper scrolling for expanded member cards */
|
||||||
.member-card.expanded {
|
.member-card.expanded {
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
border-color: rgba(255, 255, 255, 0.2);
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
|||||||
Reference in New Issue
Block a user