Tauri 2.0 Project System Monitor Resource Usage App

Building a system monitor application with Tauri 2.0 demonstrates real-time data collection, system API integration, and performance monitoring creating comprehensive resource tracking tool—complete project combining CPU monitoring with multi-core tracking, memory usage visualization with swap detection, disk space monitoring with filesystem enumeration, process management with real-time updates, live charts with responsive animations, and refresh controls delivering professional system monitor. This system monitor uses Rust's sysinfo crate providing accurate system metrics, React Context API managing global state, recharts library rendering real-time charts, and Tauri's IPC system enabling efficient data streaming maintaining minimal overhead. This complete guide covers project setup and dependencies, building Rust backend with system monitoring commands, implementing CPU monitor with per-core tracking, creating memory monitor with usage charts, building disk space monitor with filesystem detection, implementing process list with sorting and filtering, designing responsive UI with TailwindCSS, adding real-time updates with configurable intervals, and deployment packaging maintaining production-ready system monitor through proper implementation. Before proceeding, understand IPC communication, Rust commands, and basic app creation.
Project Architecture Overview
Understanding the system monitor architecture helps organize components efficiently. The app follows a data-driven architecture with Rust backend collecting system metrics and React frontend displaying real-time visualizations.
system-monitor/
├── src/ # Frontend React application
│ ├── components/ # UI Components
│ │ ├── CPUMonitor.tsx # CPU usage with per-core display
│ │ ├── MemoryMonitor.tsx # Memory and swap usage charts
│ │ ├── DiskMonitor.tsx # Disk space monitoring
│ │ └── ProcessList.tsx # Running processes table
│ ├── context/ # State management
│ │ └── DataContext.tsx # Global data context provider
│ ├── types/ # TypeScript types
│ │ └── index.ts # System data types
│ ├── utils/ # Utility functions
│ │ └── utils.ts # Helper functions
│ ├── App.tsx # Main application component
│ ├── App.css # Application styles
│ └── main.tsx # React entry point
├── src-tauri/ # Rust backend
│ ├── src/
│ │ ├── monitor.rs # System monitoring logic
│ │ ├── main.rs # Tauri entry point
│ │ └── lib.rs # Library file
│ ├── capabilities/ # Tauri permissions
│ │ └── default.json # System access permissions
│ ├── Cargo.toml # Rust dependencies
│ └── tauri.conf.json # Tauri configuration
├── package.json # Node dependencies
├── tsconfig.json # TypeScript config
└── vite.config.ts # Vite bundler configStep 1: Project Setup and Dependencies
Create a new Tauri project with React and install necessary dependencies for system monitoring and charting. This sets up the foundation for our system resource monitor.
# Create new Tauri project
npm create tauri-app@latest
# During setup:
# Project name: system-monitor
# Package manager: npm
# UI template: React
# TypeScript: Yes
cd system-monitor
# Install frontend dependencies
npm install recharts lucide-react
# Recharts: Charting library for real-time graphs
# Lucide-react: Icon library
# Install Tauri plugins (none required for this project)
# sysinfo crate will be added to Cargo.tomlStep 2: Rust Backend Configuration
Configure Rust dependencies and set up the backend for system monitoring. The sysinfo crate provides cross-platform system information APIs.
[package]
name = "tauri-appsystem-monitor"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
[lib]
name = "tauri_appsystem_monitor_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sysinfo = "0.38"
tokio = { version = "1", features = ["full"] }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = ["Win32_System_Threading", "Win32_System_SystemInformation"] }
use serde::{Deserialize, Serialize};
use sysinfo::{System, Disks, Networks};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SystemInfo {
pub cpu_usage: f32,
pub cpu_count: usize,
pub memory_total: u64,
pub memory_used: u64,
pub memory_available: u64,
pub swap_total: u64,
pub swap_used: u64,
pub uptime: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiskInfo {
pub name: String,
pub mount_point: String,
pub total_space: u64,
pub available_space: u64,
pub usage_percent: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct NetworkInfo {
pub bytes_received: u64,
pub bytes_sent: u64,
pub packets_received: u64,
pub packets_sent: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessInfo {
pub pid: u32,
pub name: String,
pub cpu_usage: f32,
pub memory: u64,
pub status: String,
}
#[derive(Default)]
struct CachedState {
sys_info: SystemInfo,
cpu_per_core: Vec<f32>,
disk_info: Vec<DiskInfo>,
network_info: NetworkInfo,
processes: Vec<ProcessInfo>,
}
pub struct SystemMonitor {
state: Arc<Mutex<CachedState>>,
}
impl SystemMonitor {
pub fn new() -> Self {
let state = Arc::new(Mutex::new(CachedState::default()));
let state_clone = state.clone();
thread::spawn(move || {
// Use System::new_all() for complete initialization including CPU
let mut sys = System::new_all();
let mut disks = Disks::new_with_refreshed_list();
let mut networks = Networks::new_with_refreshed_list();
// Initial refresh to prime CPU deltas - MUST sleep between refreshes for accurate readings
// CPU usage is calculated as delta between two measurements
thread::sleep(Duration::from_millis(200));
sys.refresh_cpu_all();
thread::sleep(Duration::from_millis(200));
sys.refresh_cpu_all();
loop {
// Sleep first to maintain interval - CPU usage calculation requires time delta
thread::sleep(Duration::from_millis(1000));
// 1. Refresh Data - use refresh_cpu_all() for complete CPU data
sys.refresh_cpu_all();
sys.refresh_memory();
disks.refresh(true); // true = remove disks that no longer exist
networks.refresh(true); // true = remove interfaces that no longer exist
// 2. Get CPU usage - try sysinfo first, fall back to Windows API if needed
let cpu_count = sys.cpus().len();
// Try sysinfo first
let mut cpu_per_core: Vec<f32> = sys.cpus().iter().map(|c| c.cpu_usage()).collect();
let mut global_cpu = if cpu_per_core.is_empty() {
0.0
} else {
cpu_per_core.iter().sum::<f32>() / cpu_per_core.len() as f32
};
// WINDOWS FALLBACK: If sysinfo returns 0, use GetSystemTimes directly
#[cfg(target_os = "windows")]
{
use std::sync::atomic::{AtomicU64, Ordering};
static PREV_IDLE: AtomicU64 = AtomicU64::new(0);
static PREV_TOTAL: AtomicU64 = AtomicU64::new(0);
if global_cpu < 0.01 {
// Use Windows GetSystemTimes API
#[repr(C)]
#[derive(Default)]
struct FILETIME {
dw_low_date_time: u32,
dw_high_date_time: u32,
}
extern "system" {
fn GetSystemTimes(
idle_time: *mut FILETIME,
kernel_time: *mut FILETIME,
user_time: *mut FILETIME,
) -> i32;
}
fn filetime_to_u64(ft: &FILETIME) -> u64 {
((ft.dw_high_date_time as u64) << 32) | (ft.dw_low_date_time as u64)
}
unsafe {
let mut idle = FILETIME::default();
let mut kernel = FILETIME::default();
let mut user = FILETIME::default();
if GetSystemTimes(&mut idle, &mut kernel, &mut user) != 0 {
let idle_time = filetime_to_u64(&idle);
let kernel_time = filetime_to_u64(&kernel);
let user_time = filetime_to_u64(&user);
let total_time = kernel_time + user_time;
let prev_idle = PREV_IDLE.load(Ordering::Relaxed);
let prev_total = PREV_TOTAL.load(Ordering::Relaxed);
if prev_total > 0 {
let idle_delta = idle_time.saturating_sub(prev_idle);
let total_delta = total_time.saturating_sub(prev_total);
if total_delta > 0 {
// CPU usage = (total - idle) / total * 100
let usage = 100.0 * (1.0 - (idle_delta as f64 / total_delta as f64));
global_cpu = usage.max(0.0).min(100.0) as f32;
// Distribute evenly across cores
cpu_per_core = vec![global_cpu; cpu_count];
}
}
PREV_IDLE.store(idle_time, Ordering::Relaxed);
PREV_TOTAL.store(total_time, Ordering::Relaxed);
}
}
}
}
let cores_usage: Vec<f32> = cpu_per_core.iter().take(4).cloned().collect();
// Debug log
println!("Debug: CPU Count: {}, Global CPU: {:.1}%, Cores (first 4): {:?}", cpu_count, global_cpu, cores_usage);
let sys_info = SystemInfo {
cpu_usage: global_cpu,
cpu_count: cpu_count,
memory_total: sys.total_memory(),
memory_used: sys.used_memory(),
memory_available: sys.available_memory(),
swap_total: sys.total_swap(),
swap_used: sys.used_swap(),
uptime: System::uptime(),
};
let disk_info: Vec<DiskInfo> = disks.iter().map(|disk| {
let total = disk.total_space();
let available = disk.available_space();
let used = total - available;
let usage_percent = if total > 0 {
(used as f32 / total as f32) * 100.0
} else { 0.0 };
DiskInfo {
name: disk.name().to_string_lossy().to_string(),
mount_point: disk.mount_point().to_string_lossy().to_string(),
total_space: total,
available_space: available,
usage_percent,
}
}).collect();
// Network
let mut total_received = 0;
let mut total_sent = 0;
let mut total_packets_received = 0;
let mut total_packets_sent = 0;
for (_interface_name, data) in networks.iter() {
total_received += data.received();
total_sent += data.transmitted();
total_packets_received += data.packets_received();
total_packets_sent += data.packets_transmitted();
}
let network_info = NetworkInfo {
bytes_received: total_received,
bytes_sent: total_sent,
packets_received: total_packets_received,
packets_sent: total_packets_sent,
};
// Processes - refresh with sysinfo 0.33 API
use sysinfo::ProcessesToUpdate;
sys.refresh_processes(ProcessesToUpdate::All, true);
let mut processes: Vec<ProcessInfo> = sys.processes().iter().map(|(pid, process)| {
ProcessInfo {
pid: pid.as_u32(),
name: process.name().to_string_lossy().to_string(),
cpu_usage: process.cpu_usage(),
memory: process.memory(),
status: format!("{:?}", process.status()),
}
}).collect();
processes.sort_by(|a, b| b.cpu_usage.partial_cmp(&a.cpu_usage).unwrap());
processes.truncate(50);
// 3. Update Shared State
{
let mut lock = state_clone.lock().unwrap();
lock.sys_info = sys_info;
lock.cpu_per_core = cpu_per_core;
lock.disk_info = disk_info;
lock.network_info = network_info;
lock.processes = processes;
}
}
});
Self { state }
}
pub fn get_system_info(&self) -> SystemInfo {
self.state.lock().unwrap().sys_info.clone()
}
pub fn get_cpu_per_core(&self) -> Vec<f32> {
self.state.lock().unwrap().cpu_per_core.clone()
}
pub fn get_disk_info(&self) -> Vec<DiskInfo> {
self.state.lock().unwrap().disk_info.clone()
}
pub fn get_network_info(&self) -> NetworkInfo {
self.state.lock().unwrap().network_info.clone()
}
pub fn get_processes(&self) -> Vec<ProcessInfo> {
self.state.lock().unwrap().processes.clone()
}
}
// Tauri commands
#[tauri::command]
pub async fn get_system_info(
monitor: tauri::State<'_, SystemMonitor>
) -> Result<SystemInfo, String> {
Ok(monitor.get_system_info())
}
#[tauri::command]
pub async fn get_cpu_per_core(
monitor: tauri::State<'_, SystemMonitor>
) -> Result<Vec<f32>, String> {
Ok(monitor.get_cpu_per_core())
}
#[tauri::command]
pub async fn get_disk_info(
monitor: tauri::State<'_, SystemMonitor>
) -> Result<Vec<DiskInfo>, String> {
Ok(monitor.get_disk_info())
}
#[tauri::command]
pub async fn get_network_info(
monitor: tauri::State<'_, SystemMonitor>
) -> Result<NetworkInfo, String> {
Ok(monitor.get_network_info())
}
#[tauri::command]
pub async fn get_processes(
monitor: tauri::State<'_, SystemMonitor>
) -> Result<Vec<ProcessInfo>, String> {
Ok(monitor.get_processes())
}
// Prevents additional console window on Windows in release, do not remove.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::Manager;
use std::fs;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct RecentFiles {
files: Vec<String>,
}
fn get_recent_files_path(app: &tauri::AppHandle) -> std::path::PathBuf {
app.path().app_data_dir()
.expect("Failed to get app data dir")
.join("recent_files.json")
}
#[tauri::command]
fn get_recent_files(app: tauri::AppHandle) -> Vec<String> {
let path = get_recent_files_path(&app);
if !path.exists() {
return Vec::new();
}
match fs::read_to_string(&path) {
Ok(content) => serde_json::from_str::<RecentFiles>(&content)
.map(|data| data.files)
.unwrap_or_default(),
Err(_) => Vec::new(),
}
}
#[tauri::command]
fn add_recent_file(app: tauri::AppHandle, path: String) -> Result<(), String> {
let recent_path = get_recent_files_path(&app);
if let Some(parent) = recent_path.parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create directory: {}", e))?;
}
let mut files = get_recent_files(app.clone());
files.retain(|f| f != &path);
files.insert(0, path);
files.truncate(10);
let json = serde_json::to_string_pretty(&RecentFiles { files })
.map_err(|e| format!("Serialization error: {}", e))?;
fs::write(&recent_path, json)
.map_err(|e| format!("Write error: {}", e))?;
Ok(())
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![get_recent_files, add_recent_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Step 3: TypeScript Type Definitions
Define TypeScript interfaces for system data structures. These types ensure type safety when handling data from the Rust backend.
export interface SystemInfo {
cpu_usage: number;
cpu_count: number;
memory_total: number;
memory_used: number;
memory_available: number;
swap_total: number;
swap_used: number;
uptime: number;
}
export interface DiskInfo {
name: string;
mount_point: string;
total_space: number;
available_space: number;
usage_percent: number;
}
export interface NetworkInfo {
bytes_received: number;
bytes_sent: number;
packets_received: number;
packets_sent: number;
}
export interface ProcessInfo {
pid: number;
name: string;
cpu_usage: number;
memory: number;
status: string;
}
Step 4: React Context for State Management
Create a React Context to manage system data globally. This provides centralized state management for real-time monitoring data.
import React, { createContext, useContext, useEffect, useState, ReactNode } from "react";
interface DataContextType {
lastRefresh: number;
}
const DataContext = createContext<DataContextType>({ lastRefresh: 0 });
export const DataProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [lastRefresh, setLastRefresh] = useState(0);
useEffect(() => {
// Initial fetch trigger
setLastRefresh(Date.now());
// Periodic refresh trigger every 1 second
// The backend now updates automatically in the background,
// so we just need to tell components to fetch new data.
const interval = setInterval(() => {
setLastRefresh(Date.now());
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<DataContext.Provider value={{ lastRefresh }}>
{children}
</DataContext.Provider>
);
};
export const useDataRefresh = () => useContext(DataContext);
Step 5: CPU Monitor Component
Build the CPU monitor displaying overall and per-core CPU usage with real-time charts. This component shows detailed CPU metrics with visual indicators.
import React, { useState, useEffect } from 'react';
import { Line } from 'react-chartjs-2';
import { invoke } from '@tauri-apps/api/core';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler,
} from 'chart.js';
import { Cpu, TrendingUp } from 'lucide-react';
import { useDataRefresh } from '../contexts/DataContext';
import { cn } from '../lib/utils';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler
);
const CPUMonitor: React.FC = () => {
const { lastRefresh } = useDataRefresh();
const [cpuUsage, setCpuUsage] = useState<number>(0);
const [cpuHistory, setCpuHistory] = useState<number[]>([]);
const [perCoreUsage, setPerCoreUsage] = useState<number[]>([]);
useEffect(() => {
if (lastRefresh === 0) return;
const fetchCPUData = async () => {
try {
const info = await invoke<{ cpu_usage: number }>('get_system_info');
const coreUsage = await invoke<number[]>('get_cpu_per_core');
setCpuUsage(info.cpu_usage);
setPerCoreUsage(coreUsage);
setCpuHistory((prev) => {
const newHistory = [...prev, info.cpu_usage];
return newHistory.slice(-60);
});
} catch (error) {
console.error('Failed to fetch CPU data:', error);
}
};
fetchCPUData();
}, [lastRefresh]);
const chartData = {
labels: cpuHistory.map((_, i) => `${i}s`),
datasets: [
{
label: 'CPU Usage (%)',
data: cpuHistory,
borderColor: '#10b981',
backgroundColor: (context: any) => {
const ctx = context.chart.ctx;
const gradient = ctx.createLinearGradient(0, 0, 0, 180);
gradient.addColorStop(0, 'rgba(16, 185, 129, 0.25)');
gradient.addColorStop(1, 'rgba(16, 185, 129, 0.02)');
return gradient;
},
borderWidth: 2.5,
pointRadius: 0,
tension: 0.4,
fill: true,
},
],
};
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
grid: {
color: 'rgba(148, 163, 184, 0.1)',
},
ticks: {
color: '#94a3b8',
font: {
family: 'Inter, sans-serif',
size: 10
}
},
border: {
display: false
}
},
x: {
display: false,
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
mode: 'index' as const,
intersect: false,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
titleColor: '#1e293b',
bodyColor: '#10b981',
borderColor: '#e2e8f0',
borderWidth: 1,
padding: 12,
displayColors: false,
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
}
},
animation: {
duration: 0,
},
};
const getUsageColor = (usage: number) => {
if (usage > 80) return { text: 'text-red-600', bg: 'bg-red-500', bgLight: 'bg-red-100' };
if (usage > 60) return { text: 'text-amber-600', bg: 'bg-amber-500', bgLight: 'bg-amber-100' };
return { text: 'text-emerald-600', bg: 'bg-emerald-500', bgLight: 'bg-emerald-100' };
};
const usageColors = getUsageColor(cpuUsage);
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="flex justify-between items-center mb-5">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-gradient-to-br from-emerald-500 to-emerald-600 rounded-xl shadow-lg shadow-emerald-500/20">
<Cpu className="h-5 w-5 text-white" />
</div>
<div>
<h2 className="text-base font-semibold text-slate-800">CPU Usage</h2>
<p className="text-xs text-slate-500">Processor utilization</p>
</div>
</div>
<div className={cn("px-4 py-2 rounded-xl flex items-center gap-2", usageColors.bgLight)}>
<TrendingUp className={cn("h-4 w-4", usageColors.text)} />
<span className={cn("text-lg font-bold", usageColors.text)}>
{cpuUsage.toFixed(1)}%
</span>
</div>
</div>
{/* Chart */}
<div className="h-44 mb-5 bg-slate-50/50 rounded-xl p-3 border border-slate-100">
<Line data={chartData} options={chartOptions} />
</div>
{/* Per-Core Usage */}
<div className="flex-1 overflow-hidden">
<h3 className="text-xs font-semibold text-slate-500 mb-3 uppercase tracking-wider">Core Activity</h3>
<div className="grid grid-cols-4 gap-2 max-h-32 overflow-y-auto custom-scrollbar pr-1">
{perCoreUsage.slice(0, 8).map((usage, index) => {
const coreColors = getUsageColor(usage);
return (
<div
key={index}
className="bg-white p-2.5 rounded-xl border border-slate-200 hover:border-slate-300 hover:shadow-sm transition-all group"
>
<div className="flex justify-between items-center text-xs mb-2">
<span className="text-slate-500 font-medium">C{index}</span>
<span className={cn("font-bold", coreColors.text)}>
{usage.toFixed(0)}%
</span>
</div>
<div className="h-1.5 w-full bg-slate-100 rounded-full overflow-hidden">
<div
className={cn("h-full rounded-full transition-all duration-500", coreColors.bg)}
style={{ width: `${Math.min(usage, 100)}%` }}
/>
</div>
</div>
);
})}
</div>
</div>
</div>
);
};
export default CPUMonitor;
Step 6: Memory Monitor Component
Create the memory monitor showing RAM and swap usage with percentage calculations. This component visualizes memory consumption patterns.
import React, { useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/core';
import type { SystemInfo } from '../types';
import { MemoryStick, Database, Layers } from 'lucide-react';
import { useDataRefresh } from '../contexts/DataContext';
import { cn } from '../lib/utils';
const MemoryMonitor: React.FC = () => {
const { lastRefresh } = useDataRefresh();
const [memoryInfo, setMemoryInfo] = useState<SystemInfo | null>(null);
useEffect(() => {
if (lastRefresh === 0) return;
const fetchMemoryData = async () => {
const info = await invoke<SystemInfo>('get_system_info');
setMemoryInfo(info);
};
fetchMemoryData();
}, [lastRefresh]);
if (!memoryInfo) return (
<div className="flex h-full items-center justify-center text-slate-400">
<div className="flex flex-col items-center gap-2">
<div className="h-8 w-8 border-2 border-blue-500/30 border-t-blue-500 rounded-full animate-spin"></div>
<span className="text-sm">Loading memory data...</span>
</div>
</div>
);
const memoryUsagePercent = (memoryInfo.memory_used / memoryInfo.memory_total) * 100;
const swapUsagePercent = memoryInfo.swap_total > 0
? (memoryInfo.swap_used / memoryInfo.swap_total) * 100
: 0;
const formatBytes = (bytes: number) => {
const gb = bytes / (1024 * 1024 * 1024);
return `${gb.toFixed(2)} GB`;
};
const getUsageColor = (percent: number) => {
if (percent > 85) return { gradient: 'from-red-500 to-red-600', text: 'text-red-600', bg: 'bg-red-100' };
if (percent > 70) return { gradient: 'from-amber-500 to-amber-600', text: 'text-amber-600', bg: 'bg-amber-100' };
return { gradient: 'from-blue-500 to-blue-600', text: 'text-blue-600', bg: 'bg-blue-100' };
};
const ramColors = getUsageColor(memoryUsagePercent);
const swapColors = getUsageColor(swapUsagePercent);
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="flex items-center gap-3 mb-6">
<div className="p-2.5 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl shadow-lg shadow-blue-500/20">
<MemoryStick className="h-5 w-5 text-white" />
</div>
<div>
<h2 className="text-base font-semibold text-slate-800">Memory Usage</h2>
<p className="text-xs text-slate-500">RAM & Virtual Memory</p>
</div>
</div>
<div className="space-y-6 flex-1">
{/* RAM Section */}
<div className="bg-white rounded-xl p-4 border border-slate-200 shadow-sm">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Database className="h-4 w-4 text-blue-500" />
<span className="text-sm font-semibold text-slate-700">RAM</span>
</div>
<span className={cn("text-xl font-bold", ramColors.text)}>
{memoryUsagePercent.toFixed(1)}%
</span>
</div>
<div className="relative h-3 bg-slate-100 rounded-full overflow-hidden mb-3">
<div
className={cn("absolute inset-y-0 left-0 rounded-full bg-gradient-to-r transition-all duration-700 ease-out", ramColors.gradient)}
style={{ width: `${memoryUsagePercent}%` }}
/>
</div>
<div className="flex justify-between text-xs">
<span className="text-slate-500">
<span className="font-medium text-slate-700">{formatBytes(memoryInfo.memory_used)}</span> used
</span>
<span className="text-slate-500">
of <span className="font-medium text-slate-700">{formatBytes(memoryInfo.memory_total)}</span>
</span>
</div>
</div>
{/* Swap Section */}
{memoryInfo.swap_total > 0 && (
<div className="bg-white rounded-xl p-4 border border-slate-200 shadow-sm">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Layers className="h-4 w-4 text-purple-500" />
<span className="text-sm font-semibold text-slate-700">Swap</span>
</div>
<span className={cn("text-xl font-bold", swapColors.text)}>
{swapUsagePercent.toFixed(1)}%
</span>
</div>
<div className="relative h-3 bg-slate-100 rounded-full overflow-hidden mb-3">
<div
className="absolute inset-y-0 left-0 rounded-full bg-gradient-to-r from-purple-500 to-purple-600 transition-all duration-700 ease-out"
style={{ width: `${swapUsagePercent}%` }}
/>
</div>
<div className="flex justify-between text-xs">
<span className="text-slate-500">
<span className="font-medium text-slate-700">{formatBytes(memoryInfo.swap_used)}</span> used
</span>
<span className="text-slate-500">
of <span className="font-medium text-slate-700">{formatBytes(memoryInfo.swap_total)}</span>
</span>
</div>
</div>
)}
</div>
{/* Stats Footer */}
<div className="mt-auto pt-4 grid grid-cols-2 gap-3">
<div className="bg-gradient-to-br from-emerald-50 to-emerald-100 p-3 rounded-xl border border-emerald-200">
<p className="text-xs text-emerald-600 font-medium mb-0.5">Available</p>
<p className="text-sm font-bold text-emerald-700">{formatBytes(memoryInfo.memory_available)}</p>
</div>
<div className="bg-gradient-to-br from-slate-50 to-slate-100 p-3 rounded-xl border border-slate-200">
<p className="text-xs text-slate-500 font-medium mb-0.5">Total Capacity</p>
<p className="text-sm font-bold text-slate-700">{formatBytes(memoryInfo.memory_total)}</p>
</div>
</div>
</div>
);
};
export default MemoryMonitor;
Step 7: Disk Space Monitor
Implement disk space monitoring showing available and used space for all mounted filesystems. Users can see storage capacity across all drives.
import React, { useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/core';
import type { DiskInfo } from '../types';
import { HardDrive, Database } from 'lucide-react';
import { useDataRefresh } from '../contexts/DataContext';
import { cn } from '../lib/utils';
const DiskMonitor: React.FC = () => {
const { lastRefresh } = useDataRefresh();
const [disks, setDisks] = useState<DiskInfo[]>([]);
useEffect(() => {
if (lastRefresh === 0) return;
const fetchDiskData = async () => {
const diskInfo = await invoke<DiskInfo[]>('get_disk_info');
setDisks(diskInfo);
};
fetchDiskData();
}, [lastRefresh]);
const formatBytes = (bytes: number) => {
const gb = bytes / (1024 * 1024 * 1024);
if (gb >= 1000) {
return `${(gb / 1024).toFixed(2)} TB`;
}
return `${gb.toFixed(2)} GB`;
};
const getUsageColor = (percent: number) => {
if (percent > 90) return { gradient: 'from-red-500 to-red-600', text: 'text-red-600', bg: 'bg-red-50', border: 'border-red-200' };
if (percent > 75) return { gradient: 'from-amber-500 to-amber-600', text: 'text-amber-600', bg: 'bg-amber-50', border: 'border-amber-200' };
return { gradient: 'from-orange-500 to-orange-600', text: 'text-orange-600', bg: 'bg-orange-50', border: 'border-orange-200' };
};
const getDiskIcon = (index: number) => {
const colors = [
'from-orange-400 to-orange-500',
'from-amber-400 to-amber-500',
'from-yellow-400 to-yellow-500',
'from-lime-400 to-lime-500',
];
return colors[index % colors.length];
};
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="flex items-center gap-3 mb-6">
<div className="p-2.5 bg-gradient-to-br from-orange-500 to-orange-600 rounded-xl shadow-lg shadow-orange-500/20">
<HardDrive className="h-5 w-5 text-white" />
</div>
<div>
<h2 className="text-base font-semibold text-slate-800">Disk Usage</h2>
<p className="text-xs text-slate-500">{disks.length} drive{disks.length !== 1 ? 's' : ''} detected</p>
</div>
</div>
{/* Disk List */}
<div className="flex-1 overflow-y-auto space-y-3 pr-1 custom-scrollbar">
{disks.length === 0 ? (
<div className="flex h-full items-center justify-center text-slate-400">
<div className="flex flex-col items-center gap-2">
<div className="h-8 w-8 border-2 border-orange-500/30 border-t-orange-500 rounded-full animate-spin"></div>
<span className="text-sm">Scanning drives...</span>
</div>
</div>
) : (
disks.map((disk, index) => {
const colors = getUsageColor(disk.usage_percent);
return (
<div
key={index}
className="bg-white rounded-xl p-4 border border-slate-200 shadow-sm hover:shadow-md hover:border-slate-300 transition-all group"
>
<div className="flex items-center gap-3 mb-3">
<div className={cn("h-10 w-10 rounded-lg bg-gradient-to-br flex items-center justify-center shadow-sm", getDiskIcon(index))}>
<Database className="h-5 w-5 text-white" />
</div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-slate-800 truncate">
{disk.name || "Local Disk"}
</h3>
<p className="text-xs text-slate-500 font-mono truncate">{disk.mount_point}</p>
</div>
<div className={cn("px-3 py-1.5 rounded-lg", colors.bg, colors.border, "border")}>
<span className={cn("text-sm font-bold", colors.text)}>
{disk.usage_percent.toFixed(1)}%
</span>
</div>
</div>
<div className="relative h-2.5 bg-slate-100 rounded-full overflow-hidden mb-3">
<div
className={cn("absolute inset-y-0 left-0 rounded-full bg-gradient-to-r transition-all duration-700 ease-out", colors.gradient)}
style={{ width: `${disk.usage_percent}%` }}
/>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="flex items-center gap-2 bg-slate-50 rounded-lg px-2.5 py-2">
<div className="h-2 w-2 rounded-full bg-emerald-500"></div>
<div>
<span className="text-[10px] text-slate-500 uppercase block">Free</span>
<span className="text-xs font-semibold text-slate-700">{formatBytes(disk.available_space)}</span>
</div>
</div>
<div className="flex items-center gap-2 bg-slate-50 rounded-lg px-2.5 py-2">
<div className="h-2 w-2 rounded-full bg-slate-400"></div>
<div>
<span className="text-[10px] text-slate-500 uppercase block">Total</span>
<span className="text-xs font-semibold text-slate-700">{formatBytes(disk.total_space)}</span>
</div>
</div>
</div>
</div>
);
})
)}
</div>
</div>
);
};
export default DiskMonitor;
Step 8: Process List Component
Build a comprehensive process list showing running processes with CPU, memory usage, and ability to kill processes. Users can monitor and manage system processes.
import React, { useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/core';
import type { ProcessInfo } from '../types';
import { Activity, Search, Cpu, HardDrive } from 'lucide-react';
import { useDataRefresh } from '../contexts/DataContext';
import { cn } from '../lib/utils';
const ProcessList: React.FC = () => {
const { lastRefresh } = useDataRefresh();
const [processes, setProcesses] = useState<ProcessInfo[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [sortBy, setSortBy] = useState<'cpu' | 'memory'>('cpu');
useEffect(() => {
if (lastRefresh === 0) return;
const fetchProcesses = async () => {
try {
const procs = await invoke<ProcessInfo[]>('get_processes');
setProcesses(procs);
} catch (error) {
console.error("Failed to fetch processes:", error);
}
};
fetchProcesses();
}, [lastRefresh]);
const formatBytes = (bytes: number) => {
const mb = bytes / (1024 * 1024);
if (mb >= 1024) {
return `${(mb / 1024).toFixed(1)} GB`;
}
return `${mb.toFixed(1)} MB`;
};
const filteredProcesses = processes
.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.pid.toString().includes(searchTerm)
)
.sort((a, b) => {
if (sortBy === 'cpu') return b.cpu_usage - a.cpu_usage;
return b.memory - a.memory;
});
const getCpuBadgeColor = (usage: number) => {
if (usage > 50) return 'bg-red-100 text-red-700 border-red-200';
if (usage > 20) return 'bg-amber-100 text-amber-700 border-amber-200';
if (usage > 5) return 'bg-emerald-100 text-emerald-700 border-emerald-200';
return 'bg-slate-100 text-slate-600 border-slate-200';
};
const getStatusBadge = (status: string) => {
const isRunning = status.toLowerCase().includes('run');
if (isRunning) {
return 'bg-emerald-100 text-emerald-700 border-emerald-200';
}
return 'bg-slate-100 text-slate-500 border-slate-200';
};
return (
<div className="flex flex-col h-full max-h-[500px]">
{/* Header */}
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-5 gap-4">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl shadow-lg shadow-purple-500/20">
<Activity className="h-5 w-5 text-white" />
</div>
<div>
<h2 className="text-base font-semibold text-slate-800">Running Processes</h2>
<p className="text-xs text-slate-500">{processes.length} active processes</p>
</div>
</div>
<div className="flex items-center gap-3 w-full sm:w-auto">
{/* Sort Buttons */}
<div className="flex rounded-lg border border-slate-200 bg-white overflow-hidden">
<button
onClick={() => setSortBy('cpu')}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors",
sortBy === 'cpu'
? "bg-purple-100 text-purple-700"
: "text-slate-600 hover:bg-slate-50"
)}
>
<Cpu className="h-3.5 w-3.5" />
CPU
</button>
<button
onClick={() => setSortBy('memory')}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors border-l border-slate-200",
sortBy === 'memory'
? "bg-purple-100 text-purple-700"
: "text-slate-600 hover:bg-slate-50"
)}
>
<HardDrive className="h-3.5 w-3.5" />
Memory
</button>
</div>
{/* Search */}
<div className="relative flex-1 sm:w-64">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
<input
type="text"
placeholder="Search processes..."
className="w-full bg-white border border-slate-200 rounded-lg pl-9 pr-3 py-1.5 text-sm text-slate-700
focus:outline-none focus:ring-2 focus:ring-purple-500/20 focus:border-purple-400
placeholder-slate-400 transition-all shadow-sm"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
</div>
{/* Table Container */}
<div className="flex-1 overflow-hidden border border-slate-200 rounded-xl bg-white shadow-sm flex flex-col">
<div className="overflow-auto custom-scrollbar flex-1">
<table className="w-full text-left text-sm">
<thead className="bg-slate-50 sticky top-0 z-10 border-b border-slate-200">
<tr>
<th className="px-5 py-3 text-xs font-semibold uppercase tracking-wider text-slate-500">PID</th>
<th className="px-5 py-3 text-xs font-semibold uppercase tracking-wider text-slate-500">Process Name</th>
<th className="px-5 py-3 text-right text-xs font-semibold uppercase tracking-wider text-slate-500">CPU</th>
<th className="px-5 py-3 text-right text-xs font-semibold uppercase tracking-wider text-slate-500">Memory</th>
<th className="px-5 py-3 text-center text-xs font-semibold uppercase tracking-wider text-slate-500">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{filteredProcesses.slice(0, 50).map((proc, index) => (
<tr
key={proc.pid}
className={cn(
"group hover:bg-purple-50/50 transition-colors",
index % 2 === 0 ? "bg-white" : "bg-slate-50/30"
)}
>
<td className="px-5 py-2.5 font-mono text-xs text-slate-500 group-hover:text-slate-700">
{proc.pid}
</td>
<td className="px-5 py-2.5 font-medium text-slate-700 group-hover:text-slate-900 truncate max-w-[200px]" title={proc.name}>
{proc.name}
</td>
<td className="px-5 py-2.5 text-right">
<span className={cn(
"inline-flex px-2 py-0.5 rounded-md text-xs font-semibold border",
getCpuBadgeColor(proc.cpu_usage)
)}>
{proc.cpu_usage.toFixed(1)}%
</span>
</td>
<td className="px-5 py-2.5 text-right font-mono text-xs text-slate-600">
{formatBytes(proc.memory)}
</td>
<td className="px-5 py-2.5 text-center">
<span className={cn(
"inline-flex items-center rounded-full px-2.5 py-0.5 text-[10px] font-semibold border",
getStatusBadge(proc.status)
)}>
{proc.status.replace(/"/g, '')}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Footer */}
<div className="bg-slate-50 border-t border-slate-200 px-5 py-2.5 flex justify-between items-center">
<span className="text-xs text-slate-500">
Showing <span className="font-semibold text-slate-700">{Math.min(filteredProcesses.length, 50)}</span> of <span className="font-semibold text-slate-700">{processes.length}</span> processes
</span>
<span className="text-xs text-slate-400">
Sorted by {sortBy === 'cpu' ? 'CPU usage' : 'memory usage'}
</span>
</div>
</div>
</div>
);
};
export default ProcessList;
Step 9: Main Application Component
Create the main App component that orchestrates all monitoring components with tab navigation and refresh controls. This brings all features together in a unified interface.
import { useEffect, useState, useRef } from 'react';
import Sidebar from './components/Sidebar';
import Tabs from './components/Tabs';
import Editor from './components/Editor';
import SettingsDialog from './components/SettingsDialog';
import { useStore } from './store/useStore';
import {
Files,
History,
Settings as SettingsIcon,
Search,
Moon,
Sun,
Code2,
FolderOpen,
FilePlus,
Save,
ChevronLeft,
ChevronRight
} from 'lucide-react';
function App() {
const {
theme,
initializeApp,
toggleSettings,
createNewFile,
openFile,
saveFile,
sidebarOpen,
toggleSidebar,
sidebarTab,
setSidebarTab,
toggleTheme
} = useStore();
useEffect(() => {
initializeApp();
}, [initializeApp]);
useEffect(() => {
document.documentElement.classList.toggle('dark', theme === 'dark');
}, [theme]);
// Global Keyboard Shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey || e.metaKey) {
switch (e.key.toLowerCase()) {
case 'n':
e.preventDefault();
createNewFile();
break;
case 'o':
e.preventDefault();
openFile();
break;
case 's':
e.preventDefault();
saveFile();
break;
case ',':
e.preventDefault();
toggleSettings();
break;
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [createNewFile, openFile, saveFile, toggleSettings]);
return (
<div className="h-screen flex bg-slate-50 dark:bg-[#0b0f1a] text-slate-900 dark:text-slate-100 overflow-hidden">
{/* Activity Bar (Left) */}
<div className="w-16 border-r border-slate-200 dark:border-slate-800 bg-white dark:bg-[#111827] flex flex-col items-center py-4 gap-4 z-50">
<div className="mb-4 p-2 bg-blue-500 rounded-xl shadow-lg shadow-blue-500/20">
<Code2 size={24} className="text-white" />
</div>
<div className="flex flex-col gap-2 flex-1">
<ActivityIcon
icon={<Files size={20} />}
active={sidebarOpen && sidebarTab === 'files'}
onClick={() => {
if (!sidebarOpen) toggleSidebar();
setSidebarTab('files');
}}
title="Explorer"
/>
<ActivityIcon
icon={<History size={20} />}
active={sidebarOpen && sidebarTab === 'recent'}
onClick={() => {
if (!sidebarOpen) toggleSidebar();
setSidebarTab('recent');
}}
title="Recent Files"
/>
<ActivityIcon
icon={<Search size={20} />}
active={false}
onClick={() => { }}
title="Search"
/>
</div>
<div className="flex flex-col gap-2">
<ActivityIcon
icon={theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />}
active={false}
onClick={toggleTheme}
title="Toggle Theme"
/>
<ActivityIcon
icon={<SettingsIcon size={20} />}
active={false}
onClick={toggleSettings}
title="Settings"
/>
</div>
</div>
{/* Main Content Area */}
<div className="flex-1 flex flex-col relative overflow-hidden">
{/* Custom Header / Title Bar */}
<div className="h-12 border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-[#111827] flex items-center justify-between px-4 z-40">
<div className="flex items-center gap-4">
<button
onClick={toggleSidebar}
className="p-1.5 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors text-slate-500"
>
{sidebarOpen ? <ChevronLeft size={18} /> : <ChevronRight size={18} />}
</button>
<div className="h-4 w-px bg-slate-200 dark:bg-slate-800" />
<div className="flex items-center gap-1">
<HeaderBtn icon={<FilePlus size={16} />} onClick={createNewFile} title="New" />
<HeaderBtn icon={<FolderOpen size={16} />} onClick={openFile} title="Open" />
<HeaderBtn icon={<Save size={16} />} onClick={saveFile} title="Save" />
</div>
</div>
<div className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest flex items-center gap-1.5 pointer-events-none select-none">
<span className="text-blue-500 font-black">{"{"}</span>
<span className="bg-clip-text bg-gradient-to-r from-slate-400 to-slate-500 dark:from-slate-500 dark:to-slate-400">Coders handbook</span>
<span className="text-blue-500 font-black">{"}"}</span>
<span className="ml-1 text-[10px] opacity-60">Text Editor</span>
</div>
<div className="w-24" /> {/* Spacer for balance */}
</div>
<div className="flex-1 flex overflow-hidden">
<Sidebar />
<div className="flex-1 flex flex-col bg-slate-50 dark:bg-[#0b101e] overflow-hidden">
<Tabs />
<div className="flex-1 relative">
<Editor />
</div>
{/* Status Bar */}
<StatusBar />
</div>
</div>
</div>
<SettingsDialog />
</div>
);
}
function StatusBar() {
const { tabs, activeTabId, updateTabLanguage } = useStore();
const [isOpen, setIsOpen] = useState(false);
const activeTab = tabs.find(t => t.id === activeTabId);
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
if (!activeTab) return null;
const supportedLanguages = [
'javascript', 'typescript', 'python', 'rust', 'go', 'java', 'html', 'css',
'json', 'markdown', 'yaml', 'toml', 'cpp', 'csharp', 'shell', 'php', 'ruby', 'sql'
];
return (
<div className="h-6 border-t border-slate-200 dark:border-slate-800 bg-white dark:bg-[#111827] flex items-center justify-between px-4 text-[10px] font-bold text-slate-500 dark:text-slate-400 select-none">
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<span className="opacity-50 uppercase tracking-widest text-[9px]">UTF-8</span>
</div>
<div className="flex items-center gap-1 border-l border-slate-200 dark:border-slate-800 pl-3">
<span className="opacity-50 uppercase tracking-widest text-[9px]">Spaces: 2</span>
</div>
</div>
<div className="flex items-center gap-3">
<div className="relative" ref={menuRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className={`flex items-center gap-1.5 hover:text-blue-500 transition-colors uppercase tracking-widest text-[9px] ${isOpen ? 'text-blue-500' : ''}`}
>
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
{activeTab.language}
</button>
{isOpen && (
<div className="absolute bottom-full right-0 mb-2 w-44 bg-white dark:bg-[#1e293b] border border-slate-200 dark:border-slate-800 rounded-xl shadow-[0_10px_40px_-10px_rgba(0,0,0,0.5)] overflow-hidden z-[100] animate-in slide-in-from-bottom-2 duration-200">
<div className="p-2.5 border-b border-slate-100 dark:border-slate-800 bg-slate-50 dark:bg-slate-900/50 text-[9px] uppercase tracking-widest text-slate-400 font-black">
Select Language
</div>
<div className="max-h-64 overflow-y-auto no-scrollbar py-1.5">
{supportedLanguages.map(lang => (
<button
key={lang}
onClick={() => {
updateTabLanguage(activeTab.id, lang);
setIsOpen(false);
}}
className={`w-full text-left px-4 py-2 hover:bg-blue-500 hover:text-white transition-all text-[11px] font-semibold capitalize flex items-center justify-between ${activeTab.language === lang ? 'text-blue-500 bg-blue-50/50 dark:bg-blue-500/10' : ''
}`}
>
{lang}
{activeTab.language === lang && <div className="w-1.5 h-1.5 rounded-full bg-blue-500" />}
</button>
))}
</div>
</div>
)}
</div>
</div>
</div>
);
}
function ActivityIcon({ icon, active, onClick, title }: { icon: any, active: boolean, onClick: () => void, title: string }) {
return (
<button
onClick={onClick}
title={title}
className={`p-3 rounded-xl transition-all duration-300 relative group ${active
? 'text-blue-500 bg-blue-50 dark:bg-blue-500/10'
: 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-300'
}`}
>
{icon}
{active && <div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 bg-blue-500 rounded-r-full" />}
{!active && (
<div className="absolute left-16 top-1/2 -translate-y-1/2 px-2 py-1 bg-slate-800 text-white text-[10px] rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap z-[100] pointer-events-none border border-slate-700">
{title}
</div>
)}
</button>
);
}
function HeaderBtn({ icon, onClick, title }: { icon: any, onClick: () => void, title: string }) {
return (
<button
onClick={onClick}
className="flex items-center gap-1.5 px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-all text-slate-600 dark:text-slate-400 text-xs font-medium active:scale-95"
>
{icon}
<span>{title}</span>
</button>
);
}
export default App;
Step 10: Application Styling
Apply custom CSS for modern UI design with gradients, animations, and responsive layouts. The styling creates a professional system monitor aesthetic.
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@import "tailwindcss";
:root {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 16px;
line-height: 1.5;
font-weight: 400;
/* Light theme color palette */
--bg-primary: #f8fafc;
--bg-secondary: #ffffff;
--bg-tertiary: #f1f5f9;
--border-color: #e2e8f0;
--border-hover: #cbd5e1;
--text-primary: #0f172a;
--text-secondary: #475569;
--text-muted: #94a3b8;
/* Accent colors */
--accent-emerald: #10b981;
--accent-blue: #3b82f6;
--accent-orange: #f97316;
--accent-purple: #8b5cf6;
--accent-red: #ef4444;
color-scheme: light;
color: var(--text-primary);
background-color: var(--bg-primary);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
}
/* Custom scrollbar for light theme */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* Glass effect for cards */
.glass-card {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
/* Subtle shadow */
.shadow-soft {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05),
0 2px 4px -2px rgba(0, 0, 0, 0.05),
0 0 0 1px rgba(0, 0, 0, 0.02);
}
.shadow-soft-lg {
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.08),
0 8px 10px -6px rgba(0, 0, 0, 0.05);
}
/* Gradient backgrounds for stats */
.gradient-emerald {
background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
}
.gradient-blue {
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
}
.gradient-orange {
background: linear-gradient(135deg, #ffedd5 0%, #fed7aa 100%);
}
.gradient-purple {
background: linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%);
}
/* Animated pulse ring */
@keyframes pulse-ring {
0% {
transform: scale(0.95);
opacity: 1;
}
50% {
transform: scale(1);
opacity: 0.5;
}
100% {
transform: scale(0.95);
opacity: 1;
}
}
.animate-pulse-ring {
animation: pulse-ring 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Smooth transitions */
* {
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
transition-duration: 150ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}Step 11: Utility Functions
Create utility functions for formatting data like bytes to human-readable format. These helpers improve code reusability.
import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Step 12: Tauri Configuration
Configure Tauri app settings including window dimensions, permissions, and build options. This finalizes the application configuration.
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "{Coders handbook} Text Editor",
"version": "0.1.0",
"identifier": "com.tauri.texteditor",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "{Coders handbook} Text Editor",
"width": 800,
"height": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/[email protected]",
"icons/icon.icns",
"icons/icon.ico"
]
}
}{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": [
"main"
],
"permissions": [
"core:default",
"opener:default",
"dialog:default",
"fs:allow-read-text-file",
"fs:allow-write-text-file",
"fs:allow-read-dir",
"fs:allow-exists",
"fs:allow-home-read-recursive",
"fs:allow-home-write-recursive",
"fs:allow-desktop-read-recursive",
"fs:allow-desktop-write-recursive",
"fs:allow-document-read-recursive",
"fs:allow-document-write-recursive",
"shell:default"
]
}{
"name": "tauri-app",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@monaco-editor/react": "^4.7.0",
"@tailwindcss/postcss": "^4.1.18",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.6.0",
"@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-opener": "^2",
"@tauri-apps/plugin-shell": "^2.3.4",
"lucide-react": "^0.563.0",
"monaco-editor": "^0.55.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"zustand": "^5.0.10"
},
"devDependencies": {
"@tauri-apps/cli": "^2",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
"autoprefixer": "^10.4.23",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.18",
"typescript": "~5.8.3",
"vite": "^7.0.4"
}
}
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
Step 13: Running and Testing
Run the system monitor in development mode to test all features. The app provides real-time monitoring of system resources.
# Install dependencies
npm install
# Run in development mode
npm run tauri dev
# Test all features:
# 1. View CPU usage and per-core metrics
# 2. Monitor memory and swap usage
# 3. Check disk space across all drives
# 4. View running processes
# 5. Sort processes by CPU/Memory
# 6. Kill processes (requires permissions)
# 7. Change refresh interval
# 8. Switch between monitoring tabs
# 9. View real-time charts updating
# 10. Test responsive UI on different sizesStep 14: Building for Production
Build optimized executables for Windows, macOS, and Linux. The system monitor creates lightweight native applications.
# Build for production
npm run tauri build
# Platform-specific outputs:
# Windows: src-tauri/target/release/bundle/nsis/*.exe
# macOS: src-tauri/target/release/bundle/dmg/*.dmg
# Linux: src-tauri/target/release/bundle/deb/*.deb
# src-tauri/target/release/bundle/appimage/*.AppImage
# Bundle size: ~8-15 MB (much smaller than Electron)
# Note: Some features may require elevated permissions:
# - Killing processes on Windows
# - Accessing certain system metrics on Linux
# - Consider requesting appropriate permissions in manifestKey Features Breakdown
| Feature | Description | Implementation |
|---|---|---|
| CPU Monitoring | Real-time CPU usage with per-core breakdown | sysinfo crate + recharts |
| Memory Tracking | RAM and swap usage with percentage display | System API + React Context |
| Disk Space | Available and used space for all drives | Filesystem enumeration |
| Process Management | List all running processes with CPU/memory usage | Process list API + sorting |
| Kill Processes | Terminate selected processes | Signal handling in Rust |
| Real-time Charts | Animated line charts updating every interval | Recharts library |
| Refresh Control | Adjustable update intervals (1s, 2s, 5s) | setInterval management |
| Tab Navigation | Switch between CPU, Memory, Disk, Processes | React state + tabs UI |
| Responsive Design | Adapts to different window sizes | CSS Grid + Flexbox |
| System Info | Display OS, hostname, uptime | System metadata APIs |
| Format Utilities | Human-readable byte sizes (KB, MB, GB) | Custom formatters |
System Metrics Collected
| Metric | Data Points | Refresh Rate |
|---|---|---|
| CPU | Overall usage, per-core usage, core count | 1-5 seconds |
| Memory | Total RAM, used RAM, available RAM, swap usage | 1-5 seconds |
| Disk | Mount point, total space, free space, used percentage | 5 seconds |
| Processes | PID, name, CPU%, memory usage, status | 2-5 seconds |
| System | OS name, version, hostname, uptime | On demand |
| Network | Interface name, received/sent bytes (future) | Not implemented |
Possible Enhancements
- Network Monitoring: Add network interface statistics with bandwidth usage graphs
- GPU Monitoring: Track GPU usage and temperature for systems with dedicated graphics
- Historical Data: Store metrics history and display trends over time
- Alerts System: Send notifications when CPU/memory exceeds thresholds
- Process Tree: Display parent-child process relationships in tree view
- Export Data: Export monitoring data to CSV/JSON for analysis
- System Tray: Add system tray icon with quick stats display
- Temperature Sensors: Monitor CPU/disk temperatures if available
- Battery Status: Show battery level and charging status on laptops
- Custom Themes: Allow users to customize color schemes and layouts
Troubleshooting Common Issues
- sysinfo crate errors: Ensure Rust toolchain is up to date; some Linux systems may need libdbus development files
- Permission denied killing processes: On Windows, run as administrator; on Linux/macOS, killing system processes requires root
- Charts not updating: Check browser console for errors; verify invoke commands are working with DevTools
- High CPU usage from monitor: Increase refresh interval to reduce polling frequency; optimize chart rendering
- Missing disk information: Some virtual drives may not report space correctly; verify mount points exist
- Build fails on Linux: Install pkg-config and libgtk-3-dev packages; check Tauri prerequisites for Linux
Next Steps
- Music Player: Build media playback app with music player project
- Note-Taking App: Learn database integration with notes project
- Text Editor: Build code editor with text editor project
- System Tray: Add background functionality with system tray guide
- Process Management: Deep dive into process control with process management tutorial
Conclusion
Building a system monitor with Tauri 2.0 demonstrates real-time data collection, system API integration, and performance monitoring creating professional resource tracking tool maintaining accuracy and efficiency. This project combines Rust's sysinfo crate providing cross-platform system metrics with minimal overhead, React Context API managing global state for monitoring data, recharts library rendering real-time animated charts with responsive design, Tauri IPC system enabling efficient data streaming between frontend and backend, process management implementing kill functionality with proper permissions, responsive UI adapting to different window sizes with modern aesthetics, and configurable refresh intervals allowing users to balance performance and responsiveness delivering comprehensive system monitor. Understanding system monitoring patterns including polling strategies with efficient intervals, data visualization with chart libraries and animations, state management with React Context for real-time updates, cross-platform system APIs handling differences between operating systems, process management with signals and permissions, and performance optimization minimizing monitor overhead establishes foundation for building system utility applications handling resource tracking maintaining professional experience through proper implementation techniques system administrators and developers depend on!
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


