| |
| |
| |
| |
|
|
| class TabManager { |
| constructor() { |
| this.currentTab = 'market'; |
| this.tabs = {}; |
| this.onChangeCallbacks = []; |
| } |
|
|
| |
| |
| |
| init() { |
| |
| this.registerTab('market', '📊', 'Market', this.loadMarketTab.bind(this)); |
| this.registerTab('api-monitor', '📡', 'API Monitor', this.loadAPIMonitorTab.bind(this)); |
| this.registerTab('advanced', '⚡', 'Advanced', this.loadAdvancedTab.bind(this)); |
| this.registerTab('admin', '⚙️', 'Admin', this.loadAdminTab.bind(this)); |
| this.registerTab('huggingface', '🤗', 'HuggingFace', this.loadHuggingFaceTab.bind(this)); |
| this.registerTab('pools', '🔄', 'Pools', this.loadPoolsTab.bind(this)); |
| this.registerTab('providers', '🧩', 'Providers', this.loadProvidersTab.bind(this)); |
| this.registerTab('logs', '📝', 'Logs', this.loadLogsTab.bind(this)); |
| this.registerTab('reports', '📊', 'Reports', this.loadReportsTab.bind(this)); |
|
|
| |
| this.setupEventListeners(); |
|
|
| |
| const hash = window.location.hash.slice(1); |
| const initialTab = hash && this.tabs[hash] ? hash : 'market'; |
| this.switchTab(initialTab); |
|
|
| |
| window.addEventListener('popstate', () => { |
| const tabId = window.location.hash.slice(1) || 'market'; |
| this.switchTab(tabId, false); |
| }); |
|
|
| console.log('[TabManager] Initialized with', Object.keys(this.tabs).length, 'tabs'); |
| } |
|
|
| |
| |
| |
| registerTab(id, icon, label, loadFn) { |
| this.tabs[id] = { |
| id, |
| icon, |
| label, |
| loadFn, |
| loaded: false, |
| }; |
| } |
|
|
| |
| |
| |
| setupEventListeners() { |
| |
| document.querySelectorAll('.nav-tab-btn').forEach(btn => { |
| btn.addEventListener('click', (e) => { |
| e.preventDefault(); |
| const tabId = btn.dataset.tab; |
| if (tabId && this.tabs[tabId]) { |
| this.switchTab(tabId); |
| } |
| }); |
|
|
| |
| btn.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter' || e.key === ' ') { |
| e.preventDefault(); |
| const tabId = btn.dataset.tab; |
| if (tabId && this.tabs[tabId]) { |
| this.switchTab(tabId); |
| } |
| } |
| }); |
| }); |
|
|
| |
| document.querySelectorAll('.mobile-nav-tab-btn').forEach(btn => { |
| btn.addEventListener('click', (e) => { |
| e.preventDefault(); |
| const tabId = btn.dataset.tab; |
| if (tabId && this.tabs[tabId]) { |
| this.switchTab(tabId); |
| } |
| }); |
| }); |
| } |
|
|
| |
| |
| |
| switchTab(tabId, updateHistory = true) { |
| if (!this.tabs[tabId]) { |
| console.warn(`[TabManager] Tab ${tabId} not found`); |
| return; |
| } |
|
|
| |
| if (window.featureFlagsManager && this.isTabDisabled(tabId)) { |
| this.showFeatureDisabledMessage(tabId); |
| return; |
| } |
|
|
| console.log(`[TabManager] Switching to tab: ${tabId}`); |
|
|
| |
| document.querySelectorAll('[data-tab]').forEach(btn => { |
| if (btn.dataset.tab === tabId) { |
| btn.classList.add('active'); |
| btn.setAttribute('aria-selected', 'true'); |
| } else { |
| btn.classList.remove('active'); |
| btn.setAttribute('aria-selected', 'false'); |
| } |
| }); |
|
|
| |
| document.querySelectorAll('.tab-content').forEach(content => { |
| content.classList.remove('active'); |
| content.setAttribute('aria-hidden', 'true'); |
| }); |
|
|
| |
| const tabContent = document.getElementById(`${tabId}-tab`); |
| if (tabContent) { |
| tabContent.classList.add('active'); |
| tabContent.setAttribute('aria-hidden', 'false'); |
| } |
|
|
| |
| const tab = this.tabs[tabId]; |
| if (!tab.loaded && tab.loadFn) { |
| tab.loadFn(); |
| tab.loaded = true; |
| } |
|
|
| |
| if (updateHistory) { |
| window.location.hash = tabId; |
| } |
|
|
| |
| this.currentTab = tabId; |
|
|
| |
| this.notifyChange(tabId); |
|
|
| |
| this.announceTabChange(tab.label); |
| } |
|
|
| |
| |
| |
| isTabDisabled(tabId) { |
| if (!window.featureFlagsManager) return false; |
|
|
| const flagMap = { |
| 'market': 'enableMarketOverview', |
| 'huggingface': 'enableHFIntegration', |
| 'pools': 'enablePoolManagement', |
| 'advanced': 'enableAdvancedCharts', |
| }; |
|
|
| const flagName = flagMap[tabId]; |
| if (flagName) { |
| return !window.featureFlagsManager.isEnabled(flagName); |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| showFeatureDisabledMessage(tabId) { |
| const tab = this.tabs[tabId]; |
| alert(`The "${tab.label}" feature is currently disabled. Enable it in Admin > Feature Flags.`); |
| } |
|
|
| |
| |
| |
| announceTabChange(label) { |
| const liveRegion = document.getElementById('sr-live-region'); |
| if (liveRegion) { |
| liveRegion.textContent = `Switched to ${label} tab`; |
| } |
| } |
|
|
| |
| |
| |
| onChange(callback) { |
| this.onChangeCallbacks.push(callback); |
| } |
|
|
| |
| |
| |
| notifyChange(tabId) { |
| this.onChangeCallbacks.forEach(callback => { |
| try { |
| callback(tabId); |
| } catch (error) { |
| console.error('[TabManager] Error in change callback:', error); |
| } |
| }); |
| } |
|
|
| |
|
|
| async loadMarketTab() { |
| console.log('[TabManager] Loading Market tab'); |
| try { |
| const marketData = await window.apiClient.getMarket(); |
| this.renderMarketData(marketData); |
| } catch (error) { |
| console.error('[TabManager] Error loading market data:', error); |
| this.showError('market-tab', 'Failed to load market data'); |
| } |
| } |
|
|
| async loadAPIMonitorTab() { |
| console.log('[TabManager] Loading API Monitor tab'); |
| try { |
| const providers = await window.apiClient.getProviders(); |
| this.renderAPIMonitor(providers); |
| } catch (error) { |
| console.error('[TabManager] Error loading API monitor:', error); |
| this.showError('api-monitor-tab', 'Failed to load API monitor data'); |
| } |
| } |
|
|
| async loadAdvancedTab() { |
| console.log('[TabManager] Loading Advanced tab'); |
| try { |
| const stats = await window.apiClient.getStats(); |
| this.renderAdvanced(stats); |
| } catch (error) { |
| console.error('[TabManager] Error loading advanced data:', error); |
| this.showError('advanced-tab', 'Failed to load advanced data'); |
| } |
| } |
|
|
| async loadAdminTab() { |
| console.log('[TabManager] Loading Admin tab'); |
| try { |
| const flags = await window.apiClient.getFeatureFlags(); |
| this.renderAdmin(flags); |
| } catch (error) { |
| console.error('[TabManager] Error loading admin data:', error); |
| this.showError('admin-tab', 'Failed to load admin data'); |
| } |
| } |
|
|
| async loadHuggingFaceTab() { |
| console.log('[TabManager] Loading HuggingFace tab'); |
| try { |
| const hfHealth = await window.apiClient.getHFHealth(); |
| this.renderHuggingFace(hfHealth); |
| } catch (error) { |
| console.error('[TabManager] Error loading HuggingFace data:', error); |
| this.showError('huggingface-tab', 'Failed to load HuggingFace data'); |
| } |
| } |
|
|
| async loadPoolsTab() { |
| console.log('[TabManager] Loading Pools tab'); |
| try { |
| const pools = await window.apiClient.getPools(); |
| this.renderPools(pools); |
| } catch (error) { |
| console.error('[TabManager] Error loading pools data:', error); |
| this.showError('pools-tab', 'Failed to load pools data'); |
| } |
| } |
|
|
| async loadProvidersTab() { |
| console.log('[TabManager] Loading Providers tab'); |
| try { |
| const providers = await window.apiClient.getProviders(); |
| this.renderProviders(providers); |
| } catch (error) { |
| console.error('[TabManager] Error loading providers data:', error); |
| this.showError('providers-tab', 'Failed to load providers data'); |
| } |
| } |
|
|
| async loadLogsTab() { |
| console.log('[TabManager] Loading Logs tab'); |
| try { |
| const logs = await window.apiClient.getRecentLogs(); |
| this.renderLogs(logs); |
| } catch (error) { |
| console.error('[TabManager] Error loading logs:', error); |
| this.showError('logs-tab', 'Failed to load logs'); |
| } |
| } |
|
|
| async loadReportsTab() { |
| console.log('[TabManager] Loading Reports tab'); |
| try { |
| const discoveryReport = await window.apiClient.getDiscoveryReport(); |
| const modelsReport = await window.apiClient.getModelsReport(); |
| this.renderReports({ discoveryReport, modelsReport }); |
| } catch (error) { |
| console.error('[TabManager] Error loading reports:', error); |
| this.showError('reports-tab', 'Failed to load reports'); |
| } |
| } |
|
|
| |
|
|
| renderMarketData(data) { |
| if (window.dashboardApp && window.dashboardApp.renderMarketTab) { |
| window.dashboardApp.renderMarketTab(data); |
| } |
| } |
|
|
| renderAPIMonitor(data) { |
| if (window.dashboardApp && window.dashboardApp.renderAPIMonitorTab) { |
| window.dashboardApp.renderAPIMonitorTab(data); |
| } |
| } |
|
|
| renderAdvanced(data) { |
| if (window.dashboardApp && window.dashboardApp.renderAdvancedTab) { |
| window.dashboardApp.renderAdvancedTab(data); |
| } |
| } |
|
|
| renderAdmin(data) { |
| if (window.dashboardApp && window.dashboardApp.renderAdminTab) { |
| window.dashboardApp.renderAdminTab(data); |
| } |
| } |
|
|
| renderHuggingFace(data) { |
| if (window.dashboardApp && window.dashboardApp.renderHuggingFaceTab) { |
| window.dashboardApp.renderHuggingFaceTab(data); |
| } |
| } |
|
|
| renderPools(data) { |
| if (window.dashboardApp && window.dashboardApp.renderPoolsTab) { |
| window.dashboardApp.renderPoolsTab(data); |
| } |
| } |
|
|
| renderProviders(data) { |
| if (window.dashboardApp && window.dashboardApp.renderProvidersTab) { |
| window.dashboardApp.renderProvidersTab(data); |
| } |
| } |
|
|
| renderLogs(data) { |
| if (window.dashboardApp && window.dashboardApp.renderLogsTab) { |
| window.dashboardApp.renderLogsTab(data); |
| } |
| } |
|
|
| renderReports(data) { |
| if (window.dashboardApp && window.dashboardApp.renderReportsTab) { |
| window.dashboardApp.renderReportsTab(data); |
| } |
| } |
|
|
| |
| |
| |
| showError(tabId, message) { |
| const tabElement = document.getElementById(tabId); |
| if (tabElement) { |
| const contentArea = tabElement.querySelector('.tab-body') || tabElement; |
| contentArea.innerHTML = ` |
| <div class="alert alert-error"> |
| <strong>❌ Error:</strong> ${message} |
| </div> |
| `; |
| } |
| } |
| } |
|
|
| |
| window.tabManager = new TabManager(); |
|
|
| |
| document.addEventListener('DOMContentLoaded', () => { |
| window.tabManager.init(); |
| }); |
|
|
| console.log('[TabManager] Module loaded'); |
|
|