| |
| |
| |
| |
|
|
| class FeatureFlagsManager { |
| constructor() { |
| this.flags = {}; |
| this.localStorageKey = 'crypto_monitor_feature_flags'; |
| this.apiEndpoint = '/api/feature-flags'; |
| this.listeners = []; |
| } |
|
|
| |
| |
| |
| async init() { |
| |
| this.loadFromLocalStorage(); |
|
|
| |
| await this.syncWithBackend(); |
|
|
| |
| setInterval(() => this.syncWithBackend(), 30000); |
|
|
| return this.flags; |
| } |
|
|
| |
| |
| |
| loadFromLocalStorage() { |
| try { |
| const stored = localStorage.getItem(this.localStorageKey); |
| if (stored) { |
| const data = JSON.parse(stored); |
| this.flags = data.flags || {}; |
| console.log('[FeatureFlags] Loaded from localStorage:', this.flags); |
| } |
| } catch (error) { |
| console.error('[FeatureFlags] Error loading from localStorage:', error); |
| } |
| } |
|
|
| |
| |
| |
| saveToLocalStorage() { |
| try { |
| const data = { |
| flags: this.flags, |
| updated_at: new Date().toISOString() |
| }; |
| localStorage.setItem(this.localStorageKey, JSON.stringify(data)); |
| console.log('[FeatureFlags] Saved to localStorage'); |
| } catch (error) { |
| console.error('[FeatureFlags] Error saving to localStorage:', error); |
| } |
| } |
|
|
| |
| |
| |
| async syncWithBackend() { |
| try { |
| const response = await fetch(this.apiEndpoint); |
| if (!response.ok) { |
| throw new Error(`HTTP ${response.status}`); |
| } |
|
|
| const data = await response.json(); |
| this.flags = data.flags || {}; |
| this.saveToLocalStorage(); |
| this.notifyListeners(); |
|
|
| console.log('[FeatureFlags] Synced with backend:', this.flags); |
| return this.flags; |
| } catch (error) { |
| console.error('[FeatureFlags] Error syncing with backend:', error); |
| |
| return this.flags; |
| } |
| } |
|
|
| |
| |
| |
| isEnabled(flagName) { |
| return this.flags[flagName] === true; |
| } |
|
|
| |
| |
| |
| getAll() { |
| return { ...this.flags }; |
| } |
|
|
| |
| |
| |
| async setFlag(flagName, value) { |
| try { |
| const response = await fetch(`${this.apiEndpoint}/${flagName}`, { |
| method: 'PUT', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| flag_name: flagName, |
| value: value |
| }) |
| }); |
|
|
| if (!response.ok) { |
| throw new Error(`HTTP ${response.status}`); |
| } |
|
|
| const data = await response.json(); |
| if (data.success) { |
| this.flags[flagName] = value; |
| this.saveToLocalStorage(); |
| this.notifyListeners(); |
| console.log(`[FeatureFlags] Set ${flagName} = ${value}`); |
| return true; |
| } |
|
|
| return false; |
| } catch (error) { |
| console.error(`[FeatureFlags] Error setting flag ${flagName}:`, error); |
| return false; |
| } |
| } |
|
|
| |
| |
| |
| async updateFlags(updates) { |
| try { |
| const response = await fetch(this.apiEndpoint, { |
| method: 'PUT', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| flags: updates |
| }) |
| }); |
|
|
| if (!response.ok) { |
| throw new Error(`HTTP ${response.status}`); |
| } |
|
|
| const data = await response.json(); |
| if (data.success) { |
| this.flags = data.flags; |
| this.saveToLocalStorage(); |
| this.notifyListeners(); |
| console.log('[FeatureFlags] Updated flags:', updates); |
| return true; |
| } |
|
|
| return false; |
| } catch (error) { |
| console.error('[FeatureFlags] Error updating flags:', error); |
| return false; |
| } |
| } |
|
|
| |
| |
| |
| async resetToDefaults() { |
| try { |
| const response = await fetch(`${this.apiEndpoint}/reset`, { |
| method: 'POST' |
| }); |
|
|
| if (!response.ok) { |
| throw new Error(`HTTP ${response.status}`); |
| } |
|
|
| const data = await response.json(); |
| if (data.success) { |
| this.flags = data.flags; |
| this.saveToLocalStorage(); |
| this.notifyListeners(); |
| console.log('[FeatureFlags] Reset to defaults'); |
| return true; |
| } |
|
|
| return false; |
| } catch (error) { |
| console.error('[FeatureFlags] Error resetting flags:', error); |
| return false; |
| } |
| } |
|
|
| |
| |
| |
| onChange(callback) { |
| this.listeners.push(callback); |
| return () => { |
| const index = this.listeners.indexOf(callback); |
| if (index > -1) { |
| this.listeners.splice(index, 1); |
| } |
| }; |
| } |
|
|
| |
| |
| |
| notifyListeners() { |
| this.listeners.forEach(callback => { |
| try { |
| callback(this.flags); |
| } catch (error) { |
| console.error('[FeatureFlags] Error in listener:', error); |
| } |
| }); |
| } |
|
|
| |
| |
| |
| renderUI(containerId) { |
| const container = document.getElementById(containerId); |
| if (!container) { |
| console.error(`[FeatureFlags] Container #${containerId} not found`); |
| return; |
| } |
|
|
| const flagDescriptions = { |
| enableWhaleTracking: 'Show whale transaction tracking', |
| enableMarketOverview: 'Display market overview dashboard', |
| enableFearGreedIndex: 'Show Fear & Greed sentiment index', |
| enableNewsFeed: 'Display cryptocurrency news feed', |
| enableSentimentAnalysis: 'Enable sentiment analysis features', |
| enableMlPredictions: 'Show ML-powered price predictions', |
| enableProxyAutoMode: 'Automatic proxy for failing APIs', |
| enableDefiProtocols: 'Display DeFi protocol data', |
| enableTrendingCoins: 'Show trending cryptocurrencies', |
| enableGlobalStats: 'Display global market statistics', |
| enableProviderRotation: 'Enable provider rotation system', |
| enableWebSocketStreaming: 'Real-time WebSocket updates', |
| enableDatabaseLogging: 'Log provider health to database', |
| enableRealTimeAlerts: 'Show real-time alert notifications', |
| enableAdvancedCharts: 'Display advanced charting', |
| enableExportFeatures: 'Enable data export functions', |
| enableCustomProviders: 'Allow custom API providers', |
| enablePoolManagement: 'Enable provider pool management', |
| enableHFIntegration: 'HuggingFace model integration' |
| }; |
|
|
| let html = '<div class="feature-flags-container">'; |
| html += '<h3>Feature Flags</h3>'; |
| html += '<div class="feature-flags-list">'; |
|
|
| Object.keys(this.flags).forEach(flagName => { |
| const enabled = this.flags[flagName]; |
| const description = flagDescriptions[flagName] || flagName; |
|
|
| html += ` |
| <div class="feature-flag-item"> |
| <label class="feature-flag-label"> |
| <input |
| type="checkbox" |
| class="feature-flag-toggle" |
| data-flag="${flagName}" |
| ${enabled ? 'checked' : ''} |
| /> |
| <span class="feature-flag-name">${description}</span> |
| </label> |
| <span class="feature-flag-status ${enabled ? 'enabled' : 'disabled'}"> |
| ${enabled ? '✓ Enabled' : '✗ Disabled'} |
| </span> |
| </div> |
| `; |
| }); |
|
|
| html += '</div>'; |
| html += '<div class="feature-flags-actions">'; |
| html += '<button id="ff-reset-btn" class="btn btn-secondary">Reset to Defaults</button>'; |
| html += '</div>'; |
| html += '</div>'; |
|
|
| container.innerHTML = html; |
|
|
| |
| container.querySelectorAll('.feature-flag-toggle').forEach(toggle => { |
| toggle.addEventListener('change', async (e) => { |
| const flagName = e.target.dataset.flag; |
| const value = e.target.checked; |
| await this.setFlag(flagName, value); |
| }); |
| }); |
|
|
| const resetBtn = container.querySelector('#ff-reset-btn'); |
| if (resetBtn) { |
| resetBtn.addEventListener('click', async () => { |
| if (confirm('Reset all feature flags to defaults?')) { |
| await this.resetToDefaults(); |
| this.renderUI(containerId); |
| } |
| }); |
| } |
|
|
| |
| this.onChange(() => { |
| this.renderUI(containerId); |
| }); |
| } |
| } |
|
|
| |
| window.featureFlagsManager = new FeatureFlagsManager(); |
|
|
| |
| document.addEventListener('DOMContentLoaded', () => { |
| window.featureFlagsManager.init().then(() => { |
| console.log('[FeatureFlags] Initialized'); |
| }); |
| }); |
|
|