| |
| |
| |
| |
|
|
| import { api } from '../../shared/js/core/api-client.js'; |
| import { LayoutManager } from '../../shared/js/core/layout-manager.js'; |
| import { Toast } from '../../shared/js/components/toast.js'; |
|
|
| |
| const DEFAULT_SETTINGS = { |
| tokens: { |
| hfToken: '', |
| coingeckoKey: '', |
| cmcKey: '', |
| etherscanKey: '', |
| cryptocompareKey: '', |
| }, |
| telegram: { |
| botToken: '', |
| chatId: '', |
| enabled: true, |
| silent: false, |
| includeCharts: true, |
| }, |
| signals: { |
| bullish: true, |
| bearish: true, |
| whale: true, |
| news: false, |
| sentiment: true, |
| price: true, |
| confidenceThreshold: 70, |
| priceChangeThreshold: 5, |
| whaleThreshold: 100000, |
| watchedCoins: 'BTC, ETH, SOL', |
| }, |
| scheduling: { |
| autoRefreshEnabled: true, |
| intervalMarket: 30, |
| intervalNews: 120, |
| intervalSentiment: 300, |
| intervalWhale: 60, |
| intervalBlockchain: 300, |
| intervalModels: 600, |
| quietHoursEnabled: false, |
| quietStart: '22:00', |
| quietEnd: '08:00', |
| }, |
| notifications: { |
| browser: true, |
| sound: true, |
| toast: true, |
| soundType: 'default', |
| volume: 50, |
| }, |
| appearance: { |
| theme: 'dark', |
| compactMode: false, |
| showAnimations: true, |
| showBgEffects: true, |
| }, |
| }; |
|
|
| const STORAGE_KEY = 'crypto_monitor_settings'; |
|
|
| class SettingsPage { |
| constructor() { |
| this.settings = this.loadSettings(); |
| this.activeSection = 'api-tokens'; |
| } |
|
|
| async init() { |
| try { |
| await LayoutManager.injectLayouts(); |
| LayoutManager.setActiveNav('settings'); |
| |
| this.bindEvents(); |
| this.populateForm(); |
| this.applySettings(); |
| } catch (error) { |
| console.error('[Settings] Init error:', error); |
| Toast.error('Failed to initialize settings page'); |
| } |
| } |
|
|
| loadSettings() { |
| try { |
| const saved = localStorage.getItem(STORAGE_KEY); |
| if (saved) { |
| const parsed = JSON.parse(saved); |
| |
| return this.deepMerge(DEFAULT_SETTINGS, parsed); |
| } |
| } catch (error) { |
| console.warn('[Settings] Could not load settings:', error); |
| } |
| return { ...DEFAULT_SETTINGS }; |
| } |
|
|
| saveSettings() { |
| try { |
| localStorage.setItem(STORAGE_KEY, JSON.stringify(this.settings)); |
| return true; |
| } catch (error) { |
| console.error('[Settings] Could not save settings:', error); |
| return false; |
| } |
| } |
|
|
| deepMerge(target, source) { |
| const result = { ...target }; |
| for (const key in source) { |
| if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { |
| result[key] = this.deepMerge(target[key] || {}, source[key]); |
| } else { |
| result[key] = source[key]; |
| } |
| } |
| return result; |
| } |
|
|
| bindEvents() { |
| |
| document.querySelectorAll('.settings-nav-btn').forEach(btn => { |
| btn.addEventListener('click', (e) => this.switchSection(e.target.closest('.settings-nav-btn').dataset.section)); |
| }); |
|
|
| |
| document.getElementById('save-all-btn')?.addEventListener('click', () => this.saveAllSettings()); |
|
|
| |
| document.getElementById('reset-btn')?.addEventListener('click', () => this.resetSettings()); |
|
|
| |
| document.querySelectorAll('.toggle-visibility').forEach(btn => { |
| btn.addEventListener('click', (e) => { |
| const targetId = e.target.closest('.toggle-visibility').dataset.target; |
| this.togglePasswordVisibility(targetId); |
| }); |
| }); |
|
|
| |
| this.bindRangeInput('signal-confidence', 'confidence-value', '%'); |
| this.bindRangeInput('price-change-threshold', 'price-threshold-value', '%'); |
| this.bindRangeInput('notif-volume', 'volume-value', '%'); |
|
|
| |
| document.getElementById('save-tokens-btn')?.addEventListener('click', () => this.saveTokens()); |
| document.getElementById('test-tokens-btn')?.addEventListener('click', () => this.testTokens()); |
| document.getElementById('save-telegram-btn')?.addEventListener('click', () => this.saveTelegram()); |
| document.getElementById('test-telegram-btn')?.addEventListener('click', () => this.testTelegram()); |
| document.getElementById('save-signals-btn')?.addEventListener('click', () => this.saveSignals()); |
| document.getElementById('save-scheduling-btn')?.addEventListener('click', () => this.saveScheduling()); |
| document.getElementById('save-notif-btn')?.addEventListener('click', () => this.saveNotifications()); |
| document.getElementById('test-notif-btn')?.addEventListener('click', () => this.testNotification()); |
| document.getElementById('save-appearance-btn')?.addEventListener('click', () => this.saveAppearance()); |
|
|
| |
| document.querySelectorAll('input[name="theme"]').forEach(radio => { |
| radio.addEventListener('change', (e) => { |
| this.settings.appearance.theme = e.target.value; |
| this.applyTheme(); |
| }); |
| }); |
|
|
| |
| document.querySelectorAll('.toggle-switch input').forEach(toggle => { |
| toggle.addEventListener('change', () => this.handleToggleChange(toggle)); |
| }); |
| } |
|
|
| bindRangeInput(rangeId, valueId, suffix = '') { |
| const range = document.getElementById(rangeId); |
| const valueEl = document.getElementById(valueId); |
| if (range && valueEl) { |
| range.addEventListener('input', () => { |
| valueEl.textContent = `${range.value}${suffix}`; |
| }); |
| } |
| } |
|
|
| switchSection(sectionId) { |
| |
| document.querySelectorAll('.settings-nav-btn').forEach(btn => { |
| btn.classList.toggle('active', btn.dataset.section === sectionId); |
| }); |
|
|
| |
| document.querySelectorAll('.settings-section').forEach(section => { |
| section.classList.toggle('active', section.id === `section-${sectionId}`); |
| }); |
|
|
| this.activeSection = sectionId; |
| } |
|
|
| populateForm() { |
| |
| this.setInputValue('hf-token', this.settings.tokens.hfToken); |
| this.setInputValue('coingecko-key', this.settings.tokens.coingeckoKey); |
| this.setInputValue('cmc-key', this.settings.tokens.cmcKey); |
| this.setInputValue('etherscan-key', this.settings.tokens.etherscanKey); |
| this.setInputValue('cryptocompare-key', this.settings.tokens.cryptocompareKey); |
|
|
| |
| this.setInputValue('telegram-bot-token', this.settings.telegram.botToken); |
| this.setInputValue('telegram-chat-id', this.settings.telegram.chatId); |
| this.setCheckbox('telegram-enabled', this.settings.telegram.enabled); |
| this.setCheckbox('telegram-silent', this.settings.telegram.silent); |
| this.setCheckbox('telegram-charts', this.settings.telegram.includeCharts); |
|
|
| |
| this.setCheckbox('signal-bullish', this.settings.signals.bullish); |
| this.setCheckbox('signal-bearish', this.settings.signals.bearish); |
| this.setCheckbox('signal-whale', this.settings.signals.whale); |
| this.setCheckbox('signal-news', this.settings.signals.news); |
| this.setCheckbox('signal-sentiment', this.settings.signals.sentiment); |
| this.setCheckbox('signal-price', this.settings.signals.price); |
| this.setRangeValue('signal-confidence', this.settings.signals.confidenceThreshold, 'confidence-value', '%'); |
| this.setRangeValue('price-change-threshold', this.settings.signals.priceChangeThreshold, 'price-threshold-value', '%'); |
| this.setInputValue('whale-threshold', this.settings.signals.whaleThreshold); |
| this.setInputValue('watched-coins', this.settings.signals.watchedCoins); |
|
|
| |
| this.setCheckbox('auto-refresh-enabled', this.settings.scheduling.autoRefreshEnabled); |
| this.setSelectValue('interval-market', this.settings.scheduling.intervalMarket); |
| this.setSelectValue('interval-news', this.settings.scheduling.intervalNews); |
| this.setSelectValue('interval-sentiment', this.settings.scheduling.intervalSentiment); |
| this.setSelectValue('interval-whale', this.settings.scheduling.intervalWhale); |
| this.setSelectValue('interval-blockchain', this.settings.scheduling.intervalBlockchain); |
| this.setSelectValue('interval-models', this.settings.scheduling.intervalModels); |
| this.setCheckbox('quiet-hours-enabled', this.settings.scheduling.quietHoursEnabled); |
| this.setInputValue('quiet-start', this.settings.scheduling.quietStart); |
| this.setInputValue('quiet-end', this.settings.scheduling.quietEnd); |
|
|
| |
| this.setCheckbox('notif-browser', this.settings.notifications.browser); |
| this.setCheckbox('notif-sound', this.settings.notifications.sound); |
| this.setCheckbox('notif-toast', this.settings.notifications.toast); |
| this.setSelectValue('notif-sound-type', this.settings.notifications.soundType); |
| this.setRangeValue('notif-volume', this.settings.notifications.volume, 'volume-value', '%'); |
|
|
| |
| this.setRadioValue('theme', this.settings.appearance.theme); |
| this.setCheckbox('compact-mode', this.settings.appearance.compactMode); |
| this.setCheckbox('show-animations', this.settings.appearance.showAnimations); |
| this.setCheckbox('show-bg-effects', this.settings.appearance.showBgEffects); |
| } |
|
|
| |
| setInputValue(id, value) { |
| const el = document.getElementById(id); |
| if (el) el.value = value || ''; |
| } |
|
|
| setCheckbox(id, checked) { |
| const el = document.getElementById(id); |
| if (el) el.checked = checked; |
| } |
|
|
| setSelectValue(id, value) { |
| const el = document.getElementById(id); |
| if (el) el.value = value; |
| } |
|
|
| setRadioValue(name, value) { |
| const radio = document.querySelector(`input[name="${name}"][value="${value}"]`); |
| if (radio) radio.checked = true; |
| } |
|
|
| setRangeValue(id, value, valueDisplayId, suffix = '') { |
| const range = document.getElementById(id); |
| const valueDisplay = document.getElementById(valueDisplayId); |
| if (range) range.value = value; |
| if (valueDisplay) valueDisplay.textContent = `${value}${suffix}`; |
| } |
|
|
| togglePasswordVisibility(inputId) { |
| const input = document.getElementById(inputId); |
| if (input) { |
| input.type = input.type === 'password' ? 'text' : 'password'; |
| } |
| } |
|
|
| handleToggleChange(toggle) { |
| |
| if (toggle.id === 'show-animations') { |
| this.applyAnimations(toggle.checked); |
| } else if (toggle.id === 'show-bg-effects') { |
| this.applyBgEffects(toggle.checked); |
| } |
| } |
|
|
| |
| saveTokens() { |
| this.settings.tokens = { |
| hfToken: document.getElementById('hf-token')?.value || '', |
| coingeckoKey: document.getElementById('coingecko-key')?.value || '', |
| cmcKey: document.getElementById('cmc-key')?.value || '', |
| etherscanKey: document.getElementById('etherscan-key')?.value || '', |
| cryptocompareKey: document.getElementById('cryptocompare-key')?.value || '', |
| }; |
|
|
| if (this.saveSettings()) { |
| Toast.success('API tokens saved successfully'); |
| this.sendTokensToBackend(); |
| } else { |
| Toast.error('Failed to save tokens'); |
| } |
| } |
|
|
| async sendTokensToBackend() { |
| try { |
| await api.post('/settings/tokens', this.settings.tokens); |
| } catch (error) { |
| console.warn('[Settings] Could not sync tokens with backend:', error); |
| } |
| } |
|
|
| async testTokens() { |
| Toast.info('Testing API tokens...'); |
| |
| const results = []; |
| |
| |
| if (this.settings.tokens.hfToken) { |
| try { |
| const response = await fetch('https://huggingface.co/api/whoami-v2', { |
| headers: { 'Authorization': `Bearer ${this.settings.tokens.hfToken}` } |
| }); |
| results.push({ name: 'HuggingFace', ok: response.ok }); |
| } catch { |
| results.push({ name: 'HuggingFace', ok: false }); |
| } |
| } |
|
|
| |
| if (this.settings.tokens.coingeckoKey) { |
| try { |
| const response = await fetch(`https://api.coingecko.com/api/v3/ping?x_cg_demo_api_key=${this.settings.tokens.coingeckoKey}`); |
| results.push({ name: 'CoinGecko', ok: response.ok }); |
| } catch { |
| results.push({ name: 'CoinGecko', ok: false }); |
| } |
| } |
|
|
| |
| const passed = results.filter(r => r.ok).length; |
| const total = results.length; |
| |
| if (total === 0) { |
| Toast.warning('No tokens configured to test'); |
| } else if (passed === total) { |
| Toast.success(`All ${total} tokens verified successfully`); |
| } else { |
| Toast.warning(`${passed}/${total} tokens verified`); |
| } |
| } |
|
|
| saveTelegram() { |
| this.settings.telegram = { |
| botToken: document.getElementById('telegram-bot-token')?.value || '', |
| chatId: document.getElementById('telegram-chat-id')?.value || '', |
| enabled: document.getElementById('telegram-enabled')?.checked || false, |
| silent: document.getElementById('telegram-silent')?.checked || false, |
| includeCharts: document.getElementById('telegram-charts')?.checked || false, |
| }; |
|
|
| if (this.saveSettings()) { |
| Toast.success('Telegram settings saved'); |
| this.sendTelegramToBackend(); |
| } else { |
| Toast.error('Failed to save Telegram settings'); |
| } |
| } |
|
|
| async sendTelegramToBackend() { |
| try { |
| await api.post('/settings/telegram', this.settings.telegram); |
| } catch (error) { |
| console.warn('[Settings] Could not sync Telegram settings with backend:', error); |
| } |
| } |
|
|
| async testTelegram() { |
| const botToken = document.getElementById('telegram-bot-token')?.value; |
| const chatId = document.getElementById('telegram-chat-id')?.value; |
|
|
| if (!botToken || !chatId) { |
| Toast.warning('Please enter both bot token and chat ID'); |
| return; |
| } |
|
|
| Toast.info('Sending test message...'); |
|
|
| try { |
| const message = `🚀 *Crypto Monitor ULTIMATE*\n\nTest message sent successfully!\n\n_Time: ${new Date().toLocaleString()}_`; |
| |
| const response = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| chat_id: chatId, |
| text: message, |
| parse_mode: 'Markdown', |
| disable_notification: document.getElementById('telegram-silent')?.checked || false, |
| }), |
| }); |
|
|
| const data = await response.json(); |
| |
| if (data.ok) { |
| Toast.success('Test message sent successfully! Check your Telegram.'); |
| } else { |
| Toast.error(`Telegram error: ${data.description}`); |
| } |
| } catch (error) { |
| Toast.error(`Failed to send test message: ${error.message}`); |
| } |
| } |
|
|
| saveSignals() { |
| this.settings.signals = { |
| bullish: document.getElementById('signal-bullish')?.checked || false, |
| bearish: document.getElementById('signal-bearish')?.checked || false, |
| whale: document.getElementById('signal-whale')?.checked || false, |
| news: document.getElementById('signal-news')?.checked || false, |
| sentiment: document.getElementById('signal-sentiment')?.checked || false, |
| price: document.getElementById('signal-price')?.checked || false, |
| confidenceThreshold: parseInt(document.getElementById('signal-confidence')?.value) || 70, |
| priceChangeThreshold: parseInt(document.getElementById('price-change-threshold')?.value) || 5, |
| whaleThreshold: parseInt(document.getElementById('whale-threshold')?.value) || 100000, |
| watchedCoins: document.getElementById('watched-coins')?.value || 'BTC, ETH, SOL', |
| }; |
|
|
| if (this.saveSettings()) { |
| Toast.success('Signal settings saved'); |
| this.sendSignalsToBackend(); |
| } else { |
| Toast.error('Failed to save signal settings'); |
| } |
| } |
|
|
| async sendSignalsToBackend() { |
| try { |
| await api.post('/settings/signals', this.settings.signals); |
| } catch (error) { |
| console.warn('[Settings] Could not sync signal settings with backend:', error); |
| } |
| } |
|
|
| saveScheduling() { |
| this.settings.scheduling = { |
| autoRefreshEnabled: document.getElementById('auto-refresh-enabled')?.checked || false, |
| intervalMarket: parseInt(document.getElementById('interval-market')?.value) || 30, |
| intervalNews: parseInt(document.getElementById('interval-news')?.value) || 120, |
| intervalSentiment: parseInt(document.getElementById('interval-sentiment')?.value) || 300, |
| intervalWhale: parseInt(document.getElementById('interval-whale')?.value) || 60, |
| intervalBlockchain: parseInt(document.getElementById('interval-blockchain')?.value) || 300, |
| intervalModels: parseInt(document.getElementById('interval-models')?.value) || 600, |
| quietHoursEnabled: document.getElementById('quiet-hours-enabled')?.checked || false, |
| quietStart: document.getElementById('quiet-start')?.value || '22:00', |
| quietEnd: document.getElementById('quiet-end')?.value || '08:00', |
| }; |
|
|
| if (this.saveSettings()) { |
| Toast.success('Schedule settings saved'); |
| this.applyScheduling(); |
| } else { |
| Toast.error('Failed to save schedule settings'); |
| } |
| } |
|
|
| applyScheduling() { |
| |
| window.dispatchEvent(new CustomEvent('settingsChanged', { |
| detail: { scheduling: this.settings.scheduling } |
| })); |
| } |
|
|
| saveNotifications() { |
| this.settings.notifications = { |
| browser: document.getElementById('notif-browser')?.checked || false, |
| sound: document.getElementById('notif-sound')?.checked || false, |
| toast: document.getElementById('notif-toast')?.checked || false, |
| soundType: document.getElementById('notif-sound-type')?.value || 'default', |
| volume: parseInt(document.getElementById('notif-volume')?.value) || 50, |
| }; |
|
|
| if (this.saveSettings()) { |
| Toast.success('Notification settings saved'); |
| } else { |
| Toast.error('Failed to save notification settings'); |
| } |
| } |
|
|
| testNotification() { |
| |
| if (this.settings.notifications.browser && 'Notification' in window) { |
| if (Notification.permission === 'granted') { |
| new Notification('Crypto Monitor ULTIMATE', { |
| body: 'Test notification! Your settings are working.', |
| icon: '/static/assets/icons/favicon.svg' |
| }); |
| } else if (Notification.permission !== 'denied') { |
| Notification.requestPermission().then(permission => { |
| if (permission === 'granted') { |
| new Notification('Crypto Monitor ULTIMATE', { |
| body: 'Notifications enabled successfully!', |
| icon: '/static/assets/icons/favicon.svg' |
| }); |
| } |
| }); |
| } |
| } |
|
|
| |
| if (this.settings.notifications.toast) { |
| Toast.info('Test notification! Your settings are working.'); |
| } |
|
|
| |
| if (this.settings.notifications.sound) { |
| console.log('[Settings] Would play sound:', this.settings.notifications.soundType); |
| } |
| } |
|
|
| saveAppearance() { |
| this.settings.appearance = { |
| theme: document.querySelector('input[name="theme"]:checked')?.value || 'dark', |
| compactMode: document.getElementById('compact-mode')?.checked || false, |
| showAnimations: document.getElementById('show-animations')?.checked || true, |
| showBgEffects: document.getElementById('show-bg-effects')?.checked || true, |
| }; |
|
|
| if (this.saveSettings()) { |
| Toast.success('Appearance settings saved'); |
| this.applySettings(); |
| } else { |
| Toast.error('Failed to save appearance settings'); |
| } |
| } |
|
|
| applySettings() { |
| this.applyTheme(); |
| this.applyAnimations(this.settings.appearance.showAnimations); |
| this.applyBgEffects(this.settings.appearance.showBgEffects); |
| this.applyCompactMode(this.settings.appearance.compactMode); |
| } |
|
|
| applyTheme() { |
| const theme = this.settings.appearance.theme; |
| if (theme === 'system') { |
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; |
| document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light'); |
| } else { |
| document.documentElement.setAttribute('data-theme', theme); |
| } |
| } |
|
|
| applyAnimations(enabled) { |
| document.body.classList.toggle('no-animations', !enabled); |
| } |
|
|
| applyBgEffects(enabled) { |
| const bgEffects = document.querySelector('.background-effects'); |
| if (bgEffects) { |
| bgEffects.style.display = enabled ? 'block' : 'none'; |
| } |
| } |
|
|
| applyCompactMode(enabled) { |
| document.body.classList.toggle('compact-mode', enabled); |
| } |
|
|
| saveAllSettings() { |
| this.saveTokens(); |
| this.saveTelegram(); |
| this.saveSignals(); |
| this.saveScheduling(); |
| this.saveNotifications(); |
| this.saveAppearance(); |
| Toast.success('All settings saved successfully!'); |
| } |
|
|
| resetSettings() { |
| if (confirm('Are you sure you want to reset all settings to defaults? This cannot be undone.')) { |
| this.settings = { ...DEFAULT_SETTINGS }; |
| this.saveSettings(); |
| this.populateForm(); |
| this.applySettings(); |
| Toast.info('Settings reset to defaults'); |
| } |
| } |
| } |
|
|
| |
| const page = new SettingsPage(); |
| window.settingsPage = page; |
|
|
| |
| export function getSettings() { |
| return page.settings; |
| } |
|
|
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', () => page.init()); |
| } else { |
| page.init(); |
| } |
|
|
|
|