Some checks failed
Integration Tests / integration-tests (push) Failing after 20s
Integration Tests / performance-tests (push) Has been skipped
Service Adapters (Python FastAPI) / test (3.11) (push) Failing after 23s
Frontend (React) / test (20) (push) Failing after 1m3s
Frontend (React) / build (push) Has been skipped
Frontend (React) / lighthouse (push) Has been skipped
Service Adapters (Python FastAPI) / test (3.12) (push) Failing after 23s
Service Adapters (Python FastAPI) / test (3.13) (push) Failing after 20s
Service Adapters (Python FastAPI) / build (push) Has been skipped
### Summary of Changes - Removed proxy configuration in `rsbuild.config.js` as the API Gateway is not running. - Added smooth transitions and gentle loading overlays in CSS for improved user experience during data loading. - Updated `Dashboard` component to conditionally display loading spinner and gentle loading overlay based on data fetching state. - Enhanced `useOfflineAwareServiceStatus` and `useOfflineAwareSystemData` hooks to manage loading states and service status more effectively. - Increased refresh intervals for service status and system data to reduce API call frequency. ### Expected Results - Improved user experience with smoother loading transitions and better feedback during data refreshes. - Enhanced handling of service status checks, providing clearer information when services are unavailable. - Streamlined code for managing loading states, making it easier to maintain and extend in the future.
200 lines
5.6 KiB
Python
200 lines
5.6 KiB
Python
"""
|
|
Time Formatting Utilities
|
|
|
|
This module provides utilities for formatting time durations and timestamps
|
|
into human-readable formats for the frontend.
|
|
"""
|
|
|
|
import re
|
|
from datetime import datetime, timezone
|
|
from typing import Optional, Union
|
|
|
|
|
|
def format_uptime_for_frontend(uptime_value: Optional[str]) -> str:
|
|
"""
|
|
Format uptime value for frontend display in "Xd Xh Xm" format.
|
|
|
|
Args:
|
|
uptime_value: Raw uptime value (timestamp, epoch, duration string, etc.)
|
|
|
|
Returns:
|
|
Formatted uptime string like "2d 5h 30m" or "0d 0h" if invalid
|
|
"""
|
|
if not uptime_value:
|
|
return "0d 0h"
|
|
|
|
try:
|
|
# Try to parse as timestamp (ISO format)
|
|
if _is_timestamp(uptime_value):
|
|
return _format_timestamp_uptime(uptime_value)
|
|
|
|
# Try to parse as epoch timestamp
|
|
if _is_epoch(uptime_value):
|
|
return _format_epoch_uptime(uptime_value)
|
|
|
|
# Try to parse as duration string (e.g., "2h 30m", "5d 2h 15m")
|
|
if _is_duration_string(uptime_value):
|
|
return _format_duration_string(uptime_value)
|
|
|
|
# Try to parse as numeric seconds
|
|
if _is_numeric_seconds(uptime_value):
|
|
return _format_seconds_uptime(float(uptime_value))
|
|
|
|
# If none of the above, return as-is or default
|
|
return uptime_value if len(uptime_value) < 50 else "0d 0h"
|
|
|
|
except Exception:
|
|
return "0d 0h"
|
|
|
|
|
|
def _is_timestamp(value: str) -> bool:
|
|
"""Check if value is an ISO timestamp."""
|
|
try:
|
|
datetime.fromisoformat(value.replace('Z', '+00:00'))
|
|
return True
|
|
except (ValueError, AttributeError):
|
|
return False
|
|
|
|
|
|
def _is_epoch(value: str) -> bool:
|
|
"""Check if value is an epoch timestamp."""
|
|
try:
|
|
float(value)
|
|
return len(value) >= 10 and float(value) > 1000000000 # Reasonable epoch range
|
|
except (ValueError, TypeError):
|
|
return False
|
|
|
|
|
|
def _is_duration_string(value: str) -> bool:
|
|
"""Check if value is a duration string like '2h 30m' or '5d 2h 15m'."""
|
|
# Look for patterns like "2h 30m", "5d 2h 15m", "1d 2h 3m 4s"
|
|
pattern = r'^\d+[dhms]\s*(\d+[dhms]\s*)*$'
|
|
return bool(re.match(pattern, value.strip()))
|
|
|
|
|
|
def _is_numeric_seconds(value: str) -> bool:
|
|
"""Check if value is numeric seconds."""
|
|
try:
|
|
float(value)
|
|
return True
|
|
except (ValueError, TypeError):
|
|
return False
|
|
|
|
|
|
def _format_timestamp_uptime(timestamp: str) -> str:
|
|
"""Format timestamp uptime (time since timestamp)."""
|
|
try:
|
|
# Parse timestamp
|
|
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
|
|
if dt.tzinfo is None:
|
|
dt = dt.replace(tzinfo=timezone.utc)
|
|
|
|
# Calculate time difference
|
|
now = datetime.now(timezone.utc)
|
|
diff = now - dt
|
|
|
|
return _format_timedelta(diff)
|
|
except Exception:
|
|
return "0d 0h"
|
|
|
|
|
|
def _format_epoch_uptime(epoch_str: str) -> str:
|
|
"""Format epoch timestamp uptime."""
|
|
try:
|
|
epoch = float(epoch_str)
|
|
dt = datetime.fromtimestamp(epoch, tz=timezone.utc)
|
|
now = datetime.now(timezone.utc)
|
|
diff = now - dt
|
|
|
|
return _format_timedelta(diff)
|
|
except Exception:
|
|
return "0d 0h"
|
|
|
|
|
|
def _format_duration_string(duration: str) -> str:
|
|
"""Format duration string to standardized format."""
|
|
try:
|
|
# Parse duration string like "2h 30m" or "5d 2h 15m"
|
|
total_seconds = _parse_duration_string(duration)
|
|
return _format_seconds_uptime(total_seconds)
|
|
except Exception:
|
|
return "0d 0h"
|
|
|
|
|
|
def _format_seconds_uptime(seconds: float) -> str:
|
|
"""Format seconds to "Xd Xh Xm" format."""
|
|
return _format_timedelta_from_seconds(seconds)
|
|
|
|
|
|
def _parse_duration_string(duration: str) -> float:
|
|
"""Parse duration string to total seconds."""
|
|
total_seconds = 0
|
|
|
|
# Extract days
|
|
days_match = re.search(r'(\d+)d', duration)
|
|
if days_match:
|
|
total_seconds += int(days_match.group(1)) * 86400
|
|
|
|
# Extract hours
|
|
hours_match = re.search(r'(\d+)h', duration)
|
|
if hours_match:
|
|
total_seconds += int(hours_match.group(1)) * 3600
|
|
|
|
# Extract minutes
|
|
minutes_match = re.search(r'(\d+)m', duration)
|
|
if minutes_match:
|
|
total_seconds += int(minutes_match.group(1)) * 60
|
|
|
|
# Extract seconds
|
|
seconds_match = re.search(r'(\d+)s', duration)
|
|
if seconds_match:
|
|
total_seconds += int(seconds_match.group(1))
|
|
|
|
return total_seconds
|
|
|
|
|
|
def _format_timedelta(td) -> str:
|
|
"""Format timedelta to "Xd Xh Xm" format."""
|
|
total_seconds = int(td.total_seconds())
|
|
return _format_timedelta_from_seconds(total_seconds)
|
|
|
|
|
|
def _format_timedelta_from_seconds(total_seconds: Union[int, float]) -> str:
|
|
"""Format total seconds to "Xd Xh Xm" format."""
|
|
if total_seconds < 0:
|
|
return "0d 0h"
|
|
|
|
# Convert to int to avoid decimal places
|
|
total_seconds = int(total_seconds)
|
|
|
|
days = total_seconds // 86400
|
|
hours = (total_seconds % 86400) // 3600
|
|
minutes = (total_seconds % 3600) // 60
|
|
|
|
# Only show days if > 0
|
|
if days > 0:
|
|
return f"{days}d {hours}h {minutes}m"
|
|
elif hours > 0:
|
|
return f"{hours}h {minutes}m"
|
|
else:
|
|
return f"{minutes}m"
|
|
|
|
|
|
def format_response_time(seconds: Optional[float]) -> str:
|
|
"""
|
|
Format response time for display.
|
|
|
|
Args:
|
|
seconds: Response time in seconds
|
|
|
|
Returns:
|
|
Formatted response time string
|
|
"""
|
|
if seconds is None:
|
|
return "N/A"
|
|
|
|
if seconds < 1:
|
|
return f"{seconds * 1000:.0f}ms"
|
|
else:
|
|
return f"{seconds:.2f}s"
|