Compare commits

...

25 Commits

Author SHA1 Message Date
fe1ce30bfe
new path 2025-04-28 11:13:54 +02:00
DJObleezy
b92f8074da Enhance README.md with new features and guidance
Updated README.md to include:
- Multi-Currency Support with configuration options and notifications
- Expanded Worker Management section with new API endpoints
- Added preferred fiat currency to environment variables in `docker-compose.yml`
- New API endpoints for managing notifications and fetching exchange rates
- Additional user guidance for currency and timezone settings
2025-04-27 17:10:11 -07:00
DJObleezy
923a4826a9 Update footer version to v0.9.2
Updated the version number in the footer of the `base.html` file from "v0.9.1 - Public Beta" to "v0.9.2 - Public Beta".
2025-04-27 16:59:23 -07:00
DJObleezy
9337dd49d3 Add read_only parameter to notification clearing API
Updated the `api_clear_notifications` function to include a `read_only` parameter, allowing conditional clearing of notifications based on their read status.

Modified the filtering logic in the `NotificationService` to retain notifications that match the specified category, are newer than a cutoff date, or are unread when `read_only` is true. This enhances the granularity of the notification clearing process.
2025-04-27 16:58:48 -07:00
DJObleezy
f7b2e550f6 Enhance notification clearing and update UI elements
- Added `read_only` parameter to `clear_notifications` method in `NotificationService` to filter unread notifications.
- Updated method docstring to reflect the new parameter.
- Changed page title and header from "BTC-OS MINING DASHBOARD" to "BTC-OS DASHBOARD".
- Incremented version number in footer from "v0.8.8" to "v0.9.1".
2025-04-27 16:46:51 -07:00
DJObleezy
39f983563a
Update README.md 2025-04-27 15:10:14 -07:00
DJObleezy
a50bd552f7 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.
2025-04-27 14:43:45 -07:00
DJObleezy
2f1cbd3143 Enhance hashrate detection and UI updates
Updated `NotificationService` to distinguish between low and normal hashrate modes, utilizing 3-hour and 10-minute averages for detection. Improved `updateChartWithNormalizedData` in `main.js` to support localStorage persistence for hashrate state and refined mode-switching logic. Introduced `showHashrateNormalizeNotice` for user notifications regarding hashrate normalization. Updated HTML files for UI consistency, including version number and structured display of pool fees and unpaid earnings. Ensured proper chart updates and annotations for 24-hour averages.
2025-04-27 11:18:42 -07:00
DJObleezy
f45ce8a1e8 Refactor footer styles and adjust container padding
- Removed margin-top from `.footer` in `common.css`, affecting spacing above the footer.
- Deleted `<style>` tag for theme preloading, which may impact theme loading behavior.
- Increased `padding-bottom` in `.container-fluid` from 60px to 100px in `dashboard.css` to accommodate a minimized system monitor.
2025-04-26 20:50:50 -07:00
DJObleezy
dec0045244 Update footer in base.html to include version info
Added a new paragraph to the footer displaying the version number "v0.8.7 - Public Beta".
2025-04-26 20:46:37 -07:00
DJObleezy
744ed27279 Add Alt+W shortcut to reset wallet address and chart
Implemented a keyboard event listener for Alt+W to reset the wallet address, clear chart data, and redirect to the configuration page with a confirmation dialog. Added a fallback function for wallet reset if chart data clearing fails. Updated the chart's Y-axis label to 'HASHRATE (TH/S)' and commented out the theme toggle button in the HTML for cleaner implementation.
2025-04-26 20:44:36 -07:00
DJObleezy
b4465e5a5c Revert "Update UI and functionality for wallet and fees"
This reverts commit 9ef1260347.
2025-04-26 20:39:05 -07:00
DJObleezy
9ef1260347 Update UI and functionality for wallet and fees
- Adjust footer CSS and increase padding in dashboard.
- Update chart title for clarity on hashrate units.
- Implement Alt+W keyboard shortcut to reset wallet address with confirmation and error handling.
- Add version number to footer in base.html.
- Change "Network Fee (%)" label to "Firmware Fee (%)" with updated tooltip in boot.html.
2025-04-26 20:35:27 -07:00
DJObleezy
57d8a9ab45 Refactor pool fee calculation to use last block earnings
Updated `calculatePoolFeeInSats` to accept `lastBlockEarnings` instead of `estimatedEarningsPerDay`. Added debugging logs for pool fee percentage and last block earnings. Modified `updateUI` to check for `last_block_earnings` and parse its value accordingly.
2025-04-26 17:36:51 -07:00
DJObleezy
b253e5aa7c Update .datum-label color to cyan
Changed the color of the `.datum-label` class from white (`#ffffff`) to cyan. Updated the corresponding comment to reflect the new color.
2025-04-26 16:14:29 -07:00
DJObleezy
0ab96cb7c1 Enhance pool fee display in SATS
- Added CSS rule to style pool fees in SATS as red and bold.
- Implemented `calculatePoolFeeInSats` function to compute fees based on percentage and earnings.
- Updated `updateUI` to display calculated pool fees, ensuring proper formatting and element creation.
2025-04-26 16:03:56 -07:00
DJObleezy
06f5c646e2 Enhance chart y-axis for low hashrate visibility
Updated `updateChartWithNormalizedData` to ensure the 24-hour average line is visible in low hashrate mode. Adjusted y-axis range calculations for both multi-point and single-point scenarios. Added console log statements for better debugging and feedback.
2025-04-26 15:31:31 -07:00
DJObleezy
367ba3788f Improve theme handling in BitcoinProgressBar.js
- Introduced fallback colors and utilized CSS variables for styling.
- Replaced hardcoded color values with dynamic CSS variable values.
- Enhanced theme change listener to respond to storage changes and class mutations for real-time UI updates.
2025-04-26 15:23:25 -07:00
DJObleezy
574d5637bc Refactor hashrate handling in main.js
Enhanced `updateChartWithNormalizedData` to manage low and high hashrate modes with a new state management system. Implemented debounce for mode switching and added hysteresis to prevent rapid transitions. Improved tracking of hashrate spikes for better handling of fluctuations.
2025-04-26 08:30:48 -07:00
DJObleezy
e9825c9006 Refactor hashrate normalization and improve error handling
- Refactored `normalizeHashrate` function to use a lookup table for unit conversion, enhancing error handling and logging for unrecognized units.
- Updated `ArrowIndicator` class to utilize the new global `normalizeHashrate` function for consistency.
- Enhanced `updateChartWithNormalizedData` with better error handling for 24-hour averages and current hashrate data, including checks for unreasonable values.
- Improved historical data handling with validation and anomaly detection.
- Encapsulated single data point display logic in `useSingleDataPoint` for better error management.
- Refined low hashrate indicator display logic to show only when necessary and updated its appearance based on the current theme.
- Overall improvements enhance robustness, maintainability, and user experience.
2025-04-26 06:39:55 -07:00
DJObleezy
24c46f058b Add last block earnings display to UI
Updated `updateUI()` in `main.js` to show last block earnings with a "+" prefix and "SATS" suffix. Modified `dashboard.html` to include a new span element that conditionally displays the earnings or defaults to "+0 SATS".
2025-04-26 06:13:58 -07:00
DJObleezy
e0c5f085cc Enhance data handling and UI responsiveness
- Updated `save_graph_state` in `state_manager.py` to include unit preservation for `arrow_history` and `metrics_log`, with improved data size logging and exception handling.
- Modified `notifications.css` for better mobile responsiveness, including a grid layout for `.filter-buttons` and adjusted button sizes.
- Changed value display in `initializeChart` to use 'PH' for petahashes, aligning with new unit standards.
- Ensured consistent unit formatting in `updateChartWithNormalizedData` for the 24-hour average line.
- Enhanced precision in `updateUI` for unpaid earnings display.
- Simplified y-axis label in chart configuration and updated value formatting to reflect new unit standards.
2025-04-25 22:13:01 -07:00
DJObleezy
312031dcae Add theme styles for Bitcoin and update DeepSea theme
Introduce new theme-specific styles for Bitcoin, including primary color and RGB values. Update DeepSea theme styles and implement a CSS variable for the graph container's shadow. Revise focus styles for theme toggle buttons to align with the new themes.
2025-04-25 17:41:12 -07:00
DJObleezy
0d5ddda2f8 Add theme-specific styles for graph container
Implemented CSS rules for Bitcoin and DeepSea themes,
adding distinct box shadows for the graph container based
on the active theme. Retained existing styling for the
theme toggle button in the Bitcoin theme.
2025-04-25 17:20:33 -07:00
DJObleezy
b9d2c39b85
Update README.md 2025-04-25 05:50:56 -07:00
16 changed files with 1482 additions and 445 deletions

41
App.py
View File

@ -368,12 +368,25 @@ def update_metrics_job(force=False):
if metrics:
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
notification_service.check_and_generate_notifications(metrics, cached_metrics)
# Then update cached metrics after comparison
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)
state_manager.update_metrics_history(metrics)
@ -771,7 +784,7 @@ def get_config():
@app.route("/api/config", methods=["POST"])
def update_config():
"""API endpoint to update configuration."""
global dashboard_service, worker_service # Add this to access the global dashboard_service
global dashboard_service, worker_service
try:
# Get the request data
@ -783,11 +796,19 @@ def update_config():
logging.error("Invalid configuration format")
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
defaults = {
"wallet": "yourwallethere",
"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
@ -802,7 +823,8 @@ def update_config():
dashboard_service = MiningDashboardService(
new_config.get("power_cost", 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')}")
@ -810,6 +832,15 @@ def update_config():
worker_service.set_dashboard_service(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
update_metrics_job(force=True)
logging.info("Forced metrics update after configuration change")
@ -1127,10 +1158,12 @@ def api_clear_notifications():
"""API endpoint to clear notifications."""
category = request.json.get('category')
older_than_days = request.json.get('older_than_days')
read_only = request.json.get('read_only', False) # Get the read_only parameter with default False
cleared_count = notification_service.clear_notifications(
category=category,
older_than_days=older_than_days
older_than_days=older_than_days,
read_only=read_only # Pass the parameter to the method
)
return jsonify({

View File

@ -8,9 +8,9 @@ This open-source dashboard provides real-time monitoring for Ocean.xyz pool mine
## Gallery:
![DeepSea Boot](https://github.com/user-attachments/assets/77222f13-1e95-48ee-a418-afd0e6b7a920)
![DeepSea Config](https://github.com/user-attachments/assets/48fcc2a6-f56e-48b9-ac61-b27e9b4a6e41)
![DeepSea Config](https://github.com/user-attachments/assets/e23859bd-76f3-4239-aa6b-060b5bf13f1b)
![DeepSea Dashboard](https://github.com/user-attachments/assets/8a96fd5e-5ba2-4e0e-be83-965ecb046671)
![DeepSea Workers](https://github.com/user-attachments/assets/a89b70f2-529e-46d6-b8dc-a140d618fb65)
![DeepSea Workers](https://github.com/user-attachments/assets/075d3f25-bbfb-4e0d-a4d1-1e7f23b96715)
![DeepSea Blocks](https://github.com/user-attachments/assets/078fc533-62c7-4375-bdb4-5f33e4a07925)
![DeepSea Notifications](https://github.com/user-attachments/assets/881ffac0-e447-4455-8b6e-39e2aac1b94e)
@ -28,6 +28,13 @@ This open-source dashboard provides real-time monitoring for Ocean.xyz pool mine
- **Payout Monitoring**: View unpaid balance and estimated time to next payout
- **Pool Fee Analysis**: Monitor pool fee percentages with visual indicator when optimal rates (0.9-1.3%) are detected
### Multi-Currency Support
- **Flexible Currency Configuration: Set your preferred fiat currency for displaying Bitcoin value and earnings
- **Wide Currency Selection: Choose from USD, EUR, GBP, JPY, CAD, AUD, CNY, KRW, BRL, CHF and more
- **Real-Time Exchange Rates: Automatically fetches up-to-date exchange rates from public APIs
- **Persistent Configuration: Currency preferences saved and restored between sessions
- **Adaptive Notifications: Financial notifications display in your selected currency
### Worker Management
- **Fleet Overview**: Comprehensive view of all mining devices in one interface
- **Status Monitoring**: Real-time status indicators for online and offline devices
@ -114,6 +121,7 @@ You can modify the following environment variables in the `docker-compose.yml` f
- `POWER_USAGE`: Power usage in watts.
- `NETWORK_FEE`: Additional fees beyond pool fees (e.g., firmware fees).
- `TIMEZONE`: Local timezone for displaying time information.
- `CURRENCY`: Preferred fiat currency for earnings display.
Redis data is stored in a persistent volume (`redis_data`), and application logs are saved in the `./logs` directory.
@ -174,6 +182,9 @@ Built with a modern stack for reliability and performance:
- `/api/available_timezones`: Returns a list of supported timezones.
- `/api/config`: Fetches or updates the mining configuration.
- `/api/health`: Returns the health status of the application.
- `/api/notifications`: Manages notifications for the user.
- `/api/workers`: Manages worker data and status.
- `/api/exchange_rates`: Fetches real-time exchange rates for supported currencies.
## Project Structure
@ -244,6 +255,9 @@ For optimal performance:
4. Access the health endpoint at `/api/health` for diagnostics
5. For stale data issues, use the Force Refresh function
6. Use hotkey Shift+R to clear chart and Redis data (as needed, not required)
7. Check the currency settings if financial calculations appear incorrect
8. Verify timezone settings for accurate time displays
9. Alt + W on Dashboard resets wallet configuration and redirects to Boot sequence
## License

View File

@ -7,7 +7,7 @@ import json
import logging
# Default configuration file path
CONFIG_FILE = "config.json"
CONFIG_FILE = "/root/config.json"
def load_config():
"""

View File

@ -164,6 +164,18 @@ class MiningDashboardService:
metrics["server_timestamp"] = 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
execution_time = time.time() - start_time
metrics["execution_time"] = execution_time
@ -452,6 +464,32 @@ class MiningDashboardService:
logging.error(f"Error fetching {url}: {e}")
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):
"""
Fetch Bitcoin network statistics with improved error handling and caching.

View File

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

View File

@ -1,14 +1,17 @@
# notification_service.py
# notification_service.py
import logging
import json
import time
import uuid
import pytz
import re
from datetime import datetime, timedelta
from enum import Enum
from collections import deque
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
ONE_DAY_SECONDS = 86400
@ -29,6 +32,52 @@ class NotificationCategory(Enum):
EARNINGS = "earnings"
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:
"""Service for managing mining dashboard notifications."""
@ -263,13 +312,14 @@ class NotificationService:
return deleted > 0
def clear_notifications(self, category: Optional[str] = None, older_than_days: Optional[int] = None) -> int:
def clear_notifications(self, category: Optional[str] = None, older_than_days: Optional[int] = None, read_only: bool = False) -> int:
"""
Clear notifications with optimized filtering.
Args:
category (str, optional): Only clear specific category
older_than_days (int, optional): Only clear notifications older than this
read_only (bool, optional): Only clear notifications that have been read
Returns:
int: Number of notifications cleared
@ -280,11 +330,12 @@ class NotificationService:
if older_than_days:
cutoff_date = self._get_current_time() - timedelta(days=older_than_days)
# Apply filters in a single pass
# Apply filters to KEEP notifications that should NOT be cleared
self.notifications = [
n for n in self.notifications
if (not category or n.get("category") != category) and
(not cutoff_date or self._parse_timestamp(n.get("timestamp", self._get_current_time().isoformat())) >= cutoff_date)
if (category and n.get("category") != category) or # Keep if we're filtering by category and this isn't that category
(cutoff_date and self._parse_timestamp(n.get("timestamp", self._get_current_time().isoformat())) >= cutoff_date) or # Keep if newer than cutoff
(read_only and not n.get("read", False)) # Keep if we're only clearing read notifications and this is unread
]
cleared_count = original_count - len(self.notifications)
@ -313,6 +364,11 @@ class NotificationService:
logging.warning("[NotificationService] No current metrics available, skipping notification checks")
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)
last_block_height = current_metrics.get("last_block_height")
if last_block_height and last_block_height != "N/A":
@ -384,12 +440,22 @@ class NotificationService:
hashrate_24hr = metrics.get("hashrate_24hr", 0)
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_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
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
logging.info(f"[NotificationService] Generating daily stats notification: {message}")
@ -401,7 +467,8 @@ class NotificationService:
"hashrate": hashrate_24hr,
"unit": hashrate_unit,
"daily_sats": daily_mined_sats,
"daily_profit": daily_profit_usd
"daily_profit": daily_profit_usd,
"currency": user_currency
}
)
except Exception as e:
@ -447,38 +514,71 @@ class NotificationService:
return 0.0
def _check_hashrate_change(self, current: Dict[str, Any], previous: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Check for significant hashrate changes using 10-minute average."""
"""Check for significant hashrate changes using appropriate time window based on mode."""
try:
# Get 10min hashrate values
current_10min = current.get("hashrate_10min", 0)
previous_10min = previous.get("hashrate_10min", 0)
# Check if we're in low hashrate mode
# A simple threshold approach: if hashrate_3hr is below 1 TH/s, consider it low hashrate mode
is_low_hashrate_mode = False
if "hashrate_3hr" in current:
current_3hr = self._parse_numeric_value(current.get("hashrate_3hr", 0))
current_3hr_unit = current.get("hashrate_3hr_unit", "TH/s").lower()
# Normalize to TH/s for comparison
if "ph/s" in current_3hr_unit:
current_3hr *= 1000
elif "gh/s" in current_3hr_unit:
current_3hr /= 1000
elif "mh/s" in current_3hr_unit:
current_3hr /= 1000000
# If hashrate is less than 3 TH/s, consider it low hashrate mode
is_low_hashrate_mode = current_3hr < 3.0
logging.debug(f"[NotificationService] Low hashrate mode: {is_low_hashrate_mode}")
# Choose the appropriate hashrate metric based on mode
if is_low_hashrate_mode:
# In low hashrate mode, use 3hr averages for more stability
current_hashrate_key = "hashrate_3hr"
previous_hashrate_key = "hashrate_3hr"
timeframe = "3hr"
else:
# In normal mode, use 10min averages for faster response
current_hashrate_key = "hashrate_10min"
previous_hashrate_key = "hashrate_10min"
timeframe = "10min"
# Get hashrate values
current_hashrate = current.get(current_hashrate_key, 0)
previous_hashrate = previous.get(previous_hashrate_key, 0)
# Log what we're comparing
logging.debug(f"[NotificationService] Comparing 10min hashrates - current: {current_10min}, previous: {previous_10min}")
logging.debug(f"[NotificationService] Comparing {timeframe} hashrates - current: {current_hashrate}, previous: {previous_hashrate}")
# Skip if values are missing
if not current_10min or not previous_10min:
logging.debug("[NotificationService] Skipping hashrate check - missing values")
if not current_hashrate or not previous_hashrate:
logging.debug(f"[NotificationService] Skipping hashrate check - missing {timeframe} values")
return None
# Parse values consistently
current_value = self._parse_numeric_value(current_10min)
previous_value = self._parse_numeric_value(previous_10min)
current_value = self._parse_numeric_value(current_hashrate)
previous_value = self._parse_numeric_value(previous_hashrate)
logging.debug(f"[NotificationService] Converted 10min hashrates - current: {current_value}, previous: {previous_value}")
logging.debug(f"[NotificationService] Converted {timeframe} hashrates - current: {current_value}, previous: {previous_value}")
# Skip if previous was zero (prevents division by zero)
if previous_value == 0:
logging.debug("[NotificationService] Skipping hashrate check - previous was zero")
logging.debug(f"[NotificationService] Skipping hashrate check - previous {timeframe} was zero")
return None
# Calculate percentage change
percent_change = ((current_value - previous_value) / previous_value) * 100
logging.debug(f"[NotificationService] 10min hashrate change: {percent_change:.1f}%")
logging.debug(f"[NotificationService] {timeframe} hashrate change: {percent_change:.1f}%")
# Significant decrease
if percent_change <= -SIGNIFICANT_HASHRATE_CHANGE_PERCENT:
message = f"Significant 10min hashrate drop detected: {abs(percent_change):.1f}% decrease"
message = f"Significant {timeframe} hashrate drop detected: {abs(percent_change):.1f}% decrease"
logging.info(f"[NotificationService] Generating hashrate notification: {message}")
return self.add_notification(
message,
@ -488,13 +588,14 @@ class NotificationService:
"previous": previous_value,
"current": current_value,
"change": percent_change,
"timeframe": "10min"
"timeframe": timeframe,
"is_low_hashrate_mode": is_low_hashrate_mode
}
)
# Significant increase
elif percent_change >= SIGNIFICANT_HASHRATE_CHANGE_PERCENT:
message = f"10min hashrate increase detected: {percent_change:.1f}% increase"
message = f"{timeframe} hashrate increase detected: {percent_change:.1f}% increase"
logging.info(f"[NotificationService] Generating hashrate notification: {message}")
return self.add_notification(
message,
@ -504,7 +605,8 @@ class NotificationService:
"previous": previous_value,
"current": current_value,
"change": percent_change,
"timeframe": "10min"
"timeframe": timeframe,
"is_low_hashrate_mode": is_low_hashrate_mode
}
)
@ -516,6 +618,18 @@ class NotificationService:
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."""
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"))
# Check if approaching payout
@ -570,6 +684,24 @@ class NotificationService:
logging.error(f"[NotificationService] Error checking earnings progress: {e}")
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:
"""Check if enough time has passed since the last payout notification."""
if self.last_payout_notification_time is None:

View File

@ -126,85 +126,61 @@ class StateManager:
logging.info("Redis not available, skipping state save.")
return
# Check if we've saved recently to avoid too frequent saves
# Only save at most once every 5 minutes
current_time = time.time()
if hasattr(self, 'last_save_time') and current_time - self.last_save_time < 300: # 300 seconds = 5 minutes
if hasattr(self, 'last_save_time') and current_time - self.last_save_time < 300: # 5 minutes
logging.debug("Skipping Redis save - last save was less than 5 minutes ago")
return
# Update the last save time
self.last_save_time = current_time
# Prune data first to reduce volume
self.prune_old_data()
# Create compact versions of the data structures for Redis storage
try:
# 1. Create compact arrow_history with minimal data
# Compact arrow_history with unit preservation
compact_arrow_history = {}
for key, values in arrow_history.items():
if isinstance(values, list) and values:
# Only store recent history (last 2 hours)
recent_values = values[-180:] if len(values) > 180 else values
# Use shorter field names and preserve arrow directions
compact_arrow_history[key] = [
{"t": entry["time"], "v": entry["value"], "a": entry["arrow"]}
{"t": entry["time"], "v": entry["value"], "a": entry["arrow"], "u": entry.get("unit", "th/s")}
for entry in recent_values
]
# 2. Only keep essential hashrate_history
# Compact hashrate_history
compact_hashrate_history = hashrate_history[-60:] if len(hashrate_history) > 60 else hashrate_history
# 3. Only keep recent metrics_log entries (last 30 minutes)
# This is typically the largest data structure
# Compact metrics_log with unit preservation
compact_metrics_log = []
if metrics_log:
# Keep only last 30 entries (30 minutes assuming 1-minute updates)
recent_logs = metrics_log[-30:]
for entry in recent_logs:
# Only keep necessary fields from each metrics entry
if "metrics" in entry and "timestamp" in entry:
metrics_copy = {}
original_metrics = entry["metrics"]
# Only copy the most important metrics for historical tracking
essential_keys = [
"hashrate_60sec", "hashrate_24hr", "btc_price",
"workers_hashing", "unpaid_earnings", "difficulty",
"network_hashrate", "daily_profit_usd"
]
for key in essential_keys:
if key in original_metrics:
metrics_copy[key] = original_metrics[key]
# Skip arrow_history within metrics as we already stored it separately
metrics_copy[key] = {
"value": original_metrics[key],
"unit": original_metrics.get(f"{key}_unit", "th/s")
}
compact_metrics_log.append({
"ts": entry["timestamp"],
"m": metrics_copy
})
# Create the final state object
state = {
"arrow_history": compact_arrow_history,
"hashrate_history": compact_hashrate_history,
"metrics_log": compact_metrics_log
}
# Convert to JSON once to reuse and measure size
state_json = json.dumps(state)
data_size_kb = len(state_json) / 1024
# Log data size for monitoring
logging.info(f"Saving graph state to Redis: {data_size_kb:.2f} KB (optimized format)")
# Only save if data size is reasonable (adjust threshold as needed)
if data_size_kb > 2000: # 2MB warning threshold (reduced from 5MB)
logging.warning(f"Redis save data size is still large: {data_size_kb:.2f} KB")
# Store version info to handle future format changes
self.redis_client.set(f"{self.STATE_KEY}_version", "2.0")
self.redis_client.set(self.STATE_KEY, state_json)
logging.info(f"Successfully saved graph state to Redis ({data_size_kb:.2f} KB)")

View File

@ -1,14 +1,10 @@
.footer {
margin-top: 30px;
padding: 10px 0;
color: grey;
font-size: 0.9rem;
border-top: 1px solid rgba(128, 128, 128, 0.2);
}
</style >
<!-- Preload theme to prevent flicker -->
<style id="theme-preload" >
/* Theme-aware loading state */
html.bitcoin-theme {
background-color: #111111;

View File

@ -184,7 +184,7 @@
}
/* Add bottom padding to accommodate minimized system monitor */
.container-fluid {
padding-bottom: 60px !important; /* Enough space for minimized monitor */
padding-bottom: 100px !important; /* Enough space for minimized monitor */
}
/* Add these styles to dashboard.css */
@ -218,7 +218,7 @@
}
.datum-label {
color: #ffffff; /* White color */
color: cyan; /* cyan color */
font-size: 0.95em;
font-weight: bold;
text-transform: uppercase;
@ -245,3 +245,10 @@
.unlucky {
color: #ff5555 !important;
}
/* Ensure the pool fee in SATS is always red regardless of theme */
#pool_fees_sats {
color: #ff5555 !important;
font-weight: bold !important;
margin-left: 6px;
}

View File

@ -283,12 +283,13 @@
.notification-actions {
flex-direction: column;
gap: 8px;
margin-top: 10px;
}
.action-button {
width: 100%; /* Full width on small screens */
padding: 8px 12px;
font-size: 1rem;
font-size: 0.95rem;
}
.notification-controls {
@ -297,16 +298,27 @@
}
.filter-buttons {
overflow-x: auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
padding-bottom: 5px;
margin-bottom: 5px;
white-space: nowrap;
margin-bottom: 10px;
width: 100%;
}
.filter-button {
text-align: center;
white-space: normal;
font-size: 0.9rem;
padding: 8px 5px;
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
min-height: 38px;
}
.notification-actions {
justify-content: flex-end;
justify-content: stretch;
}
.notification-item {
@ -324,4 +336,11 @@
.notification-actions {
flex: 0 0 60px;
}
/* For very small screens, reduce to 2 columns */
@media (max-width: 375px) {
.filter-buttons {
grid-template-columns: repeat(2, 1fr);
}
}
}

View File

@ -91,6 +91,22 @@
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
}
/* Add RGB values for primary colors */
html.deepsea-theme {
--primary-color: #0088cc;
--primary-color-rgb: 0, 136, 204;
}
html.bitcoin-theme {
--primary-color: #f2a900;
--primary-color-rgb: 242, 169, 0;
}
/* Theme-specific graph container shadow using CSS variable */
#graphContainer {
box-shadow: 0 0 10px rgba(var(--primary-color-rgb), 0.2) !important;
}
/* Bitcoin theme specific styling (orange) */
body:not(.deepsea-theme) #themeToggle,
body:not(.deepsea-theme) .theme-toggle-btn {
@ -142,15 +158,6 @@ body.deepsea-theme .theme-toggle-btn:focus {
box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3);
}
/* Add to your common.css or theme-toggle.css */
html.deepsea-theme {
--primary-color: #0088cc;
}
html.bitcoin-theme {
--primary-color: #f2a900;
}
/* Add these theme-specific loading styles */
#theme-loader {
position: fixed;

View File

@ -8,8 +8,10 @@
const BitcoinMinuteRefresh = (function () {
// Constants
const STORAGE_KEY = 'bitcoin_last_refresh_time';
const BITCOIN_COLOR = '#f7931a';
const DEEPSEA_COLOR = '#0088cc';
// Default fallback colors if CSS vars aren't available
const FALLBACK_BITCOIN_COLOR = '#f2a900';
const FALLBACK_DEEPSEA_COLOR = '#0088cc';
const DOM_IDS = {
TERMINAL: 'bitcoin-terminal',
STYLES: 'bitcoin-terminal-styles',
@ -45,9 +47,32 @@ const BitcoinMinuteRefresh = (function () {
let uptimeInterval = null;
let isInitialized = false;
let refreshCallback = null;
let currentThemeColor = BITCOIN_COLOR; // Default Bitcoin color
let currentThemeColor = '';
let currentThemeRGB = '';
let dragListenersAdded = false;
/**
* Get theme colors from CSS variables
*/
function getThemeColors() {
// Try to get CSS variables from document root
const rootStyles = getComputedStyle(document.documentElement);
let primaryColor = rootStyles.getPropertyValue('--primary-color').trim();
let primaryColorRGB = rootStyles.getPropertyValue('--primary-color-rgb').trim();
// If CSS vars not available, use theme toggle state
if (!primaryColor) {
const isDeepSea = localStorage.getItem(STORAGE_KEYS.THEME) === 'true';
primaryColor = isDeepSea ? FALLBACK_DEEPSEA_COLOR : FALLBACK_BITCOIN_COLOR;
primaryColorRGB = isDeepSea ? '0, 136, 204' : '242, 169, 0';
}
return {
color: primaryColor,
rgb: primaryColorRGB
};
}
/**
* Logging helper function
* @param {string} message - Message to log
@ -79,24 +104,22 @@ const BitcoinMinuteRefresh = (function () {
* Apply the current theme color
*/
function applyThemeColor() {
// Check if theme toggle is set to DeepSea
const isDeepSeaTheme = localStorage.getItem(STORAGE_KEYS.THEME) === 'true';
currentThemeColor = isDeepSeaTheme ? DEEPSEA_COLOR : BITCOIN_COLOR;
// Get current theme colors
const theme = getThemeColors();
currentThemeColor = theme.color;
currentThemeRGB = theme.rgb;
// Don't try to update DOM elements if they don't exist yet
if (!terminalElement) return;
// Define color values based on theme
const rgbValues = isDeepSeaTheme ? '0, 136, 204' : '247, 147, 26';
// Create theme config
const themeConfig = {
color: currentThemeColor,
borderColor: currentThemeColor,
boxShadow: `0 0 5px rgba(${rgbValues}, 0.3)`,
textShadow: `0 0 5px rgba(${rgbValues}, 0.8)`,
borderColorRGBA: `rgba(${rgbValues}, 0.5)`,
textShadowStrong: `0 0 8px rgba(${rgbValues}, 0.8)`
boxShadow: `0 0 5px rgba(${currentThemeRGB}, 0.3)`,
textShadow: `0 0 5px rgba(${currentThemeRGB}, 0.8)`,
borderColorRGBA: `rgba(${currentThemeRGB}, 0.5)`,
textShadowStrong: `0 0 8px rgba(${currentThemeRGB}, 0.8)`
};
// Apply styles to terminal
@ -144,6 +167,13 @@ const BitcoinMinuteRefresh = (function () {
if (miniLabel) {
miniLabel.style.color = themeConfig.color;
}
// Update show button if it exists
const showButton = document.getElementById(DOM_IDS.SHOW_BUTTON);
if (showButton) {
showButton.style.backgroundColor = themeConfig.color;
showButton.style.boxShadow = `0 0 10px rgba(${currentThemeRGB}, 0.5)`;
}
}
/**
@ -156,6 +186,25 @@ const BitcoinMinuteRefresh = (function () {
applyThemeColor();
}
});
// Listen for custom theme change events
document.addEventListener('themeChanged', function () {
applyThemeColor();
});
// Watch for class changes on HTML element that might indicate theme changes
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.attributeName === 'class') {
applyThemeColor();
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
}
/**
@ -348,8 +397,12 @@ const BitcoinMinuteRefresh = (function () {
<div class="terminal-header">
<div class="terminal-title">SYSTEM MONITOR v.3</div>
<div class="terminal-controls">
<div class="terminal-dot minimize" title="Minimize" onclick="BitcoinMinuteRefresh.toggleTerminal()"></div>
<div class="terminal-dot close" title="Close" onclick="BitcoinMinuteRefresh.hideTerminal()"></div>
<div class="terminal-dot minimize" title="Minimize" onclick="BitcoinMinuteRefresh.toggleTerminal()">
<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 class="terminal-content">
@ -413,13 +466,12 @@ const BitcoinMinuteRefresh = (function () {
* Add CSS styles for the terminal
*/
function addStyles() {
// Use the currentThemeColor variable instead of hardcoded colors
// Get current theme colors for initial styling
const theme = getThemeColors();
const styleElement = document.createElement('style');
styleElement.id = DOM_IDS.STYLES;
// Generate RGB values for dynamic colors
const rgbValues = currentThemeColor === DEEPSEA_COLOR ? '0, 136, 204' : '247, 147, 26';
styleElement.textContent = `
/* Terminal Container */
.bitcoin-terminal {
@ -428,14 +480,14 @@ const BitcoinMinuteRefresh = (function () {
right: 20px;
width: 230px;
background-color: #000000;
border: 1px solid ${currentThemeColor};
color: ${currentThemeColor};
border: 1px solid var(--primary-color, ${theme.color});
color: var(--primary-color, ${theme.color});
font-family: 'VT323', monospace;
z-index: 9999;
overflow: hidden;
padding: 8px;
transition: all 0.3s ease;
box-shadow: 0 0 5px rgba(${rgbValues}, 0.3);
box-shadow: 0 0 5px rgba(var(--primary-color-rgb, ${theme.rgb}), 0.3);
}
/* Terminal Header */
@ -443,20 +495,19 @@ const BitcoinMinuteRefresh = (function () {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid ${currentThemeColor};
border-bottom: 1px solid var(--primary-color, ${theme.color});
padding-bottom: 5px;
margin-bottom: 8px;
cursor: grab; /* Add grab cursor on hover */
cursor: grab;
}
/* Apply grabbing cursor during active drag */
.terminal-header:active,
.bitcoin-terminal.dragging .terminal-header {
cursor: grabbing;
}
.terminal-title {
color: ${currentThemeColor};
color: var(--primary-color, ${theme.color});
font-weight: bold;
font-size: 1.1rem;
animation: terminal-flicker 4s infinite;
@ -470,22 +521,45 @@ const BitcoinMinuteRefresh = (function () {
}
.terminal-dot {
width: 8px;
height: 8px;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #555;
cursor: pointer;
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 {
background-color: #ffcc00;
}
.terminal-dot.minimize:hover .control-symbol {
color: #664e00;
}
.terminal-dot.close:hover {
background-color: #ff3b30;
}
.terminal-dot.close:hover .control-symbol {
color: #7a0200;
}
/* Terminal Content */
.terminal-content {
position: relative;
@ -524,14 +598,14 @@ const BitcoinMinuteRefresh = (function () {
font-weight: bold;
}
/* Uptime Display - Modern Digital Clock Style (Horizontal) */
/* Uptime Display */
.uptime-timer {
display: flex;
flex-direction: column;
align-items: center;
padding: 5px;
background-color: #111;
border: 1px solid rgba(${rgbValues}, 0.5);
border: 1px solid rgba(var(--primary-color-rgb, ${theme.rgb}), 0.5);
margin-top: 5px;
}
@ -586,7 +660,7 @@ const BitcoinMinuteRefresh = (function () {
position: fixed;
bottom: 10px;
right: 10px;
background-color: ${currentThemeColor};
background-color: var(--primary-color, ${theme.color});
color: #000;
border: none;
padding: 8px 12px;
@ -594,7 +668,7 @@ const BitcoinMinuteRefresh = (function () {
cursor: pointer;
z-index: 9999;
display: none;
box-shadow: 0 0 10px rgba(${rgbValues}, 0.5);
box-shadow: 0 0 10px rgba(var(--primary-color-rgb, ${theme.rgb}), 0.5);
}
/* CRT scanline effect */
@ -660,7 +734,7 @@ const BitcoinMinuteRefresh = (function () {
letter-spacing: 1px;
opacity: 0.7;
margin-left: 45px;
color: ${currentThemeColor};
color: var(--primary-color, ${theme.color});
}
#${DOM_IDS.MINIMIZED_UPTIME} {
@ -855,9 +929,6 @@ const BitcoinMinuteRefresh = (function () {
// Store the refresh callback
refreshCallback = refreshFunc;
// Get current theme status
applyThemeColor();
// Create the terminal element if it doesn't exist
if (!document.getElementById(DOM_IDS.TERMINAL)) {
createTerminalElement();
@ -865,11 +936,11 @@ const BitcoinMinuteRefresh = (function () {
// Get references to existing elements
terminalElement = document.getElementById(DOM_IDS.TERMINAL);
uptimeElement = document.getElementById('uptime-timer');
// Apply theme to existing element
applyThemeColor();
}
// Apply theme colors
applyThemeColor();
// Set up listener for theme changes
setupThemeChangeListener();
@ -923,6 +994,9 @@ const BitcoinMinuteRefresh = (function () {
} catch (e) {
log("Error reading updated server time: " + e.message, 'error');
}
} else if (event.key === STORAGE_KEYS.THEME) {
// Update theme when theme preference changes
applyThemeColor();
}
}
@ -933,6 +1007,9 @@ const BitcoinMinuteRefresh = (function () {
if (!document.hidden) {
log("Page became visible, updating");
// Apply current theme when page becomes visible
applyThemeColor();
// Update immediately when page becomes visible
updateClock();
updateUptime();
@ -989,6 +1066,11 @@ const BitcoinMinuteRefresh = (function () {
showButton.textContent = 'Show Monitor';
showButton.onclick = showTerminal;
document.body.appendChild(showButton);
// Apply current theme to the button
const theme = getThemeColors();
showButton.style.backgroundColor = theme.color;
showButton.style.boxShadow = `0 0 10px rgba(${theme.rgb}, 0.5)`;
}
document.getElementById(DOM_IDS.SHOW_BUTTON).style.display = 'block';

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}BTC-OS MINING DASHBOARD {% endblock %}</title>
<title>{% block title %}BTC-OS DASHBOARD {% endblock %}</title>
<!-- Common fonts -->
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=VT323&display=swap" rel="stylesheet">
@ -96,7 +96,7 @@
<h1 class="text-center">
<a href="/" style="text-decoration:none; color:inherit;">
{% block header %}BTC-OS MINING DASHBOARD{% endblock %}
{% block header %}BTC-OS DASHBOARD{% endblock %}
</a>
</h1>
@ -135,6 +135,7 @@
<!-- Footer -->
<footer class="footer text-center">
<p>Not affiliated with <a href="https://www.Ocean.xyz">Ocean.xyz</a></p>
<p>v0.9.2 - Public Beta</p>
</footer>
</div>

View File

@ -68,9 +68,9 @@
});
</script>
<!-- Theme toggle button (new) -->
<button id="themeToggle" class="theme-toggle-btn">
<!--<button id="themeToggle" class="theme-toggle-btn">
<span>Toggle Theme</span>
</button>
</button>-->
<button id="skip-button">SKIP</button>
<div id="debug-info"></div>
<div id="loading-message">Loading mining data...</div>
@ -109,6 +109,27 @@ v.21
</label>
<input type="text" id="wallet-address" placeholder="bc1..." value="">
</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">
<label for="power-cost">
Power Cost ($/kWh)
@ -131,7 +152,7 @@ v.21
</div>
<div class="form-group">
<label for="network-fee">
Network Fee (%)
Firmware/Other Fees (%)
<span class="tooltip">
?
<span class="tooltip-text">Additional fees beyond pool fee, like Firmware fees</span>
@ -281,20 +302,22 @@ v.21
loadTimezoneFromConfig();
});
// Update saveConfig to include network fee
// Update saveConfig to include currency
function saveConfig() {
const wallet = document.getElementById('wallet-address').value.trim();
const powerCost = parseFloat(document.getElementById('power-cost').value) || 0;
const powerUsage = parseFloat(document.getElementById('power-usage').value) || 0;
const timezone = document.getElementById('timezone').value;
const networkFee = parseFloat(document.getElementById('network-fee').value) || 0;
const currency = document.getElementById('currency').value;
const updatedConfig = {
wallet: wallet || (currentConfig ? currentConfig.wallet : ""),
power_cost: powerCost,
power_usage: powerUsage,
timezone: timezone,
network_fee: networkFee
network_fee: networkFee,
currency: currency
};
return fetch('/api/config', {
@ -345,7 +368,7 @@ v.21
power_usage: 0.0
};
// Update loadConfig function to include network fee
// Update loadConfig function to handle currency
function loadConfig() {
return new Promise((resolve, reject) => {
fetch('/api/config?nocache=' + new Date().getTime())
@ -364,6 +387,12 @@ v.21
document.getElementById('power-cost').value = currentConfig.power_cost || "";
document.getElementById('power-usage').value = currentConfig.power_usage || "";
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;
resolve(currentConfig);
})
@ -374,13 +403,16 @@ v.21
wallet: "yourwallethere",
power_cost: 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('power-cost').value = currentConfig.power_cost || "";
document.getElementById('power-usage').value = currentConfig.power_usage || "";
document.getElementById('network-fee').value = currentConfig.network_fee || "";
document.getElementById('currency').value = currentConfig.currency || "USD";
resolve(currentConfig);
});
});

View File

@ -55,43 +55,6 @@
<div class="card" id="payoutMiscCard">
<div class="card-header">Payout Info</div>
<div class="card-body">
<p>
<strong>Unpaid Earnings:</strong>
<span id="unpaid_earnings" class="metric-value green">
{% if metrics and metrics.unpaid_earnings %}
{{ metrics.unpaid_earnings }} BTC
{% else %}
0 BTC
{% endif %}
</span>
<span id="indicator_unpaid_earnings"></span>
</p>
<p>
<strong>Last Block:</strong>
<span id="last_block_height" class="metric-value white">
{{ metrics.last_block_height|commafy if metrics and metrics.last_block_height else "N/A" }}
</span>
<span id="last_block_time" class="metric-value blue">
{{ metrics.last_block_time if metrics and metrics.last_block_time else "N/A" }}
</span>
<span class="green">
{% if metrics and metrics.last_block_earnings %}
+{{ metrics.last_block_earnings|int|commafy }} SATS
{% else %}
+0 SATS
{% endif %}
</span>
<span id="indicator_last_block"></span>
</p>
<p>
<strong>Est. Time to Payout:</strong>
<span id="est_time_to_payout" class="metric-value yellow">
{{ metrics.est_time_to_payout if metrics and metrics.est_time_to_payout else "N/A" }}
</span>
<span id="indicator_est_time_to_payout"></span>
</p>
<p>
<strong>Pool Fees:</strong>
<span id="pool_fees_percentage" class="metric-value">
@ -106,6 +69,43 @@
</span>
<span id="indicator_pool_fees_percentage"></span>
</p>
<p>
<strong>Last Block:</strong>
<span id="last_block_height" class="metric-value white">
{{ metrics.last_block_height|commafy if metrics and metrics.last_block_height else "N/A" }}
</span>
<span id="last_block_time" class="metric-value blue">
{{ metrics.last_block_time if metrics and metrics.last_block_time else "N/A" }}
</span>
<span id="last_block_earnings" class="metric-value green">
{% if metrics and metrics.last_block_earnings %}
+{{ metrics.last_block_earnings|int|commafy }} SATS
{% else %}
+0 SATS
{% endif %}
</span>
<span id="indicator_last_block"></span>
</p>
<p>
<strong>Unpaid Earnings:</strong>
<span id="unpaid_earnings" class="metric-value green">
{% if metrics and metrics.unpaid_earnings %}
{{ metrics.unpaid_earnings }} BTC
{% else %}
0 BTC
{% endif %}
</span>
<span id="indicator_unpaid_earnings"></span>
</p>
<p>
<strong>Est. Time to Payout:</strong>
<span id="est_time_to_payout" class="metric-value yellow">
{{ metrics.est_time_to_payout if metrics and metrics.est_time_to_payout else "N/A" }}
</span>
<span id="indicator_est_time_to_payout"></span>
</p>
</div>
</div>
</div>