mirror of
https://github.com/kunkunsh/kunkun-ext-neohtop.git
synced 2025-04-11 17:29:45 +00:00
464 lines
10 KiB
Svelte
464 lines
10 KiB
Svelte
<script lang="ts">
|
|
import Fa from "svelte-fa";
|
|
import {
|
|
faMicrochip,
|
|
faMemory,
|
|
faServer,
|
|
faNetworkWired,
|
|
faHardDrive,
|
|
} from "@fortawesome/free-solid-svg-icons";
|
|
import type { SystemStats } from "$lib/types";
|
|
import {
|
|
formatUptime,
|
|
formatMemorySize,
|
|
formatPercentage,
|
|
getUsageClass,
|
|
} 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="dashboard-stats">
|
|
{#if systemStats}
|
|
<div class="stats-layout">
|
|
<!-- CPU Panel -->
|
|
<div class="stat-panel">
|
|
<div class="panel-header">
|
|
<Fa icon={faMicrochip} />
|
|
<h3>CPU Usage</h3>
|
|
<div class="usage-pill">
|
|
{formatPercentage(
|
|
systemStats.cpu_usage.reduce((a, b) => a + b, 0) /
|
|
systemStats.cpu_usage.length,
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div class="stats-content cpu-grid">
|
|
{#each systemStats.cpu_usage as usage, i}
|
|
<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>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</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="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="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>
|
|
</div>
|
|
|
|
<!-- 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="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="stat-item">
|
|
<span>1m Load</span>
|
|
<span>{systemStats.load_avg[0].toFixed(2)}</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span>5m Load</span>
|
|
<span>{systemStats.load_avg[1].toFixed(2)}</span>
|
|
</div>
|
|
<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>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.dashboard-stats {
|
|
background: var(--base);
|
|
padding: 0.5rem;
|
|
border-radius: 8px;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.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;
|
|
font-size: 0.7rem;
|
|
}
|
|
|
|
.bar-container {
|
|
height: 8px;
|
|
background: var(--surface0);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.bar-container.main-bar {
|
|
height: 6px;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.usage-bar {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 100%;
|
|
transition: transform 0.3s ease;
|
|
transform-origin: left;
|
|
}
|
|
|
|
.usage-bar.low {
|
|
background: var(--blue);
|
|
}
|
|
.usage-bar.medium {
|
|
background: var(--yellow);
|
|
}
|
|
.usage-bar.high {
|
|
background: var(--peach);
|
|
}
|
|
.usage-bar.critical {
|
|
background: var(--red);
|
|
}
|
|
|
|
.memory-grid,
|
|
.disk-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 0.75rem;
|
|
align-content: start;
|
|
}
|
|
|
|
.system-grid,
|
|
.network-grid {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.4rem;
|
|
flex: 1;
|
|
}
|
|
|
|
.load-stats {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
.network-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.stat-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 0.7rem;
|
|
line-height: 1.2;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.stat-item span:first-child {
|
|
color: var(--subtext0);
|
|
}
|
|
|
|
.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 */
|
|
min-width: 125px;
|
|
}
|
|
</style>
|