mirror of
https://github.com/Retropex/custom-ocean.xyz-dashboard.git
synced 2025-05-12 03:00:45 +02:00
Add configurable timezone support throughout the app
Updated the application to use a configurable timezone instead of hardcoding "America/Los_Angeles". This change impacts the dashboard, API endpoints, and worker services. Timezone is now fetched from a configuration file or environment variable, enhancing flexibility in time display. New API endpoints for available timezones and the current configured timezone have been added. The frontend now allows users to select their timezone from a dropdown menu, which is stored in local storage for future use. Timestamps in the UI have been updated to reflect the selected timezone.
This commit is contained in:
parent
96a71ec80d
commit
97fe19d61d
41
App.py
41
App.py
@ -22,6 +22,7 @@ from config import load_config, save_config
|
||||
from data_service import MiningDashboardService
|
||||
from worker_service import WorkerService
|
||||
from state_manager import StateManager, arrow_history, metrics_log
|
||||
from config import get_timezone
|
||||
|
||||
# Initialize Flask app
|
||||
app = Flask(__name__)
|
||||
@ -45,7 +46,7 @@ scheduler_recreate_lock = threading.Lock()
|
||||
scheduler = None
|
||||
|
||||
# Global start time
|
||||
SERVER_START_TIME = datetime.now(ZoneInfo("America/Los_Angeles"))
|
||||
SERVER_START_TIME = datetime.now(ZoneInfo(get_timezone()))
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
@ -417,8 +418,8 @@ def dashboard():
|
||||
# If still None after our attempt, create default metrics
|
||||
if cached_metrics is None:
|
||||
default_metrics = {
|
||||
"server_timestamp": datetime.now(ZoneInfo("America/Los_Angeles")).isoformat(),
|
||||
"server_start_time": SERVER_START_TIME.astimezone(ZoneInfo("America/Los_Angeles")).isoformat(),
|
||||
"server_timestamp": datetime.now(ZoneInfo(get_timezone())).isoformat(),
|
||||
"server_start_time": SERVER_START_TIME.astimezone(ZoneInfo(get_timezone())).isoformat(),
|
||||
"hashrate_24hr": None,
|
||||
"hashrate_24hr_unit": "TH/s",
|
||||
"hashrate_3hr": None,
|
||||
@ -453,12 +454,12 @@ def dashboard():
|
||||
"arrow_history": {}
|
||||
}
|
||||
logging.warning("Rendering dashboard with default metrics - no data available yet")
|
||||
current_time = datetime.now(ZoneInfo("America/Los_Angeles")).strftime("%Y-%m-%d %H:%M:%S %p")
|
||||
current_time = datetime.now(ZoneInfo(get_timezone())).strftime("%Y-%m-%d %H:%M:%S %p")
|
||||
return render_template("dashboard.html", metrics=default_metrics, current_time=current_time)
|
||||
|
||||
# If we have metrics, use them
|
||||
current_time = datetime.now(ZoneInfo("America/Los_Angeles")).strftime("%Y-%m-%d %H:%M:%S %p")
|
||||
return render_template("dashboard.html", metrics=cached_metrics, current_time=current_time)# api/time endpoint
|
||||
current_time = datetime.now(ZoneInfo(get_timezone())).strftime("%Y-%m-%d %H:%M:%S %p")
|
||||
return render_template("dashboard.html", metrics=cached_metrics, current_time=current_time)
|
||||
|
||||
@app.route("/api/metrics")
|
||||
def api_metrics():
|
||||
@ -467,18 +468,30 @@ def api_metrics():
|
||||
update_metrics_job()
|
||||
return jsonify(cached_metrics)
|
||||
|
||||
@app.route("/api/available_timezones")
|
||||
def available_timezones():
|
||||
"""Return a list of available timezones."""
|
||||
from zoneinfo import available_timezones
|
||||
return jsonify({"timezones": sorted(available_timezones())})
|
||||
|
||||
@app.route('/api/timezone', methods=['GET'])
|
||||
def get_timezone_config():
|
||||
from flask import jsonify
|
||||
from config import get_timezone
|
||||
return jsonify({"timezone": get_timezone()})
|
||||
|
||||
# Add this new route to App.py
|
||||
@app.route("/blocks")
|
||||
def blocks_page():
|
||||
"""Serve the blocks overview page."""
|
||||
current_time = datetime.now(ZoneInfo("America/Los_Angeles")).strftime("%b %d, %Y, %I:%M:%S %p")
|
||||
current_time = datetime.now(ZoneInfo(get_timezone())).strftime("%b %d, %Y, %I:%M:%S %p")
|
||||
return render_template("blocks.html", current_time=current_time)
|
||||
|
||||
# --- Workers Dashboard Route and API ---
|
||||
@app.route("/workers")
|
||||
def workers_dashboard():
|
||||
"""Serve the workers overview dashboard page."""
|
||||
current_time = datetime.now(ZoneInfo("America/Los_Angeles")).strftime("%Y-%m-%d %I:%M:%S %p")
|
||||
current_time = datetime.now(ZoneInfo(get_timezone())).strftime("%Y-%m-%d %I:%M:%S %p")
|
||||
|
||||
# Only get minimal worker stats for initial page load
|
||||
# Client-side JS will fetch the full data via API
|
||||
@ -507,8 +520,8 @@ def api_workers():
|
||||
def api_time():
|
||||
"""API endpoint for server time."""
|
||||
return jsonify({ # correct time
|
||||
"server_timestamp": datetime.now(ZoneInfo("America/Los_Angeles")).isoformat(),
|
||||
"server_start_time": SERVER_START_TIME.astimezone(ZoneInfo("America/Los_Angeles")).isoformat()
|
||||
"server_timestamp": datetime.now(ZoneInfo(get_timezone())).isoformat(),
|
||||
"server_start_time": SERVER_START_TIME.astimezone(ZoneInfo(get_timezone())).isoformat()
|
||||
})
|
||||
|
||||
# --- New Config Endpoints ---
|
||||
@ -586,7 +599,7 @@ def update_config():
|
||||
def health_check():
|
||||
"""Health check endpoint with enhanced system diagnostics."""
|
||||
# Calculate uptime
|
||||
uptime_seconds = (datetime.now(ZoneInfo("America/Los_Angeles")) - SERVER_START_TIME).total_seconds()
|
||||
uptime_seconds = (datetime.now(ZoneInfo(get_timezone())) - SERVER_START_TIME).total_seconds()
|
||||
|
||||
# Get process memory usage
|
||||
try:
|
||||
@ -604,7 +617,7 @@ def health_check():
|
||||
if cached_metrics and cached_metrics.get("server_timestamp"):
|
||||
try:
|
||||
last_update = datetime.fromisoformat(cached_metrics["server_timestamp"])
|
||||
data_age = (datetime.now(ZoneInfo("America/Los_Angeles")) - last_update).total_seconds()
|
||||
data_age = (datetime.now(ZoneInfo(get_timezone())) - last_update).total_seconds()
|
||||
except Exception as e:
|
||||
logging.error(f"Error calculating data age: {e}")
|
||||
|
||||
@ -637,7 +650,7 @@ def health_check():
|
||||
"redis": {
|
||||
"connected": state_manager.redis_client is not None
|
||||
},
|
||||
"timestamp": datetime.now(ZoneInfo("America/Los_Angeles")).isoformat()
|
||||
"timestamp": datetime.now(ZoneInfo(get_timezone())).isoformat()
|
||||
}
|
||||
|
||||
# Log health check if status is not healthy
|
||||
@ -781,7 +794,7 @@ def api_clear_notifications():
|
||||
@app.route("/notifications")
|
||||
def notifications_page():
|
||||
"""Serve the notifications page."""
|
||||
current_time = datetime.now(ZoneInfo("America/Los_Angeles")).strftime("%b %d, %Y, %I:%M:%S %p")
|
||||
current_time = datetime.now(ZoneInfo(get_timezone())).strftime("%b %d, %Y, %I:%M:%S %p")
|
||||
return render_template("notifications.html", current_time=current_time)
|
||||
|
||||
@app.errorhandler(404)
|
||||
|
@ -2,4 +2,5 @@
|
||||
"power_cost": 0.0,
|
||||
"power_usage": 0.0,
|
||||
"wallet": "yourwallethere"
|
||||
"timezone": "America/Los_Angeles" // Add the timezone key
|
||||
}
|
||||
|
25
config.py
25
config.py
@ -19,7 +19,8 @@ def load_config():
|
||||
default_config = {
|
||||
"power_cost": 0.0,
|
||||
"power_usage": 0.0,
|
||||
"wallet": "yourwallethere"
|
||||
"wallet": "yourwallethere",
|
||||
"timezone": "America/Los_Angeles" # Add default timezone
|
||||
}
|
||||
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
@ -35,6 +36,28 @@ def load_config():
|
||||
|
||||
return default_config
|
||||
|
||||
def get_timezone():
|
||||
"""
|
||||
Get the configured timezone with fallback to default.
|
||||
|
||||
Returns:
|
||||
str: Timezone identifier
|
||||
"""
|
||||
# First check environment variable (for Docker)
|
||||
import os
|
||||
env_timezone = os.environ.get("TIMEZONE")
|
||||
if env_timezone:
|
||||
return env_timezone
|
||||
|
||||
# Then check config file
|
||||
config = load_config()
|
||||
timezone = config.get("timezone")
|
||||
if timezone:
|
||||
return timezone
|
||||
|
||||
# Default to Los Angeles
|
||||
return "America/Los_Angeles"
|
||||
|
||||
def save_config(config):
|
||||
"""
|
||||
Save configuration to file.
|
||||
|
@ -12,6 +12,7 @@ import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from models import OceanData, WorkerData, convert_to_ths
|
||||
from config import get_timezone
|
||||
|
||||
class MiningDashboardService:
|
||||
"""Service for fetching and processing mining dashboard data."""
|
||||
@ -136,8 +137,8 @@ class MiningDashboardService:
|
||||
metrics['estimated_rewards_in_window_sats'] = int(round(estimated_rewards_in_window * self.sats_per_btc))
|
||||
|
||||
# --- Add server timestamps to the response in Los Angeles Time ---
|
||||
metrics["server_timestamp"] = datetime.now(ZoneInfo("America/Los_Angeles")).isoformat()
|
||||
metrics["server_start_time"] = datetime.now(ZoneInfo("America/Los_Angeles")).isoformat()
|
||||
metrics["server_timestamp"] = datetime.now(ZoneInfo(get_timezone())).isoformat()
|
||||
metrics["server_start_time"] = datetime.now(ZoneInfo(get_timezone())).isoformat()
|
||||
|
||||
# Log execution time
|
||||
execution_time = time.time() - start_time
|
||||
@ -360,7 +361,7 @@ class MiningDashboardService:
|
||||
try:
|
||||
naive_dt = datetime.strptime(last_share_str, "%Y-%m-%d %H:%M")
|
||||
utc_dt = naive_dt.replace(tzinfo=ZoneInfo("UTC"))
|
||||
la_dt = utc_dt.astimezone(ZoneInfo("America/Los_Angeles"))
|
||||
la_dt = utc_dt.astimezone(ZoneInfo(get_timezone()))
|
||||
data.total_last_share = la_dt.strftime("%Y-%m-%d %I:%M %p")
|
||||
except Exception as e:
|
||||
logging.error(f"Error converting last share time '{last_share_str}': {e}")
|
||||
@ -816,7 +817,7 @@ class MiningDashboardService:
|
||||
'total_earnings': total_earnings,
|
||||
'avg_acceptance_rate': avg_acceptance_rate,
|
||||
'daily_sats': daily_sats,
|
||||
'timestamp': datetime.now(ZoneInfo("America/Los_Angeles")).isoformat()
|
||||
'timestamp': datetime.now(ZoneInfo(get_timezone())).isoformat()
|
||||
}
|
||||
|
||||
logging.info(f"Successfully retrieved worker data: {len(workers)} workers")
|
||||
@ -963,7 +964,7 @@ class MiningDashboardService:
|
||||
'workers_offline': workers_offline,
|
||||
'total_earnings': total_earnings,
|
||||
'avg_acceptance_rate': 99.0,
|
||||
'timestamp': datetime.now(ZoneInfo("America/Los_Angeles")).isoformat()
|
||||
'timestamp': datetime.now(ZoneInfo(get_timezone())).isoformat()
|
||||
}
|
||||
logging.info(f"Successfully retrieved {len(workers)} workers across multiple pages")
|
||||
return result
|
||||
|
@ -31,6 +31,7 @@ class OceanData:
|
||||
blocks_found: str = None
|
||||
total_last_share: str = "N/A"
|
||||
last_block_earnings: str = None
|
||||
pool_fees_percentage: float = None # Added missing attribute
|
||||
|
||||
@dataclass
|
||||
class WorkerData:
|
||||
|
@ -7,6 +7,7 @@ import time
|
||||
import gc
|
||||
import threading
|
||||
import redis
|
||||
from config import get_timezone
|
||||
|
||||
# Global variables for arrow history, legacy hashrate history, and a log of full metrics snapshots.
|
||||
arrow_history = {} # stored per second
|
||||
@ -327,7 +328,7 @@ class StateManager:
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
current_second = datetime.now(ZoneInfo("America/Los_Angeles")).strftime("%H:%M:%S")
|
||||
current_second = datetime.now(ZoneInfo(get_timezone())).strftime("%H:%M:%S")
|
||||
|
||||
with state_lock:
|
||||
for key in arrow_keys:
|
||||
|
@ -537,13 +537,17 @@ const BitcoinMinuteRefresh = (function () {
|
||||
function updateClock() {
|
||||
try {
|
||||
const now = new Date(Date.now() + (serverTimeOffset || 0));
|
||||
let hours = now.getHours();
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||
hours = hours % 12;
|
||||
hours = hours ? hours : 12; // the hour '0' should be '12'
|
||||
const timeString = `${String(hours).padStart(2, '0')}:${minutes}:${seconds} ${ampm}`;
|
||||
// Use the global timezone setting if available
|
||||
const timeZone = window.dashboardTimezone || 'America/Los_Angeles';
|
||||
|
||||
// Format the time in the configured timezone
|
||||
const timeString = now.toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: true,
|
||||
timeZone: timeZone
|
||||
});
|
||||
|
||||
// Update clock in normal view
|
||||
const clockElement = document.getElementById('terminal-clock');
|
||||
|
@ -10,6 +10,40 @@ let isLoading = false;
|
||||
$(document).ready(function() {
|
||||
console.log("Blocks page initialized");
|
||||
|
||||
// Load timezone setting early
|
||||
(function loadTimezoneEarly() {
|
||||
// First try to get from localStorage for instant access
|
||||
try {
|
||||
const storedTimezone = localStorage.getItem('dashboardTimezone');
|
||||
if (storedTimezone) {
|
||||
window.dashboardTimezone = storedTimezone;
|
||||
console.log(`Using cached timezone: ${storedTimezone}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error reading timezone from localStorage:", e);
|
||||
}
|
||||
|
||||
// Then fetch from server to ensure we have the latest setting
|
||||
fetch('/api/timezone')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && data.timezone) {
|
||||
window.dashboardTimezone = data.timezone;
|
||||
console.log(`Set timezone from server: ${data.timezone}`);
|
||||
|
||||
// Cache for future use
|
||||
try {
|
||||
localStorage.setItem('dashboardTimezone', data.timezone);
|
||||
} catch (e) {
|
||||
console.error("Error storing timezone in localStorage:", e);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching timezone:", error);
|
||||
});
|
||||
})();
|
||||
|
||||
// Initialize notification badge
|
||||
initNotificationBadge();
|
||||
|
||||
@ -91,7 +125,8 @@ function formatTimestamp(timestamp) {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: true
|
||||
hour12: true,
|
||||
timeZone: window.dashboardTimezone || 'America/Los_Angeles' // Use global timezone setting
|
||||
};
|
||||
return date.toLocaleString('en-US', options);
|
||||
}
|
||||
|
@ -313,6 +313,27 @@ class ArrowIndicator {
|
||||
// Create the singleton instance
|
||||
const arrowIndicator = new ArrowIndicator();
|
||||
|
||||
// Global timezone configuration
|
||||
let dashboardTimezone = 'America/Los_Angeles'; // Default
|
||||
window.dashboardTimezone = dashboardTimezone; // Make it globally accessible
|
||||
|
||||
// Fetch the configured timezone when the page loads
|
||||
function fetchTimezoneConfig() {
|
||||
fetch('/api/timezone')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && data.timezone) {
|
||||
dashboardTimezone = data.timezone;
|
||||
window.dashboardTimezone = dashboardTimezone; // Make it globally accessible
|
||||
console.log(`Using configured timezone: ${dashboardTimezone}`);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching timezone config:', error));
|
||||
}
|
||||
|
||||
// Call this on page load
|
||||
document.addEventListener('DOMContentLoaded', fetchTimezoneConfig);
|
||||
|
||||
// Global variables
|
||||
let previousMetrics = {};
|
||||
let latestMetrics = null;
|
||||
@ -1006,7 +1027,7 @@ function updateChartWithNormalizedData(chart, data) {
|
||||
// The options define Pacific Time and 12-hour format without AM/PM
|
||||
try {
|
||||
let formattedTime = timeDate.toLocaleTimeString('en-US', {
|
||||
timeZone: 'America/Los_Angeles',
|
||||
timeZone: dashboardTimezone,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
@ -1058,7 +1079,7 @@ function updateChartWithNormalizedData(chart, data) {
|
||||
|
||||
try {
|
||||
currentTime = now.toLocaleTimeString('en-US', {
|
||||
timeZone: 'America/Los_Angeles',
|
||||
timeZone: dashboardTimezone,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
@ -1307,17 +1328,43 @@ function updateUI() {
|
||||
updateElementText("estimated_rewards_in_window_sats", numberWithCommas(data.estimated_rewards_in_window_sats) + " SATS");
|
||||
|
||||
// Update last updated timestamp
|
||||
const now = new Date(Date.now() + serverTimeOffset);
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: true
|
||||
};
|
||||
updateElementHTML("lastUpdated", "<strong>Last Updated:</strong> " + now.toLocaleString('en-US', options) + "<span id='terminal-cursor'></span>");
|
||||
try {
|
||||
// Get the configured timezone with fallback
|
||||
const configuredTimezone = window.dashboardTimezone || 'America/Los_Angeles';
|
||||
|
||||
// Use server timestamp from metrics if available, otherwise use adjusted local time
|
||||
const timestampToUse = latestMetrics && latestMetrics.server_timestamp ?
|
||||
new Date(latestMetrics.server_timestamp) :
|
||||
new Date(Date.now() + (serverTimeOffset || 0));
|
||||
|
||||
// Format with explicit timezone
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: true,
|
||||
timeZone: configuredTimezone // Explicitly set timezone
|
||||
};
|
||||
|
||||
// Update the lastUpdated element
|
||||
updateElementHTML("lastUpdated",
|
||||
"<strong>Last Updated:</strong> " +
|
||||
timestampToUse.toLocaleString('en-US', options) +
|
||||
"<span id='terminal-cursor'></span>");
|
||||
|
||||
console.log(`Last updated timestamp shown using timezone: ${configuredTimezone}`);
|
||||
} catch (error) {
|
||||
console.error("Error formatting last updated timestamp:", error);
|
||||
// Fallback to basic timestamp if there's an error
|
||||
const now = new Date();
|
||||
updateElementHTML("lastUpdated",
|
||||
"<strong>Last Updated:</strong> " +
|
||||
now.toLocaleString() +
|
||||
"<span id='terminal-cursor'></span>");
|
||||
}
|
||||
|
||||
// Update chart with normalized data if it exists
|
||||
if (trendChart) {
|
||||
@ -1386,7 +1433,7 @@ function resetDashboardChart() {
|
||||
// Get current time
|
||||
const now = new Date();
|
||||
let currentTime = now.toLocaleTimeString('en-US', {
|
||||
timeZone: 'America/Los_Angeles',
|
||||
timeZone: dashboardTimezone,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
@ -1434,6 +1481,40 @@ $(document).ready(function () {
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// Load timezone setting early
|
||||
(function loadTimezoneEarly() {
|
||||
// First try to get from localStorage for instant access
|
||||
try {
|
||||
const storedTimezone = localStorage.getItem('dashboardTimezone');
|
||||
if (storedTimezone) {
|
||||
window.dashboardTimezone = storedTimezone;
|
||||
console.log(`Using cached timezone: ${storedTimezone}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error reading timezone from localStorage:", e);
|
||||
}
|
||||
|
||||
// Then fetch from server to ensure we have the latest setting
|
||||
fetch('/api/timezone')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && data.timezone) {
|
||||
window.dashboardTimezone = data.timezone;
|
||||
console.log(`Set timezone from server: ${data.timezone}`);
|
||||
|
||||
// Cache for future use
|
||||
try {
|
||||
localStorage.setItem('dashboardTimezone', data.timezone);
|
||||
} catch (e) {
|
||||
console.error("Error storing timezone in localStorage:", e);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching timezone:", error);
|
||||
});
|
||||
})();
|
||||
|
||||
// Override the manualRefresh function to update the shared lastRefreshTime
|
||||
const originalManualRefresh = manualRefresh;
|
||||
window.manualRefresh = function () {
|
||||
|
@ -7,10 +7,17 @@ const pageSize = 20;
|
||||
let hasMoreNotifications = true;
|
||||
let isLoading = false;
|
||||
|
||||
// Timezone configuration
|
||||
let dashboardTimezone = 'America/Los_Angeles'; // Default
|
||||
window.dashboardTimezone = dashboardTimezone; // Make it globally accessible
|
||||
|
||||
// Initialize when document is ready
|
||||
$(document).ready(() => {
|
||||
console.log("Notification page initializing...");
|
||||
|
||||
// Fetch timezone configuration
|
||||
fetchTimezoneConfig();
|
||||
|
||||
// Set up filter buttons
|
||||
$('.filter-button').click(function () {
|
||||
$('.filter-button').removeClass('active');
|
||||
@ -41,6 +48,34 @@ $(document).ready(() => {
|
||||
setInterval(updateNotificationTimestamps, 30000);
|
||||
});
|
||||
|
||||
// Fetch timezone configuration from server
|
||||
function fetchTimezoneConfig() {
|
||||
return fetch('/api/timezone')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && data.timezone) {
|
||||
dashboardTimezone = data.timezone;
|
||||
window.dashboardTimezone = dashboardTimezone; // Make it globally accessible
|
||||
console.log(`Notifications page using timezone: ${dashboardTimezone}`);
|
||||
|
||||
// Store in localStorage for future use
|
||||
try {
|
||||
localStorage.setItem('dashboardTimezone', dashboardTimezone);
|
||||
} catch (e) {
|
||||
console.error("Error storing timezone in localStorage:", e);
|
||||
}
|
||||
|
||||
// Update all timestamps with the new timezone
|
||||
updateNotificationTimestamps();
|
||||
return dashboardTimezone;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching timezone config:', error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// Load notifications with current filter
|
||||
function loadNotifications() {
|
||||
if (isLoading) return;
|
||||
@ -104,14 +139,36 @@ function refreshNotifications() {
|
||||
}
|
||||
}
|
||||
|
||||
// Update notification timestamps to relative time
|
||||
// This refreshes all timestamps on the page periodically
|
||||
function updateNotificationTimestamps() {
|
||||
$('.notification-item').each(function () {
|
||||
const timestampStr = $(this).attr('data-timestamp');
|
||||
if (timestampStr) {
|
||||
const timestamp = new Date(timestampStr);
|
||||
const relativeTime = formatTimestamp(timestamp);
|
||||
$(this).find('.notification-time').text(relativeTime);
|
||||
try {
|
||||
const timestamp = new Date(timestampStr);
|
||||
|
||||
// Update relative time
|
||||
$(this).find('.notification-time').text(formatTimestamp(timestamp));
|
||||
|
||||
// Update full timestamp with configured timezone
|
||||
if ($(this).find('.full-timestamp').length) {
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: true,
|
||||
timeZone: window.dashboardTimezone || 'America/Los_Angeles'
|
||||
};
|
||||
|
||||
const fullTimestamp = timestamp.toLocaleString('en-US', options);
|
||||
$(this).find('.full-timestamp').text(fullTimestamp);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error updating timestamp:", e, timestampStr);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -190,14 +247,28 @@ function createNotificationElement(notification) {
|
||||
iconElement.addClass('fa-bell');
|
||||
}
|
||||
|
||||
// Append "Z" to indicate UTC if not present
|
||||
let utcTimestampStr = notification.timestamp;
|
||||
if (!utcTimestampStr.endsWith('Z')) {
|
||||
utcTimestampStr += 'Z';
|
||||
}
|
||||
const utcDate = new Date(utcTimestampStr);
|
||||
// Important: Do not append "Z" here, as that can cause timezone issues
|
||||
// Create a date object from the notification timestamp
|
||||
let notificationDate;
|
||||
try {
|
||||
// Parse the timestamp directly without modifications
|
||||
notificationDate = new Date(notification.timestamp);
|
||||
|
||||
// Convert UTC date to Los Angeles time with a timezone name for clarity
|
||||
// Validate the date object - if invalid, try alternative approach
|
||||
if (isNaN(notificationDate.getTime())) {
|
||||
console.warn("Invalid date from notification timestamp, trying alternative format");
|
||||
|
||||
// Try adding Z to make it explicit UTC if not already ISO format
|
||||
if (!notification.timestamp.endsWith('Z') && !notification.timestamp.includes('+')) {
|
||||
notificationDate = new Date(notification.timestamp + 'Z');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing notification date:", e);
|
||||
notificationDate = new Date(); // Fallback to current date
|
||||
}
|
||||
|
||||
// Format the timestamp using the configured timezone
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
@ -205,16 +276,25 @@ function createNotificationElement(notification) {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: true
|
||||
hour12: true,
|
||||
timeZone: window.dashboardTimezone || 'America/Los_Angeles'
|
||||
};
|
||||
const fullTimestamp = utcDate.toLocaleString('en-US', options);
|
||||
|
||||
// Append the full timestamp to the notification message
|
||||
// Format full timestamp with configured timezone
|
||||
let fullTimestamp;
|
||||
try {
|
||||
fullTimestamp = notificationDate.toLocaleString('en-US', options);
|
||||
} catch (e) {
|
||||
console.error("Error formatting timestamp with timezone:", e);
|
||||
fullTimestamp = notificationDate.toLocaleString('en-US'); // Fallback without timezone
|
||||
}
|
||||
|
||||
// Append the message and formatted timestamp
|
||||
const messageWithTimestamp = `${notification.message}<br><span class="full-timestamp">${fullTimestamp}</span>`;
|
||||
element.find('.notification-message').html(messageWithTimestamp);
|
||||
|
||||
// Set metadata for relative time display
|
||||
element.find('.notification-time').text(formatTimestamp(utcDate));
|
||||
element.find('.notification-time').text(formatTimestamp(notificationDate));
|
||||
element.find('.notification-category').text(notification.category);
|
||||
|
||||
// Set up action buttons
|
||||
@ -235,10 +315,21 @@ function createNotificationElement(notification) {
|
||||
return element;
|
||||
}
|
||||
|
||||
// Format timestamp as relative time
|
||||
function formatTimestamp(timestamp) {
|
||||
// Ensure we have a valid date object
|
||||
let dateObj = timestamp;
|
||||
if (!(timestamp instanceof Date) || isNaN(timestamp.getTime())) {
|
||||
try {
|
||||
dateObj = new Date(timestamp);
|
||||
} catch (e) {
|
||||
console.error("Invalid timestamp in formatTimestamp:", e);
|
||||
return "unknown time";
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate time difference in local timezone context
|
||||
const now = new Date();
|
||||
const diffMs = now - timestamp;
|
||||
const diffMs = now - dateObj;
|
||||
const diffSec = Math.floor(diffMs / 1000);
|
||||
const diffMin = Math.floor(diffSec / 60);
|
||||
const diffHour = Math.floor(diffMin / 60);
|
||||
@ -253,17 +344,14 @@ function formatTimestamp(timestamp) {
|
||||
} else if (diffDay < 30) {
|
||||
return `${diffDay}d ago`;
|
||||
} else {
|
||||
// Format as date for older notifications
|
||||
// Format as date for older notifications using configured timezone
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: true
|
||||
timeZone: window.dashboardTimezone || 'America/Los_Angeles'
|
||||
};
|
||||
return timestamp.toLocaleDateString('en-US', options);
|
||||
return dateObj.toLocaleDateString('en-US', options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,40 @@ $(document).ready(function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Load timezone setting early
|
||||
(function loadTimezoneEarly() {
|
||||
// First try to get from localStorage for instant access
|
||||
try {
|
||||
const storedTimezone = localStorage.getItem('dashboardTimezone');
|
||||
if (storedTimezone) {
|
||||
window.dashboardTimezone = storedTimezone;
|
||||
console.log(`Using cached timezone: ${storedTimezone}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error reading timezone from localStorage:", e);
|
||||
}
|
||||
|
||||
// Then fetch from server to ensure we have the latest setting
|
||||
fetch('/api/timezone')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && data.timezone) {
|
||||
window.dashboardTimezone = data.timezone;
|
||||
console.log(`Set timezone from server: ${data.timezone}`);
|
||||
|
||||
// Cache for future use
|
||||
try {
|
||||
localStorage.setItem('dashboardTimezone', data.timezone);
|
||||
} catch (e) {
|
||||
console.error("Error storing timezone in localStorage:", e);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching timezone:", error);
|
||||
});
|
||||
})();
|
||||
|
||||
// Initialize page elements
|
||||
function initializePage() {
|
||||
console.log("Initializing page elements...");
|
||||
@ -321,11 +355,35 @@ function createWorkerCard(worker) {
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Format the last share using the proper method for timezone conversion
|
||||
let formattedLastShare = 'N/A';
|
||||
if (worker.last_share && typeof worker.last_share === 'string') {
|
||||
// This is a more reliable method for timezone conversion
|
||||
try {
|
||||
// The worker.last_share is likely in format "YYYY-MM-DD HH:MM"
|
||||
// We need to consider it as UTC and convert to the configured timezone
|
||||
|
||||
// Create a proper date object, ensuring UTC interpretation
|
||||
const dateWithoutTZ = new Date(worker.last_share + 'Z'); // Adding Z to treat as UTC
|
||||
|
||||
// Format it according to the configured timezone
|
||||
formattedLastShare = dateWithoutTZ.toLocaleString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true,
|
||||
timeZone: window.dashboardTimezone || 'America/Los_Angeles'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Error formatting last share time:", e, worker.last_share);
|
||||
formattedLastShare = worker.last_share; // Fallback to original value
|
||||
}
|
||||
}
|
||||
|
||||
card.append(`
|
||||
<div class="worker-stats">
|
||||
<div class="worker-stats-row">
|
||||
<div class="worker-stats-label">Last Share:</div>
|
||||
<div class="blue-glow">${typeof worker.last_share === 'string' ? worker.last_share.split(' ')[1] || worker.last_share : 'N/A'}</div>
|
||||
<div class="blue-glow">${formattedLastShare}</div>
|
||||
</div>
|
||||
<div class="worker-stats-row">
|
||||
<div class="worker-stats-label">Earnings:</div>
|
||||
|
@ -167,6 +167,58 @@
|
||||
border: 1px solid #ff0000;
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
/* Add these styles to match the timezone dropdown with your other inputs */
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
background-color: #111;
|
||||
border: 1px solid #f7931a;
|
||||
padding: 8px;
|
||||
color: white;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 18px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Add a custom dropdown arrow */
|
||||
.form-group select {
|
||||
background-image: linear-gradient(45deg, transparent 50%, #f7931a 50%), linear-gradient(135deg, #f7931a 50%, transparent 50%);
|
||||
background-position: calc(100% - 15px) 50%, calc(100% - 10px) 50%;
|
||||
background-size: 5px 5px, 5px 5px;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 25px; /* Space for the arrow */
|
||||
}
|
||||
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 5px #f7931a;
|
||||
}
|
||||
|
||||
/* Style the option groups */
|
||||
.form-group select optgroup {
|
||||
background-color: #111;
|
||||
color: #f7931a;
|
||||
font-family: 'VT323', monospace;
|
||||
}
|
||||
|
||||
/* Style the options */
|
||||
.form-group select option {
|
||||
background-color: #222;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Improve mobile appearance */
|
||||
@media (max-width: 768px) {
|
||||
.form-group select {
|
||||
padding: 12px;
|
||||
padding-right: 30px; /* More space for the dropdown arrow */
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -229,6 +281,30 @@
|
||||
<input type="number" id="power-usage" step="50" min="0" placeholder="13450" value="">
|
||||
</div>
|
||||
<div id="form-message"></div>
|
||||
<div class="form-group">
|
||||
<label for="timezone">
|
||||
Timezone
|
||||
<span class="tooltip">
|
||||
?
|
||||
<span class="tooltip-text">Your local timezone for displaying time information</span>
|
||||
</span>
|
||||
</label>
|
||||
<select id="timezone" class="form-control">
|
||||
<optgroup label="Common Timezones">
|
||||
<option value="America/Los_Angeles">Los Angeles (Pacific Time)</option>
|
||||
<option value="America/Denver">Denver (Mountain Time)</option>
|
||||
<option value="America/Chicago">Chicago (Central Time)</option>
|
||||
<option value="America/New_York">New York (Eastern Time)</option>
|
||||
<option value="Europe/London">London (GMT/BST)</option>
|
||||
<option value="Europe/Paris">Paris (Central European Time)</option>
|
||||
<option value="Asia/Tokyo">Tokyo (Japan Standard Time)</option>
|
||||
<option value="Australia/Sydney">Sydney (Australian Eastern Time)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Other Timezones" id="other-timezones">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-secondary" id="use-defaults">Use Defaults</button>
|
||||
<button class="btn" id="save-config">Save & Continue</button>
|
||||
@ -236,6 +312,106 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Add a function to populate all available timezones
|
||||
function populateTimezones() {
|
||||
const otherTimezones = document.getElementById('other-timezones');
|
||||
|
||||
// Common timezone areas to include
|
||||
const commonAreas = [
|
||||
'Africa', 'America', 'Antarctica', 'Asia', 'Atlantic',
|
||||
'Australia', 'Europe', 'Indian', 'Pacific'
|
||||
];
|
||||
|
||||
// Fetch the list of available timezones
|
||||
fetch('/api/available_timezones')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.timezones || !Array.isArray(data.timezones)) {
|
||||
console.error('Invalid timezone data received');
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort timezones and filter to include only common areas
|
||||
const sortedTimezones = data.timezones
|
||||
.filter(tz => commonAreas.some(area => tz.startsWith(area + '/')))
|
||||
.sort();
|
||||
|
||||
// Add options for each timezone (excluding those already in common list)
|
||||
const commonOptions = Array.from(document.querySelectorAll('#timezone optgroup:first-child option'))
|
||||
.map(opt => opt.value);
|
||||
|
||||
sortedTimezones.forEach(tz => {
|
||||
if (!commonOptions.includes(tz)) {
|
||||
const option = document.createElement('option');
|
||||
option.value = tz;
|
||||
option.textContent = tz.replace('_', ' ');
|
||||
otherTimezones.appendChild(option);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error fetching timezones:', error));
|
||||
}
|
||||
|
||||
// Call this when the page loads
|
||||
document.addEventListener('DOMContentLoaded', populateTimezones);
|
||||
|
||||
// Load the current timezone from configuration
|
||||
function loadTimezoneFromConfig() {
|
||||
if (currentConfig && currentConfig.timezone) {
|
||||
const timezoneSelect = document.getElementById('timezone');
|
||||
|
||||
// First, check if the option exists
|
||||
let optionExists = false;
|
||||
for (let i = 0; i < timezoneSelect.options.length; i++) {
|
||||
if (timezoneSelect.options[i].value === currentConfig.timezone) {
|
||||
timezoneSelect.selectedIndex = i;
|
||||
optionExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the option doesn't exist yet (might be in the 'other' group being loaded)
|
||||
// set a data attribute to select it when options are loaded
|
||||
if (!optionExists) {
|
||||
timezoneSelect.setAttribute('data-select-value', currentConfig.timezone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this after loading config
|
||||
loadConfig().then(() => {
|
||||
loadTimezoneFromConfig();
|
||||
});
|
||||
|
||||
// Update the saveConfig function to include timezone
|
||||
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 updatedConfig = {
|
||||
wallet: wallet || currentConfig.wallet,
|
||||
power_cost: powerCost,
|
||||
power_usage: powerUsage,
|
||||
timezone: timezone
|
||||
};
|
||||
|
||||
return fetch('/api/config', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(updatedConfig)
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save configuration');
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
}
|
||||
|
||||
// Debug logging
|
||||
function updateDebug(message) {
|
||||
document.getElementById('debug-info').textContent = message;
|
||||
@ -271,38 +447,43 @@
|
||||
|
||||
// Replace the current loadConfig function with this improved version
|
||||
function loadConfig() {
|
||||
// Always make a fresh request to get the latest config
|
||||
fetch('/api/config?nocache=' + new Date().getTime()) // Add cache-busting parameter
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load configuration: ' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log("Loaded configuration:", data);
|
||||
currentConfig = data;
|
||||
// Return a promise that resolves when the config is loaded
|
||||
return new Promise((resolve, reject) => {
|
||||
// Always make a fresh request to get the latest config
|
||||
fetch('/api/config?nocache=' + new Date().getTime()) // Add cache-busting parameter
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load configuration: ' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log("Loaded configuration:", data);
|
||||
currentConfig = data;
|
||||
|
||||
// After loading, always update the form fields with the latest values
|
||||
document.getElementById('wallet-address').value = currentConfig.wallet || "";
|
||||
document.getElementById('power-cost').value = currentConfig.power_cost || "";
|
||||
document.getElementById('power-usage').value = currentConfig.power_usage || "";
|
||||
configLoaded = true;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Error loading config:", err);
|
||||
// Use default values if loading fails
|
||||
currentConfig = {
|
||||
wallet: "yourwallethere",
|
||||
power_cost: 0.0,
|
||||
power_usage: 0.0
|
||||
};
|
||||
// After loading, always update the form fields with the latest values
|
||||
document.getElementById('wallet-address').value = currentConfig.wallet || "";
|
||||
document.getElementById('power-cost').value = currentConfig.power_cost || "";
|
||||
document.getElementById('power-usage').value = currentConfig.power_usage || "";
|
||||
configLoaded = true;
|
||||
resolve(currentConfig); // Resolve the promise with the config data
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Error loading config:", err);
|
||||
// Use default values if loading fails
|
||||
currentConfig = {
|
||||
wallet: "yourwallethere",
|
||||
power_cost: 0.0,
|
||||
power_usage: 0.0
|
||||
};
|
||||
|
||||
// Still update the form with default values
|
||||
document.getElementById('wallet-address').value = currentConfig.wallet || "";
|
||||
document.getElementById('power-cost').value = currentConfig.power_cost || "";
|
||||
document.getElementById('power-usage').value = currentConfig.power_usage || "";
|
||||
});
|
||||
// Still update the form with default values
|
||||
document.getElementById('wallet-address').value = currentConfig.wallet || "";
|
||||
document.getElementById('power-cost').value = currentConfig.power_cost || "";
|
||||
document.getElementById('power-usage').value = currentConfig.power_usage || "";
|
||||
resolve(currentConfig); // Resolve the promise with the default config
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Also update the save button event handler to reload the config after saving
|
||||
@ -333,11 +514,13 @@
|
||||
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 updatedConfig = {
|
||||
wallet: wallet || currentConfig.wallet,
|
||||
wallet: wallet || (currentConfig ? currentConfig.wallet : ""),
|
||||
power_cost: powerCost,
|
||||
power_usage: powerUsage
|
||||
power_usage: powerUsage,
|
||||
timezone: timezone
|
||||
};
|
||||
|
||||
return fetch('/api/config', {
|
||||
|
@ -5,6 +5,7 @@ import logging
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
from config import get_timezone
|
||||
|
||||
class WorkerService:
|
||||
"""Service for retrieving and managing worker data."""
|
||||
@ -49,7 +50,7 @@ class WorkerService:
|
||||
"daily_sats": 0,
|
||||
"avg_acceptance_rate": 0.0,
|
||||
"hashrate_history": [],
|
||||
"timestamp": datetime.now(ZoneInfo("America/Los_Angeles")).isoformat()
|
||||
"timestamp": datetime.now(ZoneInfo(get_timezone())).isoformat()
|
||||
}
|
||||
|
||||
def get_workers_data(self, cached_metrics, force_refresh=False):
|
||||
@ -286,7 +287,7 @@ class WorkerService:
|
||||
dict: Default worker data
|
||||
"""
|
||||
is_online = status == "online"
|
||||
current_time = datetime.now(ZoneInfo("America/Los_Angeles"))
|
||||
current_time = datetime.now(ZoneInfo(get_timezone()))
|
||||
|
||||
# Generate some reasonable hashrate and other values
|
||||
hashrate = round(random.uniform(50, 100), 2) if is_online else 0
|
||||
@ -438,7 +439,7 @@ class WorkerService:
|
||||
"daily_sats": daily_sats, # Fixed daily_sats value
|
||||
"avg_acceptance_rate": 98.8, # Default value
|
||||
"hashrate_history": hashrate_history,
|
||||
"timestamp": datetime.now(ZoneInfo("America/Los_Angeles")).isoformat()
|
||||
"timestamp": datetime.now(ZoneInfo(get_timezone())).isoformat()
|
||||
}
|
||||
|
||||
# Update cache
|
||||
@ -482,7 +483,7 @@ class WorkerService:
|
||||
avg_hashrate = max(0.5, total_hashrate / online_count if online_count > 0 else 0)
|
||||
|
||||
workers = []
|
||||
current_time = datetime.now(ZoneInfo("America/Los_Angeles"))
|
||||
current_time = datetime.now(ZoneInfo(get_timezone()))
|
||||
|
||||
# Default total unpaid earnings if not provided
|
||||
if total_unpaid_earnings is None or total_unpaid_earnings <= 0:
|
||||
@ -635,7 +636,7 @@ class WorkerService:
|
||||
avg_hashrate = max(0.5, total_hashrate / online_count if online_count > 0 else 0)
|
||||
|
||||
workers = []
|
||||
current_time = datetime.now(ZoneInfo("America/Los_Angeles"))
|
||||
current_time = datetime.now(ZoneInfo(get_timezone()))
|
||||
|
||||
# Default total unpaid earnings if not provided
|
||||
if total_unpaid_earnings is None or total_unpaid_earnings <= 0:
|
||||
|
Loading…
Reference in New Issue
Block a user