mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-25 18:40:06 -04:00
Adding server uptime to status pages.
This commit is contained in:
parent
b408d11ea1
commit
d4f1cfb859
6 changed files with 107 additions and 3 deletions
|
|
@ -96,6 +96,27 @@ function IOWaitStatistic({percent_iowait, ...props}) {
|
|||
return <Statistic label='IO Wait %' value={percent_iowait.toFixed(1)} {...props}/>
|
||||
}
|
||||
|
||||
function formatUptime(seconds) {
|
||||
if (!seconds || seconds < 0) return '?';
|
||||
|
||||
const days = Math.floor(seconds / 86400);
|
||||
const hours = Math.floor((seconds % 86400) / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
|
||||
if (days > 0) {
|
||||
return `${days}d ${hours}h`;
|
||||
} else if (hours > 0) {
|
||||
return `${hours}h ${minutes}m`;
|
||||
} else {
|
||||
return `${minutes}m`;
|
||||
}
|
||||
}
|
||||
|
||||
function UptimeStatistic({uptime_seconds, ...props}) {
|
||||
const value = formatUptime(uptime_seconds);
|
||||
return <Statistic label='Uptime' value={value} {...props}/>
|
||||
}
|
||||
|
||||
export function BandwidthProgress({label = '', bytes, maxBytes, ...props}) {
|
||||
// Gigabit by default.
|
||||
maxBytes = maxBytes || 125_000_000;
|
||||
|
|
@ -216,9 +237,10 @@ export function StatusPage() {
|
|||
let memoryPercent;
|
||||
let memorySize;
|
||||
let percent_iowait;
|
||||
let uptime_seconds;
|
||||
|
||||
if (status && status['cpu_stats']) {
|
||||
const {cpu_stats, load_stats, memory_stats, processes_stats, iostat_stats} = status;
|
||||
const {cpu_stats, load_stats, memory_stats, processes_stats, iostat_stats, uptime_stats} = status;
|
||||
percent = cpu_stats['percent'];
|
||||
cores = cpu_stats['cores'] || '?';
|
||||
temperature = cpu_stats['temperature'];
|
||||
|
|
@ -241,6 +263,11 @@ export function StatusPage() {
|
|||
if (iostat_stats) {
|
||||
percent_iowait = iostat_stats['percent_iowait'];
|
||||
}
|
||||
|
||||
// Extract uptime from uptime stats
|
||||
if (uptime_stats) {
|
||||
uptime_seconds = uptime_stats['uptime_seconds'];
|
||||
}
|
||||
}
|
||||
|
||||
const SizedHeader = ({children, sizeMobile = 'h1', sizeTablet = 'h2'}) => {
|
||||
|
|
@ -277,6 +304,7 @@ export function StatusPage() {
|
|||
<LoadStatistic label='5 Minute Load' value={minute_5} cores={cores}/>
|
||||
<LoadStatistic label='15 Minute Load' value={minute_15} cores={cores}/>
|
||||
<IOWaitStatistic percent_iowait={percent_iowait}/>
|
||||
<UptimeStatistic uptime_seconds={uptime_seconds}/>
|
||||
</Statistic.Group>
|
||||
</Segment>
|
||||
</Media>
|
||||
|
|
@ -296,6 +324,7 @@ export function StatusPage() {
|
|||
<LoadStatistic label='5 Min.' value={minute_5} cores={cores}/>
|
||||
<LoadStatistic label='15 Min.' value={minute_15} cores={cores}/>
|
||||
<IOWaitStatistic percent_iowait={percent_iowait}/>
|
||||
<UptimeStatistic uptime_seconds={uptime_seconds}/>
|
||||
</Statistic.Group>
|
||||
</Segment>
|
||||
</Media>
|
||||
|
|
|
|||
|
|
@ -154,3 +154,14 @@ async def get_iostat(request: Request):
|
|||
# Fallback to direct call
|
||||
from controller.lib.status import get_iostat_status
|
||||
return JSONResponse(content=get_iostat_status())
|
||||
|
||||
|
||||
@router.get("/uptime")
|
||||
async def get_uptime(request: Request):
|
||||
"""Get system uptime information."""
|
||||
cached = get_cached_status(request)
|
||||
if cached and cached.get("uptime_stats"):
|
||||
return JSONResponse(content=cached["uptime_stats"])
|
||||
# Fallback to direct call
|
||||
from controller.lib.status import get_uptime_status
|
||||
return JSONResponse(content=get_uptime_status())
|
||||
|
|
|
|||
|
|
@ -341,6 +341,25 @@ def get_processes_status() -> list[dict]:
|
|||
return processes
|
||||
|
||||
|
||||
def get_uptime_status() -> dict:
|
||||
"""
|
||||
Get system uptime information.
|
||||
|
||||
Returns format:
|
||||
dict with keys: boot_time (ISO timestamp), uptime_seconds (int)
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
boot_time = psutil.boot_time()
|
||||
boot_datetime = datetime.fromtimestamp(boot_time)
|
||||
uptime_seconds = int((datetime.now() - boot_datetime).total_seconds())
|
||||
|
||||
return {
|
||||
"boot_time": boot_datetime.isoformat(),
|
||||
"uptime_seconds": uptime_seconds,
|
||||
}
|
||||
|
||||
|
||||
IGNORED_DISK_NAMES = ('loop', 'ram')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from controller.lib.status import (
|
|||
get_network_status,
|
||||
get_power_status,
|
||||
get_processes_status,
|
||||
get_uptime_status,
|
||||
)
|
||||
|
||||
# Type alias for cached status dict
|
||||
|
|
@ -35,7 +36,7 @@ async def collect_all_status() -> CachedStatus:
|
|||
"""
|
||||
# Run all status collections concurrently using asyncio.to_thread
|
||||
# since the status functions are synchronous (use psutil)
|
||||
cpu, memory, load, drives, network, power, iostat, processes, disk_bandwidth = await asyncio.gather(
|
||||
cpu, memory, load, drives, network, power, iostat, processes, disk_bandwidth, uptime = await asyncio.gather(
|
||||
asyncio.to_thread(get_cpu_status),
|
||||
asyncio.to_thread(get_memory_status),
|
||||
asyncio.to_thread(get_load_status),
|
||||
|
|
@ -45,6 +46,7 @@ async def collect_all_status() -> CachedStatus:
|
|||
asyncio.to_thread(get_iostat_status),
|
||||
asyncio.to_thread(get_processes_status),
|
||||
asyncio.to_thread(get_disk_bandwidth_status),
|
||||
asyncio.to_thread(get_uptime_status),
|
||||
return_exceptions=True,
|
||||
)
|
||||
|
||||
|
|
@ -59,6 +61,7 @@ async def collect_all_status() -> CachedStatus:
|
|||
"iostat_stats": iostat if not isinstance(iostat, Exception) else None,
|
||||
"processes_stats": processes if not isinstance(processes, Exception) else [],
|
||||
"disk_bandwidth_stats": disk_bandwidth if not isinstance(disk_bandwidth, Exception) else {},
|
||||
"uptime_stats": uptime if not isinstance(uptime, Exception) else None,
|
||||
"last_status": datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ from controller.lib.status import (
|
|||
get_load_status,
|
||||
get_memory_status,
|
||||
get_primary_drive_status,
|
||||
get_uptime_status,
|
||||
)
|
||||
from controller.lib.status_worker import status_worker_loop
|
||||
from controller.lib.systemd import get_all_services_status
|
||||
|
|
@ -118,6 +119,7 @@ async def dashboard(request: Request):
|
|||
memory = cached.get("memory_stats") or {}
|
||||
load = cached.get("load_stats") or {}
|
||||
iostat = cached.get("iostat_stats") or {}
|
||||
uptime = cached.get("uptime_stats") or {}
|
||||
drives = cached.get("drives_stats") or []
|
||||
primary_drive = None
|
||||
for drive in drives:
|
||||
|
|
@ -130,6 +132,7 @@ async def dashboard(request: Request):
|
|||
memory = get_memory_status()
|
||||
load = get_load_status()
|
||||
iostat = get_iostat_status()
|
||||
uptime = get_uptime_status()
|
||||
primary_drive = get_primary_drive_status()
|
||||
|
||||
# Format storage data
|
||||
|
|
@ -141,6 +144,15 @@ async def dashboard(request: Request):
|
|||
"free_gb": round(free_bytes / (1024 ** 3), 1),
|
||||
}
|
||||
|
||||
# Format uptime
|
||||
uptime_seconds = uptime.get("uptime_seconds", 0)
|
||||
if uptime_seconds:
|
||||
days = uptime_seconds // 86400
|
||||
hours = (uptime_seconds % 86400) // 3600
|
||||
uptime_formatted = f"{days}d {hours}h" if days > 0 else f"{hours}h {(uptime_seconds % 3600) // 60}m"
|
||||
else:
|
||||
uptime_formatted = "--"
|
||||
|
||||
# Get service status
|
||||
if is_docker_mode():
|
||||
if can_manage_containers():
|
||||
|
|
@ -175,6 +187,10 @@ async def dashboard(request: Request):
|
|||
"iostat": {
|
||||
"percent_iowait": iostat.get("percent_iowait"),
|
||||
},
|
||||
"uptime": {
|
||||
"formatted": uptime_formatted,
|
||||
"seconds": uptime_seconds,
|
||||
},
|
||||
|
||||
# Real service status
|
||||
"services": services,
|
||||
|
|
|
|||
|
|
@ -633,6 +633,11 @@
|
|||
<p class="value" id="iowait-value">{% if iostat and iostat.percent_iowait is not none %}{{ iostat.percent_iowait }}{% else %}--{% endif %}%</p>
|
||||
<p class="sub">disk waiting</p>
|
||||
</div>
|
||||
<div class="status-card">
|
||||
<h3>Uptime</h3>
|
||||
<p class="value" id="uptime-value">{{ uptime.formatted|default('--') }}</p>
|
||||
<p class="sub">since boot</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -1472,15 +1477,30 @@
|
|||
|
||||
// ===== PERIODIC UPDATES =====
|
||||
|
||||
function formatUptime(seconds) {
|
||||
if (!seconds || seconds < 0) return '--';
|
||||
const days = Math.floor(seconds / 86400);
|
||||
const hours = Math.floor((seconds % 86400) / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
if (days > 0) {
|
||||
return `${days}d ${hours}h`;
|
||||
} else if (hours > 0) {
|
||||
return `${hours}h ${minutes}m`;
|
||||
} else {
|
||||
return `${minutes}m`;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSystemStatus() {
|
||||
try {
|
||||
// Fetch all stats endpoints in parallel
|
||||
const [cpu, memory, load, primaryDrive, iostat] = await Promise.all([
|
||||
const [cpu, memory, load, primaryDrive, iostat, uptime] = await Promise.all([
|
||||
apiCall('api/stats/cpu'),
|
||||
apiCall('api/stats/memory'),
|
||||
apiCall('api/stats/load'),
|
||||
apiCall('api/stats/drives/primary'),
|
||||
apiCall('api/stats/iostat'),
|
||||
apiCall('api/stats/uptime'),
|
||||
]);
|
||||
|
||||
// Update CPU card
|
||||
|
|
@ -1538,6 +1558,12 @@
|
|||
iowaitValue.style.color = '#4ecca3';
|
||||
}
|
||||
}
|
||||
|
||||
// Update Uptime card
|
||||
const uptimeValue = document.getElementById('uptime-value');
|
||||
if (uptimeValue) {
|
||||
uptimeValue.textContent = formatUptime(uptime.uptime_seconds);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to update system status:', e);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue