mirror of
https://github.com/Retropex/custom-ocean.xyz-dashboard.git
synced 2025-05-12 19:20:45 +02:00
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:
parent
2f1cbd3143
commit
a50bd552f7
37
App.py
37
App.py
@ -368,12 +368,25 @@ def update_metrics_job(force=False):
|
|||||||
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")
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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."""
|
||||||
|
|
||||||
@ -313,6 +362,11 @@ class NotificationService:
|
|||||||
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")
|
||||||
if last_block_height and last_block_height != "N/A":
|
if last_block_height and last_block_height != "N/A":
|
||||||
@ -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,6 +616,18 @@ 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
|
||||||
@ -605,6 +682,24 @@ 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:
|
||||||
|
@ -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;
|
||||||
|
@ -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,32 +2125,45 @@ 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 dailyProfit = data.daily_profit_usd * exchangeRate;
|
||||||
const dailyProfitElement = document.getElementById("daily_profit_usd");
|
const dailyProfitElement = document.getElementById("daily_profit_usd");
|
||||||
if (dailyProfitElement) {
|
if (dailyProfitElement) {
|
||||||
dailyProfitElement.textContent = "$" + numberWithCommas(dailyProfitUSD.toFixed(2));
|
dailyProfitElement.textContent = formatCurrencyValue(dailyProfit, currency);
|
||||||
if (dailyProfitUSD < 0) {
|
if (dailyProfit < 0) {
|
||||||
// Use setAttribute to properly set the style with !important
|
// Use setAttribute to properly set the style with !important
|
||||||
dailyProfitElement.setAttribute("style", "color: #ff5555 !important; font-weight: bold !important;");
|
dailyProfitElement.setAttribute("style", "color: #ff5555 !important; font-weight: bold !important;");
|
||||||
} else {
|
} else {
|
||||||
// Clear the style attribute completely instead of setting it to empty
|
// Clear the style attribute completely
|
||||||
dailyProfitElement.removeAttribute("style");
|
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 monthlyProfit = data.monthly_profit_usd * exchangeRate;
|
||||||
const monthlyProfitElement = document.getElementById("monthly_profit_usd");
|
const monthlyProfitElement = document.getElementById("monthly_profit_usd");
|
||||||
if (monthlyProfitElement) {
|
if (monthlyProfitElement) {
|
||||||
monthlyProfitElement.textContent = "$" + numberWithCommas(monthlyProfitUSD.toFixed(2));
|
monthlyProfitElement.textContent = formatCurrencyValue(monthlyProfit, currency);
|
||||||
if (monthlyProfitUSD < 0) {
|
if (monthlyProfit < 0) {
|
||||||
// Use setAttribute to properly set the style with !important
|
// Use setAttribute to properly set the style with !important
|
||||||
monthlyProfitElement.setAttribute("style", "color: #ff5555 !important; font-weight: bold !important;");
|
monthlyProfitElement.setAttribute("style", "color: #ff5555 !important; font-weight: bold !important;");
|
||||||
} else {
|
} else {
|
||||||
@ -2136,6 +2171,7 @@ function updateUI() {
|
|||||||
monthlyProfitElement.removeAttribute("style");
|
monthlyProfitElement.removeAttribute("style");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateElementText("daily_mined_sats", numberWithCommas(data.daily_mined_sats) + " SATS");
|
updateElementText("daily_mined_sats", numberWithCommas(data.daily_mined_sats) + " SATS");
|
||||||
updateElementText("monthly_mined_sats", numberWithCommas(data.monthly_mined_sats) + " SATS");
|
updateElementText("monthly_mined_sats", numberWithCommas(data.monthly_mined_sats) + " SATS");
|
||||||
@ -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 {
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user