refactor(components): split components.js into separate files and add loader; app waits for components before init
This commit is contained in:
628
public/scripts/components/ClusterMembersComponent.js
Normal file
628
public/scripts/components/ClusterMembersComponent.js
Normal file
@@ -0,0 +1,628 @@
|
||||
// Cluster Members Component with enhanced state preservation
|
||||
class ClusterMembersComponent extends Component {
|
||||
constructor(container, viewModel, eventBus) {
|
||||
super(container, viewModel, eventBus);
|
||||
|
||||
logger.debug('ClusterMembersComponent: Constructor called');
|
||||
logger.debug('ClusterMembersComponent: Container:', container);
|
||||
logger.debug('ClusterMembersComponent: Container ID:', container?.id);
|
||||
logger.debug('ClusterMembersComponent: Container innerHTML:', container?.innerHTML);
|
||||
|
||||
// Track if we're in the middle of a render operation
|
||||
this.renderInProgress = false;
|
||||
this.lastRenderData = null;
|
||||
|
||||
// Ensure initial render happens even if no data
|
||||
setTimeout(() => {
|
||||
if (this.isMounted && !this.renderInProgress) {
|
||||
logger.debug('ClusterMembersComponent: Performing initial render check');
|
||||
this.render();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
mount() {
|
||||
logger.debug('ClusterMembersComponent: Starting mount...');
|
||||
super.mount();
|
||||
|
||||
// Show loading state immediately when mounted
|
||||
logger.debug('ClusterMembersComponent: Showing initial loading state');
|
||||
this.showLoadingState();
|
||||
|
||||
// Set up loading timeout safeguard
|
||||
this.setupLoadingTimeout();
|
||||
|
||||
logger.debug('ClusterMembersComponent: Mounted successfully');
|
||||
}
|
||||
|
||||
// Setup loading timeout safeguard to prevent getting stuck in loading state
|
||||
setupLoadingTimeout() {
|
||||
this.loadingTimeout = setTimeout(() => {
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
if (isLoading) {
|
||||
logger.warn('ClusterMembersComponent: Loading timeout reached, forcing render check');
|
||||
this.forceRenderCheck();
|
||||
}
|
||||
}, 10000); // 10 second timeout
|
||||
}
|
||||
|
||||
// Force a render check when loading gets stuck
|
||||
forceRenderCheck() {
|
||||
logger.debug('ClusterMembersComponent: Force render check called');
|
||||
const members = this.viewModel.get('members');
|
||||
const error = this.viewModel.get('error');
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
|
||||
logger.debug('ClusterMembersComponent: Force render check state:', { members, error, isLoading });
|
||||
|
||||
if (error) {
|
||||
this.showErrorState(error);
|
||||
} else if (members && members.length > 0) {
|
||||
this.renderMembers(members);
|
||||
} else if (!isLoading) {
|
||||
this.showEmptyState();
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
logger.debug('ClusterMembersComponent: Setting up event listeners...');
|
||||
// Note: Refresh button is now handled by ClusterViewComponent
|
||||
// since it's in the cluster header, not in the members container
|
||||
}
|
||||
|
||||
setupViewModelListeners() {
|
||||
logger.debug('ClusterMembersComponent: Setting up view model listeners...');
|
||||
// Listen to cluster members changes with change detection
|
||||
this.subscribeToProperty('members', this.handleMembersUpdate.bind(this));
|
||||
this.subscribeToProperty('isLoading', this.handleLoadingUpdate.bind(this));
|
||||
this.subscribeToProperty('error', this.handleErrorUpdate.bind(this));
|
||||
logger.debug('ClusterMembersComponent: View model listeners set up');
|
||||
}
|
||||
|
||||
// Handle members update with state preservation
|
||||
handleMembersUpdate(newMembers, previousMembers) {
|
||||
logger.debug('ClusterMembersComponent: Members updated:', { newMembers, previousMembers });
|
||||
|
||||
// Prevent multiple simultaneous renders
|
||||
if (this.renderInProgress) {
|
||||
logger.debug('ClusterMembersComponent: Render already in progress, skipping update');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're currently loading - if so, let the loading handler deal with it
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
if (isLoading) {
|
||||
logger.debug('ClusterMembersComponent: Currently loading, skipping members update (will be handled by loading completion)');
|
||||
return;
|
||||
}
|
||||
|
||||
// On first load (no previous members), always render
|
||||
if (!previousMembers || !Array.isArray(previousMembers) || previousMembers.length === 0) {
|
||||
logger.debug('ClusterMembersComponent: First load or no previous members, performing full render');
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.shouldPreserveState(newMembers, previousMembers)) {
|
||||
// Perform partial update to preserve UI state
|
||||
logger.debug('ClusterMembersComponent: Preserving state, performing partial update');
|
||||
this.updateMembersPartially(newMembers, previousMembers);
|
||||
} else {
|
||||
// Full re-render if structure changed significantly
|
||||
logger.debug('ClusterMembersComponent: Structure changed, performing full re-render');
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle loading state update
|
||||
handleLoadingUpdate(isLoading) {
|
||||
logger.debug('ClusterMembersComponent: Loading state changed:', isLoading);
|
||||
|
||||
if (isLoading) {
|
||||
logger.debug('ClusterMembersComponent: Showing loading state');
|
||||
this.renderLoading(`
|
||||
<div class="loading">
|
||||
<div>Loading cluster members...</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Set up a loading completion check
|
||||
this.checkLoadingCompletion();
|
||||
} else {
|
||||
logger.debug('ClusterMembersComponent: Loading completed, checking if we need to render');
|
||||
// When loading completes, check if we have data to render
|
||||
this.handleLoadingCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if loading has completed and handle accordingly
|
||||
handleLoadingCompletion() {
|
||||
const members = this.viewModel.get('members');
|
||||
const error = this.viewModel.get('error');
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
|
||||
logger.debug('ClusterMembersComponent: Handling loading completion:', { members, error, isLoading });
|
||||
|
||||
if (error) {
|
||||
logger.debug('ClusterMembersComponent: Loading completed with error, showing error state');
|
||||
this.showErrorState(error);
|
||||
} else if (members && members.length > 0) {
|
||||
logger.debug('ClusterMembersComponent: Loading completed with data, rendering members');
|
||||
this.renderMembers(members);
|
||||
} else if (!isLoading) {
|
||||
logger.debug('ClusterMembersComponent: Loading completed but no data, showing empty state');
|
||||
this.showEmptyState();
|
||||
}
|
||||
}
|
||||
|
||||
// Set up a check to ensure loading completion is handled
|
||||
checkLoadingCompletion() {
|
||||
// Clear any existing completion check
|
||||
if (this.loadingCompletionCheck) {
|
||||
clearTimeout(this.loadingCompletionCheck);
|
||||
}
|
||||
|
||||
// Set up a completion check that runs after a short delay
|
||||
this.loadingCompletionCheck = setTimeout(() => {
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
if (!isLoading) {
|
||||
logger.debug('ClusterMembersComponent: Loading completion check triggered');
|
||||
this.handleLoadingCompletion();
|
||||
}
|
||||
}, 1000); // Check after 1 second
|
||||
}
|
||||
|
||||
// Handle error state update
|
||||
handleErrorUpdate(error) {
|
||||
if (error) {
|
||||
this.showErrorState(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should preserve UI state during update
|
||||
shouldPreserveState(newMembers, previousMembers) {
|
||||
if (!previousMembers || !Array.isArray(previousMembers)) return false;
|
||||
if (!Array.isArray(newMembers)) return false;
|
||||
|
||||
// If member count changed, we need to re-render
|
||||
if (newMembers.length !== previousMembers.length) return false;
|
||||
|
||||
// Check if member IPs are the same (same nodes)
|
||||
const newIps = new Set(newMembers.map(m => m.ip));
|
||||
const prevIps = new Set(previousMembers.map(m => m.ip));
|
||||
|
||||
// If IPs are the same, we can preserve state
|
||||
return newIps.size === prevIps.size &&
|
||||
[...newIps].every(ip => prevIps.has(ip));
|
||||
}
|
||||
|
||||
// Check if we should skip rendering during view switches
|
||||
shouldSkipRender() {
|
||||
// Rely on lifecycle flags controlled by App
|
||||
if (!this.isMounted || this.isPaused) {
|
||||
logger.debug('ClusterMembersComponent: Not mounted or paused, skipping render');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update members partially to preserve UI state
|
||||
updateMembersPartially(newMembers, previousMembers) {
|
||||
logger.debug('ClusterMembersComponent: Performing partial update to preserve UI state');
|
||||
|
||||
// Build previous map by IP for stable diffs
|
||||
const prevByIp = new Map((previousMembers || []).map(m => [m.ip, m]));
|
||||
newMembers.forEach((newMember) => {
|
||||
const prevMember = prevByIp.get(newMember.ip);
|
||||
if (prevMember && this.hasMemberChanged(newMember, prevMember)) {
|
||||
this.updateMemberCard(newMember);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check if a specific member has changed
|
||||
hasMemberChanged(newMember, prevMember) {
|
||||
return newMember.status !== prevMember.status ||
|
||||
newMember.latency !== prevMember.latency ||
|
||||
newMember.hostname !== prevMember.hostname;
|
||||
}
|
||||
|
||||
// Update a specific member card without re-rendering the entire component
|
||||
updateMemberCard(member) {
|
||||
const card = this.findElement(`[data-member-ip="${member.ip}"]`);
|
||||
if (!card) return;
|
||||
|
||||
// Update status
|
||||
const statusElement = card.querySelector('.member-status');
|
||||
if (statusElement) {
|
||||
const statusClass = member.status === 'active' ? 'status-online' : 'status-offline';
|
||||
const statusIcon = member.status === 'active' ? '🟢' : '🔴';
|
||||
|
||||
statusElement.className = `member-status ${statusClass}`;
|
||||
statusElement.innerHTML = `${statusIcon}`;
|
||||
}
|
||||
|
||||
// Update latency
|
||||
const latencyElement = card.querySelector('.latency-value');
|
||||
if (latencyElement) {
|
||||
latencyElement.textContent = member.latency ? member.latency + 'ms' : 'N/A';
|
||||
}
|
||||
|
||||
// Update hostname if changed
|
||||
const hostnameElement = card.querySelector('.member-hostname');
|
||||
if (hostnameElement && member.hostname !== hostnameElement.textContent) {
|
||||
hostnameElement.textContent = member.hostname || 'Unknown Device';
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.renderInProgress) {
|
||||
logger.debug('ClusterMembersComponent: Render already in progress, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we should skip rendering during view switches
|
||||
if (this.shouldSkipRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderInProgress = true;
|
||||
|
||||
try {
|
||||
logger.debug('ClusterMembersComponent: render() called');
|
||||
logger.debug('ClusterMembersComponent: Container element:', this.container);
|
||||
logger.debug('ClusterMembersComponent: Is mounted:', this.isMounted);
|
||||
|
||||
const members = this.viewModel.get('members');
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
const error = this.viewModel.get('error');
|
||||
|
||||
logger.debug('ClusterMembersComponent: render data:', { members, isLoading, error });
|
||||
|
||||
if (isLoading) {
|
||||
logger.debug('ClusterMembersComponent: Showing loading state');
|
||||
this.showLoadingState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
logger.debug('ClusterMembersComponent: Showing error state');
|
||||
this.showErrorState(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!members || members.length === 0) {
|
||||
logger.debug('ClusterMembersComponent: Showing empty state');
|
||||
this.showEmptyState();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug('ClusterMembersComponent: Rendering members:', members);
|
||||
this.renderMembers(members);
|
||||
|
||||
} finally {
|
||||
this.renderInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
showLoadingState() {
|
||||
logger.debug('ClusterMembersComponent: showLoadingState() called');
|
||||
this.renderLoading(`
|
||||
<div class="loading">
|
||||
<div>Loading cluster members...</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Show error state
|
||||
showErrorState(error) {
|
||||
logger.debug('ClusterMembersComponent: showErrorState() called with error:', error);
|
||||
this.renderError(`Error loading cluster members: ${error}`);
|
||||
}
|
||||
|
||||
// Show empty state
|
||||
showEmptyState() {
|
||||
logger.debug('ClusterMembersComponent: showEmptyState() called');
|
||||
this.renderEmpty(`
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">🌐</div>
|
||||
<div>No cluster members found</div>
|
||||
<div style="font-size: 0.9rem; margin-top: 0.5rem; opacity: 0.7;">
|
||||
The cluster might be empty or not yet discovered
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
renderMembers(members) {
|
||||
logger.debug('ClusterMembersComponent: renderMembers() called with', members.length, 'members');
|
||||
|
||||
const membersHTML = members.map(member => {
|
||||
const statusClass = member.status === 'active' ? 'status-online' : 'status-offline';
|
||||
const statusText = member.status === 'active' ? 'Online' : 'Offline';
|
||||
const statusIcon = member.status === 'active' ? '🟢' : '🔴';
|
||||
|
||||
logger.debug('ClusterMembersComponent: Rendering member:', member);
|
||||
|
||||
return `
|
||||
<div class="member-card" data-member-ip="${member.ip}">
|
||||
<div class="member-header">
|
||||
<div class="member-info">
|
||||
<div class="member-row-1">
|
||||
<div class="status-hostname-group">
|
||||
<div class="member-status ${statusClass}">
|
||||
${statusIcon}
|
||||
</div>
|
||||
<div class="member-hostname">${this.escapeHtml(member.hostname || 'Unknown Device')}</div>
|
||||
</div>
|
||||
<div class="member-ip">${this.escapeHtml(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>
|
||||
${member.labels && Object.keys(member.labels).length ? `
|
||||
<div class="member-row-2">
|
||||
<div class="member-labels">
|
||||
${Object.entries(member.labels).map(([key, value]) => `<span class=\"label-chip\">${this.escapeHtml(key)}: ${this.escapeHtml(value)}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="expand-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M6 9l6 6 6-6"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-details">
|
||||
<div class="loading-details">Loading detailed information...</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
logger.debug('ClusterMembersComponent: Setting HTML, length:', membersHTML.length);
|
||||
this.setHTML('', membersHTML);
|
||||
logger.debug('ClusterMembersComponent: HTML set, setting up member cards...');
|
||||
this.setupMemberCards(members);
|
||||
}
|
||||
|
||||
setupMemberCards(members) {
|
||||
setTimeout(() => {
|
||||
this.findAllElements('.member-card').forEach((card, index) => {
|
||||
const expandIcon = card.querySelector('.expand-icon');
|
||||
const memberDetails = card.querySelector('.member-details');
|
||||
const memberIp = card.dataset.memberIp;
|
||||
|
||||
// Ensure all cards start collapsed by default
|
||||
card.classList.remove('expanded');
|
||||
if (expandIcon) {
|
||||
expandIcon.classList.remove('expanded');
|
||||
}
|
||||
|
||||
// Clear any previous content
|
||||
memberDetails.innerHTML = '<div class="loading-details">Loading detailed information...</div>';
|
||||
|
||||
// Make the entire card clickable
|
||||
this.addEventListener(card, 'click', async (e) => {
|
||||
if (e.target === expandIcon) return;
|
||||
|
||||
const isExpanding = !card.classList.contains('expanded');
|
||||
|
||||
if (isExpanding) {
|
||||
await this.expandCard(card, memberIp, memberDetails);
|
||||
} else {
|
||||
this.collapseCard(card, expandIcon);
|
||||
}
|
||||
});
|
||||
|
||||
// Keep the expand icon click handler for visual feedback
|
||||
if (expandIcon) {
|
||||
this.addEventListener(expandIcon, 'click', async (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const isExpanding = !card.classList.contains('expanded');
|
||||
|
||||
if (isExpanding) {
|
||||
await this.expandCard(card, memberIp, memberDetails);
|
||||
} else {
|
||||
this.collapseCard(card, expandIcon);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 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) {
|
||||
logger.error('Failed to expand card:', error);
|
||||
memberDetails.innerHTML = `
|
||||
<div class="error">
|
||||
<strong>Error loading node details:</strong><br>
|
||||
${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
collapseCard(card, expandIcon) {
|
||||
card.classList.remove('expanded');
|
||||
if (expandIcon) {
|
||||
expandIcon.classList.remove('expanded');
|
||||
}
|
||||
}
|
||||
|
||||
setupTabs(container) {
|
||||
super.setupTabs(container, {
|
||||
onChange: (targetTab) => {
|
||||
const memberCard = container.closest('.member-card');
|
||||
if (memberCard) {
|
||||
const memberIp = memberCard.dataset.memberIp;
|
||||
this.viewModel.storeActiveTab(memberIp, targetTab);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Restore active tab state
|
||||
restoreActiveTab(container, activeTab) {
|
||||
const tabButtons = container.querySelectorAll('.tab-button');
|
||||
const tabContents = container.querySelectorAll('.tab-content');
|
||||
|
||||
// Remove active class from all buttons and contents
|
||||
tabButtons.forEach(btn => btn.classList.remove('active'));
|
||||
tabContents.forEach(content => content.classList.remove('active'));
|
||||
|
||||
// Add active class to the restored tab
|
||||
const activeButton = container.querySelector(`[data-tab="${activeTab}"]`);
|
||||
const activeContent = container.querySelector(`#${activeTab}-tab`);
|
||||
|
||||
if (activeButton) activeButton.classList.add('active');
|
||||
if (activeContent) activeContent.classList.add('active');
|
||||
}
|
||||
|
||||
// Note: handleRefresh method has been moved to ClusterViewComponent
|
||||
// since the refresh button is in the cluster header, not in the members container
|
||||
|
||||
// Debug method to check component state
|
||||
debugState() {
|
||||
const members = this.viewModel.get('members');
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
const error = this.viewModel.get('error');
|
||||
const expandedCards = this.viewModel.get('expandedCards');
|
||||
const activeTabs = this.viewModel.get('activeTabs');
|
||||
|
||||
logger.debug('ClusterMembersComponent: Debug State:', {
|
||||
isMounted: this.isMounted,
|
||||
container: this.container,
|
||||
members: members,
|
||||
membersCount: members?.length || 0,
|
||||
isLoading: isLoading,
|
||||
error: error,
|
||||
expandedCardsCount: expandedCards?.size || 0,
|
||||
activeTabsCount: activeTabs?.size || 0,
|
||||
loadingTimeout: this.loadingTimeout
|
||||
});
|
||||
|
||||
return { members, isLoading, error, expandedCards, activeTabs };
|
||||
}
|
||||
|
||||
// Manual refresh method that bypasses potential state conflicts
|
||||
async manualRefresh() {
|
||||
logger.debug('ClusterMembersComponent: Manual refresh called');
|
||||
|
||||
try {
|
||||
// Clear any existing loading state
|
||||
this.viewModel.set('isLoading', false);
|
||||
this.viewModel.set('error', null);
|
||||
|
||||
// Force a fresh data load
|
||||
await this.viewModel.updateClusterMembers();
|
||||
|
||||
logger.debug('ClusterMembersComponent: Manual refresh completed');
|
||||
} catch (error) {
|
||||
logger.error('ClusterMembersComponent: Manual refresh failed:', error);
|
||||
this.showErrorState(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
unmount() {
|
||||
if (!this.isMounted) return;
|
||||
|
||||
this.isMounted = false;
|
||||
|
||||
// Clear any pending timeouts
|
||||
if (this.loadingTimeout) {
|
||||
clearTimeout(this.loadingTimeout);
|
||||
this.loadingTimeout = null;
|
||||
}
|
||||
|
||||
if (this.loadingCompletionCheck) {
|
||||
clearTimeout(this.loadingCompletionCheck);
|
||||
this.loadingCompletionCheck = null;
|
||||
}
|
||||
|
||||
// Clear any pending render operations
|
||||
this.renderInProgress = false;
|
||||
|
||||
this.cleanupEventListeners();
|
||||
this.cleanupViewModelListeners();
|
||||
|
||||
logger.debug(`${this.constructor.name} unmounted`);
|
||||
}
|
||||
|
||||
// Override pause method to handle timeouts and operations
|
||||
onPause() {
|
||||
logger.debug('ClusterMembersComponent: Pausing...');
|
||||
|
||||
// Clear any pending timeouts
|
||||
if (this.loadingTimeout) {
|
||||
clearTimeout(this.loadingTimeout);
|
||||
this.loadingTimeout = null;
|
||||
}
|
||||
|
||||
if (this.loadingCompletionCheck) {
|
||||
clearTimeout(this.loadingCompletionCheck);
|
||||
this.loadingCompletionCheck = null;
|
||||
}
|
||||
|
||||
// Mark as paused to prevent new operations
|
||||
this.isPaused = true;
|
||||
}
|
||||
|
||||
// Override resume method to restore functionality
|
||||
onResume() {
|
||||
logger.debug('ClusterMembersComponent: Resuming...');
|
||||
|
||||
this.isPaused = false;
|
||||
|
||||
// Re-setup loading timeout if needed
|
||||
if (!this.loadingTimeout) {
|
||||
this.setupLoadingTimeout();
|
||||
}
|
||||
|
||||
// Check if we need to handle any pending operations
|
||||
this.checkPendingOperations();
|
||||
}
|
||||
|
||||
// Check for any operations that need to be handled after resume
|
||||
checkPendingOperations() {
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
const members = this.viewModel.get('members');
|
||||
|
||||
// If we were loading and it completed while paused, handle the completion
|
||||
if (!isLoading && members && members.length > 0) {
|
||||
logger.debug('ClusterMembersComponent: Handling pending loading completion after resume');
|
||||
this.handleLoadingCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
// Override to determine if re-render is needed on resume
|
||||
shouldRenderOnResume() {
|
||||
// Don't re-render on resume - maintain current state
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
window.ClusterMembersComponent = ClusterMembersComponent;
|
||||
Reference in New Issue
Block a user