(function () { const form = document.getElementById('mining-settings-form'); const statsGrid = document.getElementById('stats-grid'); const historyList = document.getElementById('history-list'); const snapshotButton = document.getElementById('snapshot-button'); const statTemplate = document.getElementById('stat-card-template'); const presetButtons = Array.from(document.querySelectorAll('[data-preset]')); if (!form || !statsGrid || !historyList || !snapshotButton || !statTemplate) { return; } const presets = { doge: { project_name: 'DOGE Mining', coin_symbol: 'DOGE', algorithm: 'Scrypt', currency: 'EUR', hashrate: 950, hashrate_unit: 'MH/s', network_hashrate: 920, network_hashrate_unit: 'TH/s', block_reward: 10000, block_time_seconds: 60, coin_price: 0.14, power_watts: 3420, electricity_price: 0.32, pool_fee_percent: 1.5, hardware_cost: 8400, }, ltc: { project_name: 'LTC Mining', coin_symbol: 'LTC', algorithm: 'Scrypt', currency: 'EUR', hashrate: 950, hashrate_unit: 'MH/s', network_hashrate: 1.35, network_hashrate_unit: 'PH/s', block_reward: 6.25, block_time_seconds: 150, coin_price: 82, power_watts: 3420, electricity_price: 0.32, pool_fee_percent: 1.5, hardware_cost: 8400, }, }; const fmtNumber = (value, digits = 4) => { if (value === null || value === undefined || value === '') { return 'n/a'; } return Number(value).toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: digits, }); }; const fmtMoney = (value, currency) => { if (value === null || value === undefined || currency === '') { return 'n/a'; } return new Intl.NumberFormat('de-DE', { style: 'currency', currency, maximumFractionDigits: 4, }).format(Number(value)); }; const fmtDateTime = (value) => { if (!value) { return 'n/a'; } const date = new Date(value); if (Number.isNaN(date.getTime())) { return value; } return new Intl.DateTimeFormat('de-DE', { dateStyle: 'medium', timeStyle: 'short', }).format(date); }; const toHashrateHps = (value, unit) => { const numericValue = Number(value || 0); if (!Number.isFinite(numericValue) || numericValue <= 0) { return 0; } const multipliers = { 'H/S': 1, 'KH/S': 1e3, 'MH/S': 1e6, 'GH/S': 1e9, 'TH/S': 1e12, 'PH/S': 1e15, 'EH/S': 1e18, }; return numericValue * (multipliers[String(unit || 'MH/s').toUpperCase()] || 1e6); }; const calculateMetrics = (settings) => { const hashrate = toHashrateHps(settings.hashrate, settings.hashrate_unit); const networkHashrate = toHashrateHps(settings.network_hashrate, settings.network_hashrate_unit); const powerWatts = Math.max(0, Number(settings.power_watts || 0)); const electricityPrice = Math.max(0, Number(settings.electricity_price || 0)); const poolFeePercent = Math.max(0, Math.min(100, Number(settings.pool_fee_percent || 0))); const coinPrice = Math.max(0, Number(settings.coin_price || 0)); const blockReward = Math.max(0, Number(settings.block_reward || 0)); const blockTimeSeconds = Math.max(1, Number(settings.block_time_seconds || 60)); const hardwareCost = Math.max(0, Number(settings.hardware_cost || 0)); const share = networkHashrate > 0 ? hashrate / networkHashrate : null; const blocksPerDay = 86400 / blockTimeSeconds; const dailyCoinsGross = share !== null ? share * blocksPerDay * blockReward : null; const dailyCoinsNet = dailyCoinsGross !== null ? dailyCoinsGross * (1 - poolFeePercent / 100) : null; const dailyRevenue = dailyCoinsNet !== null ? dailyCoinsNet * coinPrice : null; const dailyPowerCost = (powerWatts / 1000) * 24 * electricityPrice; const dailyProfit = dailyRevenue !== null ? dailyRevenue - dailyPowerCost : null; const monthlyProfit = dailyProfit !== null ? dailyProfit * 30 : null; const annualProfit = dailyProfit !== null ? dailyProfit * 365 : null; const breakEvenDays = dailyProfit !== null && dailyProfit > 0 && hardwareCost > 0 ? hardwareCost / dailyProfit : null; return { daily_coins_net: dailyCoinsNet, daily_revenue: dailyRevenue, daily_power_cost: dailyPowerCost, daily_profit: dailyProfit, monthly_profit: monthlyProfit, annual_profit: annualProfit, break_even_days: breakEvenDays, share_ratio: share, }; }; const showToast = (message) => { const node = document.createElement('div'); node.className = 'toast'; node.textContent = message; document.body.appendChild(node); window.setTimeout(() => { node.remove(); }, 2400); }; const readForm = () => Object.fromEntries(new FormData(form).entries()); const writeForm = (settings) => { Object.entries(settings).forEach(([key, value]) => { const field = form.elements.namedItem(key); if (!(field instanceof HTMLInputElement || field instanceof HTMLSelectElement || field instanceof HTMLTextAreaElement)) { return; } field.value = String(value ?? ''); }); }; const renderStats = (settings, metrics) => { const currency = String(settings.currency || 'EUR').toUpperCase(); const cards = [ ['Taegliche Coins netto', fmtNumber(metrics.daily_coins_net, 6), `${settings.coin_symbol || 'COIN'} pro Tag`], ['Taeglicher Umsatz', fmtMoney(metrics.daily_revenue, currency), 'vor Stromkosten'], ['Taeglicher Strom', fmtMoney(metrics.daily_power_cost, currency), `${fmtNumber(settings.power_watts, 0)} W @ ${fmtMoney(settings.electricity_price, currency)}/kWh`], ['Taeglicher Profit', fmtMoney(metrics.daily_profit, currency), 'nach Pool Fee und Strom'], ['Monatlicher Profit', fmtMoney(metrics.monthly_profit, currency), '30 Tage Hochrechnung'], ['Jaehrlicher Profit', fmtMoney(metrics.annual_profit, currency), '365 Tage Hochrechnung'], ['ROI / Break-even', metrics.break_even_days ? `${fmtNumber(metrics.break_even_days, 1)} Tage` : 'n/a', 'bezogen auf Hardwarekosten'], ['Rig Anteil am Netzwerk', metrics.share_ratio ? `${fmtNumber(metrics.share_ratio * 100, 8)} %` : 'n/a', 'Hashrate Share'], ]; statsGrid.innerHTML = ''; cards.forEach(([label, value, meta]) => { const fragment = statTemplate.content.cloneNode(true); fragment.querySelector('.stat-label').textContent = label; fragment.querySelector('.stat-value').textContent = value; fragment.querySelector('.stat-meta').textContent = meta; statsGrid.appendChild(fragment); }); }; const renderHistory = (entries) => { historyList.innerHTML = ''; if (!Array.isArray(entries) || entries.length === 0) { const empty = document.createElement('div'); empty.className = 'history-empty'; empty.textContent = 'Noch keine Snapshots gespeichert.'; historyList.appendChild(empty); return; } entries.forEach((entry) => { const item = document.createElement('article'); item.className = 'history-item'; item.innerHTML = `
${entry.project_name || 'Snapshot'} ${fmtDateTime(entry.created_at)}
${entry.coin_symbol || 'COIN'} Profit/Tag: ${fmtMoney(entry.metrics?.daily_profit, entry.currency || 'EUR')} ROI: ${entry.metrics?.break_even_days ? `${fmtNumber(entry.metrics.break_even_days, 1)} Tage` : 'n/a'}
${entry.note ? `

${entry.note}

` : ''} `; historyList.appendChild(item); }); }; const load = async () => { const response = await fetch('/api/mining-checker/'); const data = await response.json(); writeForm(data.settings || {}); renderStats(data.settings || {}, data.metrics || {}); renderHistory(data.history || []); }; const saveSettings = async () => { const response = await fetch('/api/mining-checker/', { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(readForm()), }); const data = await response.json(); writeForm(data.settings || {}); renderStats(data.settings || {}, data.metrics || {}); renderHistory(data.history || []); showToast('Mining-Checker gespeichert'); }; const saveSnapshot = async () => { const note = window.prompt('Optionaler Snapshot-Hinweis', ''); if (note === null) { return; } const response = await fetch('/api/mining-checker/', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ settings: readForm(), note, }), }); const data = await response.json(); writeForm(data.settings || {}); renderStats(data.settings || {}, data.metrics || {}); renderHistory(data.history || []); showToast('Snapshot gespeichert'); }; form.addEventListener('submit', async (event) => { event.preventDefault(); await saveSettings(); }); snapshotButton.addEventListener('click', async () => { await saveSnapshot(); }); presetButtons.forEach((button) => { button.addEventListener('click', () => { const preset = button.dataset.preset; if (!preset || preset === 'custom') { showToast('Aktuelle Werte bleiben unveraendert'); return; } writeForm(presets[preset]); renderStats(readForm(), calculateMetrics(readForm())); showToast(`Preset ${preset.toUpperCase()} geladen`); }); }); form.addEventListener('input', () => { const settings = readForm(); renderStats(settings, calculateMetrics(settings)); }); load().catch((error) => { console.error(error); showToast('Mining-Checker konnte nicht geladen werden'); }); }());