Add currency support and config management enhancements

This commit introduces significant updates to the application, focusing on currency support and improved configuration management. Key changes include:

- Added a `config_reset` flag in `update_metrics_job` in `App.py` to manage configuration resets.
- Modified `update_config` to handle currency changes and update notifications accordingly.
- Updated `MiningDashboardService` to fetch and include exchange rates and configured currency in metrics.
- Introduced utility functions for currency formatting and symbol retrieval in `notification_service.py`.
- Enhanced UI components in `main.js` and `boot.html` to support currency selection and display.
- Adjusted Docker Compose file to remove hardcoded wallet and power settings for better flexibility.

These changes enhance usability by allowing users to view financial data in their preferred currency and manage configurations more effectively.
This commit is contained in:
DJObleezy 2025-04-27 14:43:45 -07:00
parent 2f1cbd3143
commit a50bd552f7
7 changed files with 349 additions and 56 deletions

37
App.py
View File

@ -367,12 +367,25 @@ def update_metrics_job(force=False):
metrics = dashboard_service.fetch_metrics() metrics = dashboard_service.fetch_metrics()
if metrics: if metrics:
logging.info("Fetched metrics successfully") logging.info("Fetched metrics successfully")
# Add config_reset flag to metrics from the config
config = load_config()
metrics["config_reset"] = config.get("config_reset", False)
logging.info(f"Added config_reset flag to metrics: {metrics.get('config_reset')}")
# First check for notifications by comparing new metrics with old cached metrics # First check for notifications by comparing new metrics with old cached metrics
notification_service.check_and_generate_notifications(metrics, cached_metrics) notification_service.check_and_generate_notifications(metrics, cached_metrics)
# Then update cached metrics after comparison # Then update cached metrics after comparison
cached_metrics = metrics cached_metrics = metrics
# Clear the config_reset flag after it's been used
if metrics.get("config_reset"):
config = load_config()
if "config_reset" in config:
del config["config_reset"]
save_config(config)
logging.info("Cleared config_reset flag from configuration after use")
# Update state history (only once) # Update state history (only once)
state_manager.update_metrics_history(metrics) state_manager.update_metrics_history(metrics)
@ -771,7 +784,7 @@ def get_config():
@app.route("/api/config", methods=["POST"]) @app.route("/api/config", methods=["POST"])
def update_config(): def update_config():
"""API endpoint to update configuration.""" """API endpoint to update configuration."""
global dashboard_service, worker_service # Add this to access the global dashboard_service global dashboard_service, worker_service
try: try:
# Get the request data # Get the request data
@ -783,11 +796,19 @@ def update_config():
logging.error("Invalid configuration format") logging.error("Invalid configuration format")
return jsonify({"error": "Invalid configuration format"}), 400 return jsonify({"error": "Invalid configuration format"}), 400
# Get current config to check if currency is changing
current_config = load_config()
currency_changed = (
"currency" in new_config and
new_config.get("currency") != current_config.get("currency", "USD")
)
# Required fields and default values # Required fields and default values
defaults = { defaults = {
"wallet": "yourwallethere", "wallet": "yourwallethere",
"power_cost": 0.0, "power_cost": 0.0,
"power_usage": 0.0 "power_usage": 0.0,
"currency": "USD" # Add default currency
} }
# Merge new config with defaults for any missing fields # Merge new config with defaults for any missing fields
@ -802,7 +823,8 @@ def update_config():
dashboard_service = MiningDashboardService( dashboard_service = MiningDashboardService(
new_config.get("power_cost", 0.0), new_config.get("power_cost", 0.0),
new_config.get("power_usage", 0.0), new_config.get("power_usage", 0.0),
new_config.get("wallet") new_config.get("wallet"),
network_fee=new_config.get("network_fee", 0.0) # Include network fee
) )
logging.info(f"Dashboard service reinitialized with new wallet: {new_config.get('wallet')}") logging.info(f"Dashboard service reinitialized with new wallet: {new_config.get('wallet')}")
@ -810,6 +832,15 @@ def update_config():
worker_service.set_dashboard_service(dashboard_service) worker_service.set_dashboard_service(dashboard_service)
logging.info(f"Worker service updated with the new dashboard service") logging.info(f"Worker service updated with the new dashboard service")
# If currency changed, update notifications to use the new currency
if currency_changed:
try:
logging.info(f"Currency changed from {current_config.get('currency', 'USD')} to {new_config['currency']}")
updated_count = notification_service.update_notification_currency()
logging.info(f"Updated {updated_count} notifications to use {new_config['currency']} currency")
except Exception as e:
logging.error(f"Error updating notification currency: {e}")
# Force a metrics update to reflect the new configuration # Force a metrics update to reflect the new configuration
update_metrics_job(force=True) update_metrics_job(force=True)
logging.info("Forced metrics update after configuration change") logging.info("Forced metrics update after configuration change")

View File

@ -164,6 +164,18 @@ class MiningDashboardService:
metrics["server_timestamp"] = datetime.now(ZoneInfo(get_timezone())).isoformat() metrics["server_timestamp"] = datetime.now(ZoneInfo(get_timezone())).isoformat()
metrics["server_start_time"] = datetime.now(ZoneInfo(get_timezone())).isoformat() metrics["server_start_time"] = datetime.now(ZoneInfo(get_timezone())).isoformat()
# Get the configured currency
from config import load_config
config = load_config()
selected_currency = config.get("currency", "USD")
# Fetch exchange rates
exchange_rates = self.fetch_exchange_rates()
# Add to metrics
metrics["currency"] = selected_currency
metrics["exchange_rates"] = exchange_rates
# Log execution time # Log execution time
execution_time = time.time() - start_time execution_time = time.time() - start_time
metrics["execution_time"] = execution_time metrics["execution_time"] = execution_time
@ -452,6 +464,32 @@ class MiningDashboardService:
logging.error(f"Error fetching {url}: {e}") logging.error(f"Error fetching {url}: {e}")
return None return None
# Add the fetch_exchange_rates method after the fetch_url method
def fetch_exchange_rates(self, base_currency="USD"):
"""
Fetch currency exchange rates from a public API.
Args:
base_currency (str): Base currency for rates (default: USD)
Returns:
dict: Exchange rates for supported currencies
"""
try:
# Use exchangerate-api for currency rates
url = f"https://api.exchangerate-api.com/v4/latest/{base_currency}"
response = self.session.get(url, timeout=5)
if response.ok:
data = response.json()
return data.get('rates', {})
else:
logging.error(f"Failed to fetch exchange rates: {response.status_code}")
return {}
except Exception as e:
logging.error(f"Error fetching exchange rates: {e}")
return {}
def get_bitcoin_stats(self): def get_bitcoin_stats(self):
""" """
Fetch Bitcoin network statistics with improved error handling and caching. Fetch Bitcoin network statistics with improved error handling and caching.

View File

@ -16,11 +16,6 @@ services:
- "5000:5000" - "5000:5000"
environment: environment:
- REDIS_URL=redis://redis:6379 - REDIS_URL=redis://redis:6379
- WALLET=35eS5Lsqw8NCjFJ8zhp9JaEmyvLDwg6XtS
- POWER_COST=0
- POWER_USAGE=0
- NETWORK_FEE=0
- TIMEZONE=America/Los_Angeles
- LOG_LEVEL=INFO - LOG_LEVEL=INFO
volumes: volumes:
- ./logs:/app/logs - ./logs:/app/logs

View File

@ -1,14 +1,17 @@
# notification_service.py # notification_service.py
import logging import logging
import json import json
import time import time
import uuid import uuid
import pytz import pytz
import re
from datetime import datetime, timedelta from datetime import datetime, timedelta
from enum import Enum from enum import Enum
from collections import deque from collections import deque
from typing import List, Dict, Any, Optional, Union from typing import List, Dict, Any, Optional, Union
from config import get_timezone from config import get_timezone, load_config
from data_service import MiningDashboardService
# Constants to replace magic values # Constants to replace magic values
ONE_DAY_SECONDS = 86400 ONE_DAY_SECONDS = 86400
@ -29,6 +32,52 @@ class NotificationCategory(Enum):
EARNINGS = "earnings" EARNINGS = "earnings"
SYSTEM = "system" SYSTEM = "system"
# Currency utility functions
def get_currency_symbol(currency):
"""Return symbol for the specified currency"""
symbols = {
'USD': '$',
'EUR': '',
'GBP': '£',
'JPY': '¥',
'CAD': 'CA$',
'AUD': 'A$',
'CNY': '¥',
'KRW': '',
'BRL': 'R$',
'CHF': 'Fr'
}
return symbols.get(currency, '$')
def format_currency_value(value, currency, exchange_rates):
"""Format a USD value in the selected currency"""
if value is None or value == "N/A":
return "N/A"
# Get exchange rate (default to 1.0 if not found)
exchange_rate = exchange_rates.get(currency, 1.0)
converted_value = value * exchange_rate
# Get currency symbol
symbol = get_currency_symbol(currency)
# Format with or without decimals based on currency
if currency in ['JPY', 'KRW']:
return f"{symbol}{int(converted_value):,}"
else:
return f"{symbol}{converted_value:.2f}"
def get_exchange_rates():
"""Get exchange rates with caching"""
try:
# Create a dashboard service instance for fetching exchange rates
dashboard_service = MiningDashboardService(0, 0, "", 0)
exchange_rates = dashboard_service.fetch_exchange_rates()
return exchange_rates
except Exception as e:
logging.error(f"Error fetching exchange rates for notifications: {e}")
return {} # Return empty dict if failed
class NotificationService: class NotificationService:
"""Service for managing mining dashboard notifications.""" """Service for managing mining dashboard notifications."""
@ -312,6 +361,11 @@ class NotificationService:
if not current_metrics: if not current_metrics:
logging.warning("[NotificationService] No current metrics available, skipping notification checks") logging.warning("[NotificationService] No current metrics available, skipping notification checks")
return new_notifications return new_notifications
# Skip notification generation after configuration reset
if current_metrics.get("wallet") == "yourwallethere" or current_metrics.get("config_reset", False):
logging.info("[NotificationService] Configuration reset detected, skipping all notifications")
return new_notifications
# Check for block updates (using persistent storage) # Check for block updates (using persistent storage)
last_block_height = current_metrics.get("last_block_height") last_block_height = current_metrics.get("last_block_height")
@ -384,12 +438,22 @@ class NotificationService:
hashrate_24hr = metrics.get("hashrate_24hr", 0) hashrate_24hr = metrics.get("hashrate_24hr", 0)
hashrate_unit = metrics.get("hashrate_24hr_unit", "TH/s") hashrate_unit = metrics.get("hashrate_24hr_unit", "TH/s")
# Format daily earnings # Format daily earnings with user's currency
daily_mined_sats = metrics.get("daily_mined_sats", 0) daily_mined_sats = metrics.get("daily_mined_sats", 0)
daily_profit_usd = metrics.get("daily_profit_usd", 0) daily_profit_usd = metrics.get("daily_profit_usd", 0)
# Get user's currency preference
config = load_config()
user_currency = config.get("currency", "USD")
# Get exchange rates
exchange_rates = get_exchange_rates()
# Format with the user's currency
formatted_profit = format_currency_value(daily_profit_usd, user_currency, exchange_rates)
# Build message # Build message
message = f"Daily Mining Summary: {hashrate_24hr} {hashrate_unit} average hashrate, {daily_mined_sats} SATS mined (${daily_profit_usd:.2f})" message = f"Daily Mining Summary: {hashrate_24hr} {hashrate_unit} average hashrate, {daily_mined_sats} SATS mined ({formatted_profit})"
# Add notification # Add notification
logging.info(f"[NotificationService] Generating daily stats notification: {message}") logging.info(f"[NotificationService] Generating daily stats notification: {message}")
@ -401,7 +465,8 @@ class NotificationService:
"hashrate": hashrate_24hr, "hashrate": hashrate_24hr,
"unit": hashrate_unit, "unit": hashrate_unit,
"daily_sats": daily_mined_sats, "daily_sats": daily_mined_sats,
"daily_profit": daily_profit_usd "daily_profit": daily_profit_usd,
"currency": user_currency
} }
) )
except Exception as e: except Exception as e:
@ -551,8 +616,20 @@ class NotificationService:
def _check_earnings_progress(self, current: Dict[str, Any], previous: Dict[str, Any]) -> Optional[Dict[str, Any]]: def _check_earnings_progress(self, current: Dict[str, Any], previous: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Check for significant earnings progress or payout approach.""" """Check for significant earnings progress or payout approach."""
try: try:
# First check for configuration reset via Alt+W (this is a more robust check)
# This specifically looks for the default "yourwallethere" wallet which indicates Alt+W was used
current_wallet = str(current.get("wallet", ""))
if current_wallet == "yourwallethere":
logging.info("[NotificationService] Detected wallet reset to default (likely Alt+W) - skipping payout notification")
return None
# Check if ANY config value changed that might affect balance
if self._is_configuration_change_affecting_balance(current, previous):
logging.info("[NotificationService] Configuration change detected - skipping payout notification")
return None
current_unpaid = self._parse_numeric_value(current.get("unpaid_earnings", "0")) current_unpaid = self._parse_numeric_value(current.get("unpaid_earnings", "0"))
# Check if approaching payout # Check if approaching payout
if current.get("est_time_to_payout"): if current.get("est_time_to_payout"):
est_time = current.get("est_time_to_payout") est_time = current.get("est_time_to_payout")
@ -605,9 +682,27 @@ class NotificationService:
logging.error(f"[NotificationService] Error checking earnings progress: {e}") logging.error(f"[NotificationService] Error checking earnings progress: {e}")
return None return None
def _is_configuration_change_affecting_balance(self, current: Dict[str, Any], previous: Dict[str, Any]) -> bool:
"""Check if any configuration changed that would affect balance calculations."""
# Check wallet
if "wallet" in current and "wallet" in previous:
if current.get("wallet") != previous.get("wallet"):
return True
# Check currency
if "currency" in current and "currency" in previous:
if current.get("currency") != previous.get("currency"):
return True
# Check for emergency reset flag
if current.get("config_reset", False):
return True
return False
def _should_send_payout_notification(self) -> bool: def _should_send_payout_notification(self) -> bool:
"""Check if enough time has passed since the last payout notification.""" """Check if enough time has passed since the last payout notification."""
if self.last_payout_notification_time is None: if self.last_payout_notification_time is None:
return True return True
time_since_last_notification = self._get_current_time() - self.last_payout_notification_time time_since_last_notification = self._get_current_time() - self.last_payout_notification_time
return time_since_last_notification.total_seconds() > ONE_DAY_SECONDS return time_since_last_notification.total_seconds() > ONE_DAY_SECONDS

View File

@ -397,8 +397,12 @@ const BitcoinMinuteRefresh = (function () {
<div class="terminal-header"> <div class="terminal-header">
<div class="terminal-title">SYSTEM MONITOR v.3</div> <div class="terminal-title">SYSTEM MONITOR v.3</div>
<div class="terminal-controls"> <div class="terminal-controls">
<div class="terminal-dot minimize" title="Minimize" onclick="BitcoinMinuteRefresh.toggleTerminal()"></div> <div class="terminal-dot minimize" title="Minimize" onclick="BitcoinMinuteRefresh.toggleTerminal()">
<div class="terminal-dot close" title="Close" onclick="BitcoinMinuteRefresh.hideTerminal()"></div> <span class="control-symbol">-</span>
</div>
<div class="terminal-dot close" title="Close" onclick="BitcoinMinuteRefresh.hideTerminal()">
<span class="control-symbol">x</span>
</div>
</div> </div>
</div> </div>
<div class="terminal-content"> <div class="terminal-content">
@ -517,22 +521,45 @@ const BitcoinMinuteRefresh = (function () {
} }
.terminal-dot { .terminal-dot {
width: 8px; width: 12px;
height: 8px; height: 12px;
border-radius: 50%; border-radius: 50%;
background-color: #555; background-color: #555;
cursor: pointer; cursor: pointer;
transition: background-color 0.3s; transition: background-color 0.3s;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.control-symbol {
color: #333;
font-size: 9px;
font-weight: bold;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
line-height: 1;
} }
.terminal-dot.minimize:hover { .terminal-dot.minimize:hover {
background-color: #ffcc00; background-color: #ffcc00;
} }
.terminal-dot.minimize:hover .control-symbol {
color: #664e00;
}
.terminal-dot.close:hover { .terminal-dot.close:hover {
background-color: #ff3b30; background-color: #ff3b30;
} }
.terminal-dot.close:hover .control-symbol {
color: #7a0200;
}
/* Terminal Content */ /* Terminal Content */
.terminal-content { .terminal-content {
position: relative; position: relative;

View File

@ -1634,7 +1634,7 @@ function calculatePoolFeeInSats(poolFeePercentage, lastBlockEarnings) {
return -Math.round(feeAmount); return -Math.round(feeAmount);
} }
// Main UI update function with hashrate normalization // Main UI update function with currency support
function updateUI() { function updateUI() {
function ensureElementStyles() { function ensureElementStyles() {
// Create a style element if it doesn't exist // Create a style element if it doesn't exist
@ -1735,15 +1735,31 @@ function updateUI() {
try { try {
const data = latestMetrics; const data = latestMetrics;
// Get currency and exchange rate information
const currency = data.currency || 'USD';
const exchangeRate = data.exchange_rates && data.exchange_rates[currency] ?
data.exchange_rates[currency] : 1.0;
// Update currency-related labels
const earningsHeader = document.querySelector('.card-header');
if (earningsHeader && earningsHeader.textContent.includes("EARNINGS")) {
// Find the card header that says "USD EARNINGS" and update it
const headers = document.querySelectorAll('.card-header');
headers.forEach(header => {
if (header.textContent.includes("EARNINGS") &&
header.textContent.match(/[A-Z]{3} EARNINGS/)) {
header.textContent = `${currency} EARNINGS`;
}
});
}
// If this is the initial load, force a reset of all arrows // If this is the initial load, force a reset of all arrows
if (initialLoad) { if (initialLoad) {
arrowIndicator.forceApplyArrows(); arrowIndicator.forceApplyArrows();
initialLoad = false; initialLoad = false;
} }
// Cache jQuery selectors for performance and use safe update methods
// Format each hashrate with proper normalization // Format each hashrate with proper normalization
// Pool Hashrate // Pool Hashrate
let formattedPoolHashrate = "N/A"; let formattedPoolHashrate = "N/A";
if (data.pool_total_hashrate != null) { if (data.pool_total_hashrate != null) {
@ -2079,9 +2095,15 @@ function updateUI() {
// Update other non-hashrate metrics // Update other non-hashrate metrics
updateElementText("block_number", numberWithCommas(data.block_number)); updateElementText("block_number", numberWithCommas(data.block_number));
updateElementText("btc_price", // Update BTC price with currency conversion and symbol
data.btc_price != null ? "$" + numberWithCommas(parseFloat(data.btc_price).toFixed(2)) : "N/A" if (data.btc_price != null) {
); const btcPriceValue = data.btc_price * exchangeRate;
const symbol = getCurrencySymbol(currency);
updateElementText("btc_price", formatCurrencyValue(btcPriceValue, currency));
} else {
updateElementText("btc_price", formatCurrencyValue(0, currency));
}
// Update last block earnings // Update last block earnings
if (data.last_block_earnings !== undefined) { if (data.last_block_earnings !== undefined) {
@ -2103,37 +2125,51 @@ function updateUI() {
} }
updateElementText("difficulty", numberWithCommas(Math.round(data.difficulty))); updateElementText("difficulty", numberWithCommas(Math.round(data.difficulty)));
// Daily revenue // Daily revenue with currency conversion
updateElementText("daily_revenue", "$" + numberWithCommas(data.daily_revenue.toFixed(2))); if (data.daily_revenue != null) {
const dailyRevenue = data.daily_revenue * exchangeRate;
updateElementText("daily_revenue", formatCurrencyValue(dailyRevenue, currency));
} else {
updateElementText("daily_revenue", formatCurrencyValue(0, currency));
}
// Daily power cost // Daily power cost with currency conversion
updateElementText("daily_power_cost", "$" + numberWithCommas(data.daily_power_cost.toFixed(2))); if (data.daily_power_cost != null) {
const dailyPowerCost = data.daily_power_cost * exchangeRate;
updateElementText("daily_power_cost", formatCurrencyValue(dailyPowerCost, currency));
} else {
updateElementText("daily_power_cost", formatCurrencyValue(0, currency));
}
// Daily profit USD - Add red color if negative // Daily profit with currency conversion and color
const dailyProfitUSD = data.daily_profit_usd; if (data.daily_profit_usd != null) {
const dailyProfitElement = document.getElementById("daily_profit_usd"); const dailyProfit = data.daily_profit_usd * exchangeRate;
if (dailyProfitElement) { const dailyProfitElement = document.getElementById("daily_profit_usd");
dailyProfitElement.textContent = "$" + numberWithCommas(dailyProfitUSD.toFixed(2)); if (dailyProfitElement) {
if (dailyProfitUSD < 0) { dailyProfitElement.textContent = formatCurrencyValue(dailyProfit, currency);
// Use setAttribute to properly set the style with !important if (dailyProfit < 0) {
dailyProfitElement.setAttribute("style", "color: #ff5555 !important; font-weight: bold !important;"); // Use setAttribute to properly set the style with !important
} else { dailyProfitElement.setAttribute("style", "color: #ff5555 !important; font-weight: bold !important;");
// Clear the style attribute completely instead of setting it to empty } else {
dailyProfitElement.removeAttribute("style"); // Clear the style attribute completely
dailyProfitElement.removeAttribute("style");
}
} }
} }
// Monthly profit USD - Add red color if negative // Monthly profit with currency conversion and color
const monthlyProfitUSD = data.monthly_profit_usd; if (data.monthly_profit_usd != null) {
const monthlyProfitElement = document.getElementById("monthly_profit_usd"); const monthlyProfit = data.monthly_profit_usd * exchangeRate;
if (monthlyProfitElement) { const monthlyProfitElement = document.getElementById("monthly_profit_usd");
monthlyProfitElement.textContent = "$" + numberWithCommas(monthlyProfitUSD.toFixed(2)); if (monthlyProfitElement) {
if (monthlyProfitUSD < 0) { monthlyProfitElement.textContent = formatCurrencyValue(monthlyProfit, currency);
// Use setAttribute to properly set the style with !important if (monthlyProfit < 0) {
monthlyProfitElement.setAttribute("style", "color: #ff5555 !important; font-weight: bold !important;"); // Use setAttribute to properly set the style with !important
} else { monthlyProfitElement.setAttribute("style", "color: #ff5555 !important; font-weight: bold !important;");
// Clear the style attribute completely } else {
monthlyProfitElement.removeAttribute("style"); // Clear the style attribute completely
monthlyProfitElement.removeAttribute("style");
}
} }
} }
@ -2254,7 +2290,6 @@ function updateUI() {
} }
} }
// Update unread notifications badge in navigation // Update unread notifications badge in navigation
function updateNotificationBadge() { function updateNotificationBadge() {
$.ajax({ $.ajax({
@ -2348,6 +2383,8 @@ function resetWalletAddress() {
.then(config => { .then(config => {
// Reset the wallet address to default // Reset the wallet address to default
config.wallet = "yourwallethere"; config.wallet = "yourwallethere";
// Add special flag to indicate config reset
config.config_reset = true;
// Save the updated configuration // Save the updated configuration
return fetch('/api/config', { return fetch('/api/config', {
@ -2510,6 +2547,44 @@ function checkAndShowHashrateNotice() {
} }
} }
// Currency conversion functions
function getCurrencySymbol(currency) {
const symbols = {
'USD': '$',
'EUR': '€',
'GBP': '£',
'JPY': '¥',
'CAD': 'CA$',
'AUD': 'A$',
'CNY': '¥',
'KRW': '₩',
'BRL': 'R$',
'CHF': 'Fr'
};
return symbols[currency] || '$';
}
function formatCurrencyValue(value, currency) {
if (value == null || isNaN(value)) return "N/A";
const symbol = getCurrencySymbol(currency);
// For JPY and KRW, show without decimal places
if (currency === 'JPY' || currency === 'KRW') {
return `${symbol}${numberWithCommas(Math.round(value))}`;
}
return `${symbol}${numberWithCommas(value.toFixed(2))}`;
}
// Update the BTC price and earnings card header with the selected currency
function updateCurrencyLabels(currency) {
const earningsHeader = document.querySelector('.card-header:contains("USD EARNINGS")');
if (earningsHeader) {
earningsHeader.textContent = `${currency} EARNINGS`;
}
}
$(document).ready(function () { $(document).ready(function () {
// Apply theme based on stored preference - moved to beginning for better initialization // Apply theme based on stored preference - moved to beginning for better initialization
try { try {

View File

@ -109,6 +109,27 @@ v.21
</label> </label>
<input type="text" id="wallet-address" placeholder="bc1..." value=""> <input type="text" id="wallet-address" placeholder="bc1..." value="">
</div> </div>
<div class="form-group">
<label for="currency">
Preferred Currency
<span class="tooltip">
?
<span class="tooltip-text">Currency for displaying BTC value and earnings</span>
</span>
</label>
<select id="currency" class="form-control">
<option value="USD">USD ($)</option>
<option value="EUR">EUR (€)</option>
<option value="GBP">GBP (£)</option>
<option value="JPY">JPY (¥)</option>
<option value="CAD">CAD ($)</option>
<option value="AUD">AUD ($)</option>
<option value="CNY">CNY (¥)</option>
<option value="KRW">KRW (₩)</option>
<option value="BRL">BRL (R$)</option>
<option value="CHF">CHF (Fr)</option>
</select>
</div>
<div class="form-group"> <div class="form-group">
<label for="power-cost"> <label for="power-cost">
Power Cost ($/kWh) Power Cost ($/kWh)
@ -281,20 +302,22 @@ v.21
loadTimezoneFromConfig(); loadTimezoneFromConfig();
}); });
// Update saveConfig to include network fee // Update saveConfig to include currency
function saveConfig() { function saveConfig() {
const wallet = document.getElementById('wallet-address').value.trim(); const wallet = document.getElementById('wallet-address').value.trim();
const powerCost = parseFloat(document.getElementById('power-cost').value) || 0; const powerCost = parseFloat(document.getElementById('power-cost').value) || 0;
const powerUsage = parseFloat(document.getElementById('power-usage').value) || 0; const powerUsage = parseFloat(document.getElementById('power-usage').value) || 0;
const timezone = document.getElementById('timezone').value; const timezone = document.getElementById('timezone').value;
const networkFee = parseFloat(document.getElementById('network-fee').value) || 0; const networkFee = parseFloat(document.getElementById('network-fee').value) || 0;
const currency = document.getElementById('currency').value;
const updatedConfig = { const updatedConfig = {
wallet: wallet || (currentConfig ? currentConfig.wallet : ""), wallet: wallet || (currentConfig ? currentConfig.wallet : ""),
power_cost: powerCost, power_cost: powerCost,
power_usage: powerUsage, power_usage: powerUsage,
timezone: timezone, timezone: timezone,
network_fee: networkFee network_fee: networkFee,
currency: currency
}; };
return fetch('/api/config', { return fetch('/api/config', {
@ -345,7 +368,7 @@ v.21
power_usage: 0.0 power_usage: 0.0
}; };
// Update loadConfig function to include network fee // Update loadConfig function to handle currency
function loadConfig() { function loadConfig() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch('/api/config?nocache=' + new Date().getTime()) fetch('/api/config?nocache=' + new Date().getTime())
@ -364,6 +387,12 @@ v.21
document.getElementById('power-cost').value = currentConfig.power_cost || ""; document.getElementById('power-cost').value = currentConfig.power_cost || "";
document.getElementById('power-usage').value = currentConfig.power_usage || ""; document.getElementById('power-usage').value = currentConfig.power_usage || "";
document.getElementById('network-fee').value = currentConfig.network_fee || ""; document.getElementById('network-fee').value = currentConfig.network_fee || "";
// Set currency dropdown value if available
if (currentConfig.currency) {
document.getElementById('currency').value = currentConfig.currency;
}
configLoaded = true; configLoaded = true;
resolve(currentConfig); resolve(currentConfig);
}) })
@ -374,13 +403,16 @@ v.21
wallet: "yourwallethere", wallet: "yourwallethere",
power_cost: 0.0, power_cost: 0.0,
power_usage: 0.0, power_usage: 0.0,
network_fee: 0.0 network_fee: 0.0,
currency: "USD"
}; };
document.getElementById('wallet-address').value = currentConfig.wallet || ""; document.getElementById('wallet-address').value = currentConfig.wallet || "";
document.getElementById('power-cost').value = currentConfig.power_cost || ""; document.getElementById('power-cost').value = currentConfig.power_cost || "";
document.getElementById('power-usage').value = currentConfig.power_usage || ""; document.getElementById('power-usage').value = currentConfig.power_usage || "";
document.getElementById('network-fee').value = currentConfig.network_fee || ""; document.getElementById('network-fee').value = currentConfig.network_fee || "";
document.getElementById('currency').value = currentConfig.currency || "USD";
resolve(currentConfig); resolve(currentConfig);
}); });
}); });