224 lines
9.6 KiB
JavaScript
224 lines
9.6 KiB
JavaScript
// Main SPORE UI Application
|
|
|
|
// Initialize the application when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
logger.debug('=== SPORE UI Application Initialization ===');
|
|
|
|
// Initialize the framework (but don't navigate yet)
|
|
logger.debug('App: Creating framework instance...');
|
|
const app = window.app;
|
|
|
|
// Components are loaded via script tags in order; no blocking wait required
|
|
|
|
// Create view models
|
|
logger.debug('App: Creating view models...');
|
|
const clusterViewModel = new ClusterViewModel();
|
|
const firmwareViewModel = new FirmwareViewModel();
|
|
const clusterFirmwareViewModel = new ClusterFirmwareViewModel();
|
|
const topologyViewModel = new TopologyViewModel();
|
|
const monitoringViewModel = new MonitoringViewModel();
|
|
const eventsViewModel = new EventViewModel();
|
|
logger.debug('App: View models created:', { clusterViewModel, firmwareViewModel, clusterFirmwareViewModel, topologyViewModel, monitoringViewModel, eventsViewModel });
|
|
|
|
// Connect firmware view model to cluster data
|
|
clusterViewModel.subscribe('members', (members) => {
|
|
logger.debug('App: Members subscription triggered:', members);
|
|
if (members && members.length > 0) {
|
|
// Extract node information for firmware view
|
|
const nodes = members.map(member => ({
|
|
ip: member.ip,
|
|
hostname: member.hostname || member.ip,
|
|
labels: member.labels || {}
|
|
}));
|
|
firmwareViewModel.updateAvailableNodes(nodes);
|
|
logger.debug('App: Updated firmware view model with nodes:', nodes);
|
|
} else {
|
|
firmwareViewModel.updateAvailableNodes([]);
|
|
logger.debug('App: Cleared firmware view model nodes');
|
|
}
|
|
});
|
|
|
|
// Connect cluster firmware view model to cluster data
|
|
// Note: This subscription is disabled because target nodes should be set explicitly
|
|
// when opening the firmware deploy drawer, not automatically updated
|
|
/*
|
|
clusterViewModel.subscribe('members', (members) => {
|
|
logger.debug('App: Members subscription triggered for cluster firmware:', members);
|
|
if (members && members.length > 0) {
|
|
// Extract node information for cluster firmware view
|
|
const nodes = members.map(member => ({
|
|
ip: member.ip,
|
|
hostname: member.hostname || member.ip,
|
|
labels: member.labels || {}
|
|
}));
|
|
clusterFirmwareViewModel.setTargetNodes(nodes);
|
|
logger.debug('App: Updated cluster firmware view model with nodes:', nodes);
|
|
} else {
|
|
clusterFirmwareViewModel.setTargetNodes([]);
|
|
logger.debug('App: Cleared cluster firmware view model nodes');
|
|
}
|
|
});
|
|
*/
|
|
|
|
// Register routes with their view models
|
|
logger.debug('App: Registering routes...');
|
|
app.registerRoute('cluster', ClusterViewComponent, 'cluster-view', clusterViewModel);
|
|
app.registerRoute('topology', TopologyGraphComponent, 'topology-view', topologyViewModel);
|
|
app.registerRoute('firmware', FirmwareViewComponent, 'firmware-view', firmwareViewModel);
|
|
app.registerRoute('monitoring', MonitoringViewComponent, 'monitoring-view', monitoringViewModel);
|
|
app.registerRoute('events', EventComponent, 'events-view', eventsViewModel);
|
|
logger.debug('App: Routes registered and components pre-initialized');
|
|
|
|
// Initialize cluster status component for header badge
|
|
logger.debug('App: Initializing cluster status component...');
|
|
const clusterStatusComponent = new ClusterStatusComponent(
|
|
document.querySelector('.cluster-status'),
|
|
clusterViewModel,
|
|
app.eventBus
|
|
);
|
|
clusterStatusComponent.mount();
|
|
logger.debug('App: Cluster status component initialized');
|
|
|
|
// Set up random primary node button
|
|
logger.debug('App: Setting up random primary node button...');
|
|
const randomPrimaryBtn = document.getElementById('random-primary-toggle');
|
|
if (randomPrimaryBtn) {
|
|
randomPrimaryBtn.addEventListener('click', async function() {
|
|
try {
|
|
// Add spinning animation
|
|
randomPrimaryBtn.classList.add('spinning');
|
|
randomPrimaryBtn.disabled = true;
|
|
|
|
logger.debug('App: Selecting random primary node...');
|
|
await clusterViewModel.selectRandomPrimaryNode();
|
|
|
|
// Show success state briefly
|
|
logger.info('App: Random primary node selected successfully');
|
|
|
|
// Refresh topology to show new primary node connections
|
|
// Wait a bit for the backend to update, then refresh topology
|
|
setTimeout(async () => {
|
|
logger.debug('App: Refreshing topology after primary node change...');
|
|
try {
|
|
await topologyViewModel.updateNetworkTopology();
|
|
logger.debug('App: Topology refreshed successfully');
|
|
} catch (error) {
|
|
logger.error('App: Failed to refresh topology:', error);
|
|
}
|
|
}, 1000);
|
|
|
|
// Also refresh cluster view to update member list with new primary
|
|
setTimeout(async () => {
|
|
logger.debug('App: Refreshing cluster view after primary node change...');
|
|
try {
|
|
if (clusterViewModel.updateClusterMembers) {
|
|
await clusterViewModel.updateClusterMembers();
|
|
}
|
|
logger.debug('App: Cluster view refreshed successfully');
|
|
} catch (error) {
|
|
logger.error('App: Failed to refresh cluster view:', error);
|
|
}
|
|
}, 1000);
|
|
|
|
// Remove spinning animation after delay
|
|
setTimeout(() => {
|
|
randomPrimaryBtn.classList.remove('spinning');
|
|
randomPrimaryBtn.disabled = false;
|
|
}, 1500);
|
|
|
|
} catch (error) {
|
|
logger.error('App: Failed to select random primary node:', error);
|
|
randomPrimaryBtn.classList.remove('spinning');
|
|
randomPrimaryBtn.disabled = false;
|
|
|
|
// Show error notification (could be enhanced with a toast notification)
|
|
alert('Failed to select random primary node: ' + error.message);
|
|
}
|
|
});
|
|
logger.debug('App: Random primary node button configured');
|
|
}
|
|
|
|
// Set up navigation event listeners
|
|
logger.debug('App: Setting up navigation...');
|
|
app.setupNavigation();
|
|
|
|
// Now navigate to the default route
|
|
logger.debug('App: Navigating to default route...');
|
|
app.navigateTo('cluster');
|
|
|
|
logger.debug('=== SPORE UI Application initialization completed ===');
|
|
});
|
|
|
|
// Burger menu toggle for mobile
|
|
(function setupBurgerMenu(){
|
|
document.addEventListener('DOMContentLoaded', function(){
|
|
const nav = document.querySelector('.main-navigation');
|
|
const burger = document.getElementById('burger-btn');
|
|
const navLeft = nav ? nav.querySelector('.nav-left') : null;
|
|
if (!nav || !burger || !navLeft) return;
|
|
burger.addEventListener('click', function(e){
|
|
e.preventDefault();
|
|
nav.classList.toggle('mobile-open');
|
|
});
|
|
// Close menu when a nav tab is clicked
|
|
navLeft.addEventListener('click', function(e){
|
|
const btn = e.target.closest('.nav-tab');
|
|
if (btn && nav.classList.contains('mobile-open')) {
|
|
nav.classList.remove('mobile-open');
|
|
}
|
|
});
|
|
// Close menu on outside click
|
|
document.addEventListener('click', function(e){
|
|
if (!nav.contains(e.target) && nav.classList.contains('mobile-open')) {
|
|
nav.classList.remove('mobile-open');
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
|
|
// Set up periodic updates
|
|
function setupPeriodicUpdates() {
|
|
// Auto-refresh cluster members every 30 seconds using smart update
|
|
setInterval(() => {
|
|
if (window.app.currentView && window.app.currentView.viewModel) {
|
|
const viewModel = window.app.currentView.viewModel;
|
|
|
|
// Use smart update if available, otherwise fall back to regular update
|
|
if (viewModel.smartUpdate && typeof viewModel.smartUpdate === 'function') {
|
|
logger.debug('App: Performing smart update...');
|
|
viewModel.smartUpdate();
|
|
} else if (viewModel.updateClusterMembers && typeof viewModel.updateClusterMembers === 'function') {
|
|
logger.debug('App: Performing regular update...');
|
|
viewModel.updateClusterMembers();
|
|
}
|
|
}
|
|
}, 30000);
|
|
|
|
// Update primary node display every 10 seconds (this is lightweight and doesn't affect UI state)
|
|
setInterval(() => {
|
|
if (window.app.currentView && window.app.currentView.viewModel) {
|
|
const viewModel = window.app.currentView.viewModel;
|
|
if (viewModel.updatePrimaryNodeDisplay && typeof viewModel.updatePrimaryNodeDisplay === 'function') {
|
|
viewModel.updatePrimaryNodeDisplay();
|
|
}
|
|
}
|
|
}, 10000);
|
|
}
|
|
|
|
// Global error handler
|
|
window.addEventListener('error', function(event) {
|
|
logger.error('Global error:', event.error);
|
|
});
|
|
|
|
// Global unhandled promise rejection handler
|
|
window.addEventListener('unhandledrejection', function(event) {
|
|
logger.error('Unhandled promise rejection:', event.reason);
|
|
});
|
|
|
|
// Clean up on page unload
|
|
window.addEventListener('beforeunload', function() {
|
|
if (window.app) {
|
|
logger.debug('App: Cleaning up cached components...');
|
|
window.app.cleanup();
|
|
}
|
|
});
|