chore: restructure public files
This commit is contained in:
229
public/scripts/app.js
Normal file
229
public/scripts/app.js
Normal file
@@ -0,0 +1,229 @@
|
||||
// Main SPORE UI Application
|
||||
|
||||
// Initialize the application when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('=== SPORE UI Application Initialization ===');
|
||||
|
||||
// Initialize the framework (but don't navigate yet)
|
||||
console.log('App: Creating framework instance...');
|
||||
const app = window.app;
|
||||
|
||||
// Create view models
|
||||
console.log('App: Creating view models...');
|
||||
const clusterViewModel = new ClusterViewModel();
|
||||
const firmwareViewModel = new FirmwareViewModel();
|
||||
const topologyViewModel = new TopologyViewModel();
|
||||
console.log('App: View models created:', { clusterViewModel, firmwareViewModel, topologyViewModel });
|
||||
|
||||
// Connect firmware view model to cluster data
|
||||
clusterViewModel.subscribe('members', (members) => {
|
||||
console.log('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);
|
||||
console.log('App: Updated firmware view model with nodes:', nodes);
|
||||
} else {
|
||||
firmwareViewModel.updateAvailableNodes([]);
|
||||
console.log('App: Cleared firmware view model nodes');
|
||||
}
|
||||
});
|
||||
|
||||
// Register routes with their view models
|
||||
console.log('App: Registering routes...');
|
||||
app.registerRoute('cluster', ClusterViewComponent, 'cluster-view', clusterViewModel);
|
||||
app.registerRoute('topology', TopologyGraphComponent, 'topology-view', topologyViewModel);
|
||||
app.registerRoute('firmware', FirmwareViewComponent, 'firmware-view', firmwareViewModel);
|
||||
console.log('App: Routes registered and components pre-initialized');
|
||||
|
||||
// Initialize cluster status component for header badge AFTER main components
|
||||
// DISABLED - causes interference with main cluster functionality
|
||||
/*
|
||||
console.log('App: Initializing cluster status component...');
|
||||
const clusterStatusComponent = new ClusterStatusComponent(
|
||||
document.querySelector('.cluster-status'),
|
||||
clusterViewModel,
|
||||
app.eventBus
|
||||
);
|
||||
clusterStatusComponent.initialize();
|
||||
console.log('App: Cluster status component initialized');
|
||||
*/
|
||||
|
||||
// Set up navigation event listeners
|
||||
console.log('App: Setting up navigation...');
|
||||
app.setupNavigation();
|
||||
|
||||
// Set up cluster status updates (simple approach without component interference)
|
||||
setupClusterStatusUpdates(clusterViewModel);
|
||||
|
||||
// Set up periodic updates for cluster view with state preservation
|
||||
// setupPeriodicUpdates(); // Disabled automatic refresh
|
||||
|
||||
// Now navigate to the default route
|
||||
console.log('App: Navigating to default route...');
|
||||
app.navigateTo('cluster');
|
||||
|
||||
console.log('=== 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 with state preservation
|
||||
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') {
|
||||
console.log('App: Performing smart update to preserve UI state...');
|
||||
viewModel.smartUpdate();
|
||||
} else if (viewModel.updateClusterMembers && typeof viewModel.updateClusterMembers === 'function') {
|
||||
console.log('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);
|
||||
}
|
||||
|
||||
// Set up cluster status updates (simple approach without component interference)
|
||||
function setupClusterStatusUpdates(clusterViewModel) {
|
||||
// Set initial "discovering" state immediately
|
||||
updateClusterStatusBadge(undefined, undefined, undefined);
|
||||
|
||||
// Force a fresh fetch and keep showing "discovering" until we get real data
|
||||
let hasReceivedRealData = false;
|
||||
|
||||
// Subscribe to view model changes to update cluster status
|
||||
clusterViewModel.subscribe('totalNodes', (totalNodes) => {
|
||||
if (hasReceivedRealData) {
|
||||
updateClusterStatusBadge(totalNodes, clusterViewModel.get('clientInitialized'), clusterViewModel.get('error'));
|
||||
}
|
||||
});
|
||||
|
||||
clusterViewModel.subscribe('clientInitialized', (clientInitialized) => {
|
||||
if (hasReceivedRealData) {
|
||||
updateClusterStatusBadge(clusterViewModel.get('totalNodes'), clientInitialized, clusterViewModel.get('error'));
|
||||
}
|
||||
});
|
||||
|
||||
clusterViewModel.subscribe('error', (error) => {
|
||||
if (hasReceivedRealData) {
|
||||
updateClusterStatusBadge(clusterViewModel.get('totalNodes'), clusterViewModel.get('clientInitialized'), error);
|
||||
}
|
||||
});
|
||||
|
||||
// Force a fresh fetch and only update status after we get real data
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
console.log('Cluster Status: Forcing fresh fetch from backend...');
|
||||
const discoveryInfo = await window.apiClient.getDiscoveryInfo();
|
||||
console.log('Cluster Status: Got fresh data:', discoveryInfo);
|
||||
|
||||
// Now we have real data, mark it and update the status
|
||||
hasReceivedRealData = true;
|
||||
updateClusterStatusBadge(discoveryInfo.totalNodes, discoveryInfo.clientInitialized, null);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Cluster Status: Failed to fetch fresh data:', error);
|
||||
hasReceivedRealData = true;
|
||||
updateClusterStatusBadge(0, false, error.message);
|
||||
}
|
||||
}, 100); // Small delay to ensure view model is ready
|
||||
}
|
||||
|
||||
function updateClusterStatusBadge(totalNodes, clientInitialized, error) {
|
||||
const clusterStatusBadge = document.querySelector('.cluster-status');
|
||||
if (!clusterStatusBadge) return;
|
||||
|
||||
let statusText, statusIcon, statusClass;
|
||||
|
||||
// Check if we're still in initial state (no real data yet)
|
||||
const hasRealData = totalNodes !== undefined && clientInitialized !== undefined;
|
||||
|
||||
if (!hasRealData) {
|
||||
statusText = 'Cluster Discovering...';
|
||||
statusIcon = '🔍';
|
||||
statusClass = 'cluster-status-discovering';
|
||||
} else if (error || totalNodes === 0) {
|
||||
// Show "Cluster Offline" for both errors and when no nodes are discovered
|
||||
statusText = 'Cluster Offline';
|
||||
statusIcon = '🔴';
|
||||
statusClass = 'cluster-status-offline';
|
||||
} else if (clientInitialized) {
|
||||
statusText = 'Cluster Online';
|
||||
statusIcon = '🟢';
|
||||
statusClass = 'cluster-status-online';
|
||||
} else {
|
||||
statusText = 'Cluster Connecting';
|
||||
statusIcon = '🟡';
|
||||
statusClass = 'cluster-status-connecting';
|
||||
}
|
||||
|
||||
// Update the badge
|
||||
clusterStatusBadge.innerHTML = `${statusIcon} ${statusText}`;
|
||||
|
||||
// Remove all existing status classes
|
||||
clusterStatusBadge.classList.remove('cluster-status-online', 'cluster-status-offline', 'cluster-status-connecting', 'cluster-status-error', 'cluster-status-discovering');
|
||||
|
||||
// Add the appropriate status class
|
||||
clusterStatusBadge.classList.add(statusClass);
|
||||
}
|
||||
|
||||
// Global error handler
|
||||
window.addEventListener('error', function(event) {
|
||||
console.error('Global error:', event.error);
|
||||
});
|
||||
|
||||
// Global unhandled promise rejection handler
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
console.error('Unhandled promise rejection:', event.reason);
|
||||
});
|
||||
|
||||
// Clean up on page unload
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (window.app) {
|
||||
console.log('App: Cleaning up cached components...');
|
||||
window.app.cleanup();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user