mirror of
https://github.com/kunkunsh/kunkun-ext-neohtop.git
synced 2025-04-04 09:46:43 +00:00
adding more stats, network traffic and disk usage + few tweaks
This commit is contained in:
parent
33628d77ce
commit
b179b0aa98
@ -1,13 +1,42 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use sysinfo::{CpuExt, SystemExt, ProcessExt, System, PidExt, ProcessStatus};
|
||||
use sysinfo::{
|
||||
System,
|
||||
ProcessStatus,
|
||||
NetworksExt,
|
||||
NetworkExt,
|
||||
DiskExt,
|
||||
SystemExt,
|
||||
CpuExt,
|
||||
ProcessExt,
|
||||
PidExt,
|
||||
};
|
||||
use tauri::State;
|
||||
use std::sync::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::time::{Instant};
|
||||
|
||||
struct AppState {
|
||||
sys: Mutex<System>,
|
||||
process_cache: Mutex<HashMap<u32, ProcessStaticInfo>>,
|
||||
last_network_update: Mutex<(Instant, u64, u64)>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> Self {
|
||||
let mut sys = System::new();
|
||||
sys.refresh_all();
|
||||
|
||||
// Initialize network stats
|
||||
let initial_rx = sys.networks().iter().map(|(_, data)| data.total_received()).sum();
|
||||
let initial_tx = sys.networks().iter().map(|(_, data)| data.total_transmitted()).sum();
|
||||
|
||||
Self {
|
||||
sys: Mutex::new(sys),
|
||||
process_cache: Mutex::new(HashMap::new()),
|
||||
last_network_update: Mutex::new((Instant::now(), initial_rx, initial_tx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -31,14 +60,19 @@ struct ProcessInfo {
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct SystemStats {
|
||||
cpu_usage: Vec<f32>,
|
||||
memory_total: u64,
|
||||
memory_used: u64,
|
||||
memory_free:u64,
|
||||
memory_cached: u64,
|
||||
uptime: u64,
|
||||
load_avg: [f64; 3],
|
||||
pub struct SystemStats {
|
||||
pub cpu_usage: Vec<f32>,
|
||||
pub memory_total: u64,
|
||||
pub memory_used: u64,
|
||||
pub memory_free: u64,
|
||||
pub memory_cached: u64,
|
||||
pub uptime: u64,
|
||||
pub load_avg: [f64; 3],
|
||||
pub network_rx_bytes: u64,
|
||||
pub network_tx_bytes: u64,
|
||||
pub disk_total_bytes: u64,
|
||||
pub disk_used_bytes: u64,
|
||||
pub disk_free_bytes: u64,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -50,7 +84,10 @@ async fn get_processes(state: State<'_, AppState>) -> Result<(Vec<ProcessInfo>,
|
||||
{
|
||||
let mut sys = state.sys.lock().map_err(|_| "Failed to lock system state")?;
|
||||
sys.refresh_all();
|
||||
|
||||
sys.refresh_networks();
|
||||
sys.refresh_disks_list();
|
||||
sys.refresh_disks();
|
||||
|
||||
// Collect all the process data we need while holding sys lock
|
||||
processes_data = sys
|
||||
.processes()
|
||||
@ -69,22 +106,43 @@ async fn get_processes(state: State<'_, AppState>) -> Result<(Vec<ProcessInfo>,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get system stats while we still have the sys lock
|
||||
let cpu_usage: Vec<f32> = sys.cpus().iter().map(|cpu| cpu.cpu_usage()).collect();
|
||||
let load_avg = sys.load_average();
|
||||
let memory_total = sys.total_memory();
|
||||
let memory_used = sys.used_memory();
|
||||
let memory_free = memory_total - memory_used;
|
||||
let memory_cached = memory_total - (memory_used + memory_free); // Estimated
|
||||
// Calculate total network I/O
|
||||
let mut last_update = state.last_network_update.lock().map_err(|_| "Failed to lock network state")?;
|
||||
let elapsed = last_update.0.elapsed().as_secs_f64();
|
||||
let current_time = Instant::now();
|
||||
|
||||
let current_rx: u64 = sys.networks().iter().map(|(_, data)| data.total_received()).sum();
|
||||
let current_tx: u64 = sys.networks().iter().map(|(_, data)| data.total_transmitted()).sum();
|
||||
|
||||
let network_stats = (
|
||||
((current_rx - last_update.1) as f64 / elapsed) as u64,
|
||||
((current_tx - last_update.2) as f64 / elapsed) as u64,
|
||||
);
|
||||
|
||||
*last_update = (current_time, current_rx, current_tx);
|
||||
|
||||
// Calculate total disk usage
|
||||
let disk_stats = sys.disks().iter().fold((0, 0, 0), |acc, disk| {
|
||||
(
|
||||
acc.0 + disk.total_space(),
|
||||
acc.1 + disk.total_space() - disk.available_space(),
|
||||
acc.2 + disk.available_space()
|
||||
)
|
||||
});
|
||||
|
||||
system_stats = SystemStats {
|
||||
cpu_usage,
|
||||
memory_total,
|
||||
memory_used,
|
||||
memory_free,
|
||||
memory_cached,
|
||||
cpu_usage: sys.cpus().iter().map(|cpu| cpu.cpu_usage()).collect(),
|
||||
memory_total: sys.total_memory(),
|
||||
memory_used: sys.used_memory(),
|
||||
memory_free: sys.total_memory() - sys.used_memory(),
|
||||
memory_cached: sys.total_memory() - (sys.used_memory() + (sys.total_memory() - sys.used_memory())),
|
||||
uptime: sys.uptime(),
|
||||
load_avg: [load_avg.one, load_avg.five, load_avg.fifteen],
|
||||
load_avg: [sys.load_average().one, sys.load_average().five, sys.load_average().fifteen],
|
||||
network_rx_bytes: network_stats.0,
|
||||
network_tx_bytes: network_stats.1,
|
||||
disk_total_bytes: disk_stats.0,
|
||||
disk_used_bytes: disk_stats.1,
|
||||
disk_free_bytes: disk_stats.2,
|
||||
};
|
||||
} // sys lock is automatically dropped here
|
||||
|
||||
@ -139,10 +197,7 @@ async fn kill_process(pid: u32, state: State<'_, AppState>) -> Result<bool, Stri
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.manage(AppState {
|
||||
sys: Mutex::new(System::new_all()),
|
||||
process_cache: Mutex::new(HashMap::new()),
|
||||
})
|
||||
.manage(AppState::new())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_processes,
|
||||
kill_process
|
||||
|
@ -38,12 +38,12 @@
|
||||
"windows": [
|
||||
{
|
||||
"fullscreen": false,
|
||||
"height": 800,
|
||||
"height": 900,
|
||||
"resizable": true,
|
||||
"title": "NeoHtop",
|
||||
"width": 1280,
|
||||
"minWidth": 1180,
|
||||
"minHeight": 600
|
||||
"minWidth": 1280,
|
||||
"minHeight": 900
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
faMicrochip,
|
||||
faMemory,
|
||||
faServer,
|
||||
faNetworkWired,
|
||||
faHardDrive,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import type { SystemStats } from "$lib/types";
|
||||
import {
|
||||
@ -14,100 +16,159 @@
|
||||
} from "$lib/utils";
|
||||
|
||||
export let systemStats: SystemStats | null = null;
|
||||
|
||||
$: memoryPercentage = systemStats
|
||||
? (systemStats.memory_used / systemStats.memory_total) * 100
|
||||
: 0;
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||
let value = bytes;
|
||||
let unitIndex = 0;
|
||||
|
||||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||
value /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return `${value.toFixed(1)} ${units[unitIndex]}`;
|
||||
}
|
||||
|
||||
$: diskUsagePercentage = systemStats
|
||||
? (systemStats.disk_used_bytes / systemStats.disk_total_bytes) * 100
|
||||
: 0;
|
||||
</script>
|
||||
|
||||
<div class="stats-bar">
|
||||
<div class="dashboard-stats">
|
||||
{#if systemStats}
|
||||
<div class="stats-grid">
|
||||
<div class="stat-box">
|
||||
<div class="stat-header">
|
||||
<div class="stat-title">
|
||||
<Fa icon={faMicrochip} />
|
||||
<span>CPU Usage</span>
|
||||
</div>
|
||||
<div class="stats-layout">
|
||||
<!-- CPU Panel -->
|
||||
<div class="stat-panel">
|
||||
<div class="panel-header">
|
||||
<Fa icon={faMicrochip} />
|
||||
<h3>CPU Usage</h3>
|
||||
</div>
|
||||
<div class="cpu-bars">
|
||||
<div class="stats-content cpu-grid">
|
||||
{#each systemStats.cpu_usage as usage, i}
|
||||
<div class="cpu-bar">
|
||||
<span class="cpu-label">CPU {i}</span>
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-fill {getUsageClass(usage)}"
|
||||
style="width: {usage}%"
|
||||
></div>
|
||||
<div class="stat-item with-progress">
|
||||
<div class="progress-container">
|
||||
<span class="label">Core {i}</span>
|
||||
<div class="bar-container">
|
||||
<div
|
||||
class="usage-bar {getUsageClass(usage)}"
|
||||
style="transform: translateX({usage - 100}%);"
|
||||
></div>
|
||||
</div>
|
||||
<span class="value">{Math.round(usage)}%</span>
|
||||
</div>
|
||||
<span class="cpu-value">{usage.toFixed(1)}%</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-box">
|
||||
<div class="stat-header">
|
||||
<div class="stat-title">
|
||||
<Fa icon={faMemory} />
|
||||
<span>Memory</span>
|
||||
</div>
|
||||
<!-- Memory Panel -->
|
||||
<div class="stat-panel">
|
||||
<div class="panel-header">
|
||||
<Fa icon={faMemory} />
|
||||
<h3>Memory</h3>
|
||||
<div class="usage-pill">{formatPercentage(memoryPercentage)}</div>
|
||||
</div>
|
||||
<div class="memory-info">
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-fill {getUsageClass(memoryPercentage)}"
|
||||
style="width: {memoryPercentage}%"
|
||||
></div>
|
||||
<div class="stats-content">
|
||||
<div class="stat-item with-progress">
|
||||
<div class="memory-progress-container">
|
||||
<span class="label">Memory usage</span>
|
||||
<div class="bar-container">
|
||||
<div
|
||||
class="usage-bar {getUsageClass(memoryPercentage)}"
|
||||
style="transform: translateX({memoryPercentage - 100}%);"
|
||||
></div>
|
||||
</div>
|
||||
<span class="value">{formatPercentage(memoryPercentage)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="memory-details">
|
||||
<div class="memory-row">
|
||||
<span>Total:</span>
|
||||
<span>{formatMemorySize(systemStats.memory_total)}</span>
|
||||
</div>
|
||||
<div class="memory-row">
|
||||
<span>Used:</span>
|
||||
<span
|
||||
>{formatMemorySize(systemStats.memory_used)} ({formatPercentage(
|
||||
memoryPercentage,
|
||||
)})</span
|
||||
>
|
||||
</div>
|
||||
<div class="memory-row">
|
||||
<span>Free:</span>
|
||||
<span>{formatMemorySize(systemStats.memory_free)}</span>
|
||||
</div>
|
||||
<div class="memory-row">
|
||||
<span>Cached:</span>
|
||||
<span>{formatMemorySize(systemStats.memory_cached)}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>Total</span>
|
||||
<span>{formatMemorySize(systemStats.memory_total)}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>Used</span>
|
||||
<span>{formatMemorySize(systemStats.memory_used)}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>Free</span>
|
||||
<span>{formatMemorySize(systemStats.memory_free)}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>Cached</span>
|
||||
<span>{formatMemorySize(systemStats.memory_cached)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-box">
|
||||
<div class="stat-header">
|
||||
<div class="stat-title">
|
||||
<Fa icon={faServer} />
|
||||
<span>System Info</span>
|
||||
<!-- Storage Panel -->
|
||||
<div class="stat-panel">
|
||||
<div class="panel-header">
|
||||
<Fa icon={faHardDrive} />
|
||||
<h3>Storage</h3>
|
||||
<div class="usage-pill">
|
||||
{formatPercentage(diskUsagePercentage)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="system-info">
|
||||
<div class="info-row">
|
||||
<span>Uptime:</span>
|
||||
<div class="stats-content">
|
||||
<div class="stat-item">
|
||||
<span>Total</span>
|
||||
<span>{formatBytes(systemStats.disk_total_bytes)}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>Used</span>
|
||||
<span>{formatBytes(systemStats.disk_used_bytes)}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>Free</span>
|
||||
<span>{formatBytes(systemStats.disk_free_bytes)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Panel -->
|
||||
<div class="stat-panel">
|
||||
<div class="panel-header">
|
||||
<Fa icon={faServer} />
|
||||
<h3>System</h3>
|
||||
</div>
|
||||
<div class="system-grid">
|
||||
<div class="stat-item">
|
||||
<span>Uptime</span>
|
||||
<span>{formatUptime(systemStats.uptime)}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span>Load Average:</span>
|
||||
<span>{systemStats.load_avg[0].toFixed(2)} (1m)</span>
|
||||
<div class="stat-item">
|
||||
<span>1m Load</span>
|
||||
<span>{systemStats.load_avg[0].toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span></span>
|
||||
<span>{systemStats.load_avg[1].toFixed(2)} (5m)</span>
|
||||
<div class="stat-item">
|
||||
<span>5m Load</span>
|
||||
<span>{systemStats.load_avg[1].toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span></span>
|
||||
<span>{systemStats.load_avg[2].toFixed(2)} (15m)</span>
|
||||
<div class="stat-item">
|
||||
<span>15m Load</span>
|
||||
<span>{systemStats.load_avg[2].toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Network Panel -->
|
||||
<div class="stat-panel">
|
||||
<div class="panel-header">
|
||||
<Fa icon={faNetworkWired} />
|
||||
<h3>Network I/O</h3>
|
||||
</div>
|
||||
<div class="network-stats">
|
||||
<div class="stat-item">
|
||||
<span>↓ Receiving</span>
|
||||
<span>{formatBytes(systemStats.network_rx_bytes)}/s</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>↑ Sending</span>
|
||||
<span>{formatBytes(systemStats.network_tx_bytes)}/s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -116,143 +177,284 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.stats-bar {
|
||||
background-color: var(--mantle);
|
||||
border-bottom: 1px solid var(--surface0);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 12px;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
.dashboard-stats {
|
||||
background: var(--base);
|
||||
border: 1px solid var(--surface0);
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.stat-header {
|
||||
.stats-layout {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stat-panel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: var(--mantle);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--surface0);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.panel-header h3 {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.panel-header :global(svg) {
|
||||
color: var(--blue);
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
}
|
||||
|
||||
.usage-pill {
|
||||
margin-left: auto;
|
||||
background: var(--surface0);
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cpu-layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.75rem;
|
||||
overflow-y: auto;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.cpu-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.cpu-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--surface0);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-title :global(svg) {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--subtext0);
|
||||
}
|
||||
|
||||
.cpu-bars {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cpu-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cpu-label {
|
||||
width: 45px;
|
||||
color: var(--subtext0);
|
||||
}
|
||||
|
||||
.cpu-value {
|
||||
width: 45px;
|
||||
text-align: right;
|
||||
color: var(--subtext0);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
.bar-container {
|
||||
height: 8px;
|
||||
background-color: var(--surface0);
|
||||
background: var(--surface0);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.2s ease;
|
||||
.bar-container.main-bar {
|
||||
height: 6px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-fill.low {
|
||||
background-color: var(--green);
|
||||
.usage-bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
transition: transform 0.3s ease;
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.progress-fill.medium {
|
||||
background-color: var(--yellow);
|
||||
.usage-bar.low {
|
||||
background: var(--blue);
|
||||
}
|
||||
.usage-bar.medium {
|
||||
background: var(--yellow);
|
||||
}
|
||||
.usage-bar.high {
|
||||
background: var(--peach);
|
||||
}
|
||||
.usage-bar.critical {
|
||||
background: var(--red);
|
||||
}
|
||||
|
||||
.progress-fill.high {
|
||||
background-color: var(--peach);
|
||||
.memory-grid,
|
||||
.disk-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.75rem;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.progress-fill.critical {
|
||||
background-color: var(--red);
|
||||
}
|
||||
|
||||
.memory-info {
|
||||
.system-grid,
|
||||
.network-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 0.4rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.memory-text {
|
||||
font-size: 12px;
|
||||
color: var(--subtext0);
|
||||
}
|
||||
|
||||
.system-info {
|
||||
font-size: 12px;
|
||||
color: var(--subtext0);
|
||||
.load-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.memory-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
.network-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.memory-row,
|
||||
.info-row {
|
||||
.stat-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
font-size: 0.7rem;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stat-item span:first-child {
|
||||
color: var(--subtext0);
|
||||
}
|
||||
|
||||
.memory-row span:first-child,
|
||||
.info-row span:first-child {
|
||||
color: var(--subtext1);
|
||||
min-width: 80px;
|
||||
.stat-item span:last-child {
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.network-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.stats-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.stat-item.with-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.7rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 4rem 1fr 3rem;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.memory-progress-container {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 5rem 1fr 2.5rem;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.memory-progress-container .label {
|
||||
color: var(--subtext0);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.memory-progress-container .value {
|
||||
color: var(--text);
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progress-container .label {
|
||||
color: var(--subtext0);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progress-container .value {
|
||||
color: var(--text);
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bar-container {
|
||||
height: 8px;
|
||||
background: var(--surface0);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.usage-bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
transition: transform 0.3s ease;
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.cpu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.4rem 2rem;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 2.5rem 1fr 2.5rem;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/* Make CPU panel take more space */
|
||||
.stat-panel:first-child {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
/* Customize flex distribution for each panel */
|
||||
.stat-panel:nth-child(1) {
|
||||
flex: 2.5; /* CPU: more space for 2 columns */
|
||||
}
|
||||
.stat-panel:nth-child(2) {
|
||||
flex: 2; /* Memory: more space for details */
|
||||
}
|
||||
.stat-panel:nth-child(3),
|
||||
.stat-panel:nth-child(4),
|
||||
.stat-panel:nth-child(5) {
|
||||
flex: 0.8; /* Storage, System, and Network: less space */
|
||||
}
|
||||
</style>
|
||||
|
@ -19,6 +19,11 @@ export interface SystemStats {
|
||||
memory_cached: number;
|
||||
uptime: number;
|
||||
load_avg: [number, number, number];
|
||||
network_rx_bytes: number;
|
||||
network_tx_bytes: number;
|
||||
disk_total_bytes: number;
|
||||
disk_used_bytes: number;
|
||||
disk_free_bytes: number;
|
||||
}
|
||||
|
||||
export interface Column {
|
||||
|
Loading…
x
Reference in New Issue
Block a user