Tauri Samples

Lightweight desktop application development examples using Tauri framework with Rust backend and web frontend

💻 Tauri Hello World Application rust

🟢 simple ⭐⭐

Basic Tauri application setup with minimal configuration and simple Rust-JavaScript communication

⏱️ 15 min 🏷️ tauri, desktop, rust, web
Prerequisites: Rust basics, HTML/CSS/JavaScript, Node.js
// Tauri Hello World Application Examples

// 1. Cargo.toml Dependencies
[package]
name = "tauri-app"
version = "0.1.0"
edition = "2021"

[build-dependencies]
tauri-build = { version = "1.0", features = [] }

[dependencies]
tauri = { version = "1.0", features = ["api-all"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[features]
default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]

// 2. tauri.conf.json - Application Configuration
{
  "build": {
    "beforeBuildCommand": "npm run build",
    "beforeDevCommand": "npm run dev",
    "devPath": "http://localhost:3000",
    "distDir": "../dist"
  },
  "package": {
    "productName": "Tauri App",
    "version": "0.1.0"
  },
  "tauri": {
    "allowlist": {
      "all": false,
      "shell": {
        "all": false,
        "open": true
      },
      "dialog": {
        "all": false,
        "open": true,
        "save": true
      }
    },
    "bundle": {
      "active": true,
      "category": "DeveloperTool",
      "copyright": "",
      "deb": {
        "depends": []
      },
      "externalBin": [],
      "icon": [
        "icons/32x32.png",
        "icons/128x128.png",
        "icons/[email protected]",
        "icons/icon.icns",
        "icons/icon.ico"
      ],
      "identifier": "com.tauri.app",
      "longDescription": "",
      "macOS": {
        "entitlements": null,
        "exceptionDomain": "",
        "frameworks": [],
        "providerShortName": null,
        "signingIdentity": null
      },
      "resources": [],
      "shortDescription": "",
      "targets": "all",
      "windows": {
        "certificateThumbprint": null,
        "digestAlgorithm": "sha256",
        "timestampUrl": ""
      }
    },
    "security": {
      "csp": null
    },
    "updater": {
      "active": false
    },
    "windows": [
      {
        "fullscreen": false,
        "height": 600,
        "resizable": true,
        "title": "Tauri App",
        "width": 800
      }
    ]
  }
}

// 3. main.rs - Rust Backend
#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

use tauri::Manager;

// Simple greeting command
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

// Command to get system info
#[tauri::command]
fn get_system_info() -> serde_json::Value {
    serde_json::json!({
        "platform": std::env::consts::OS,
        "arch": std::env::consts::ARCH,
        "version": env!("CARGO_PKG_VERSION"),
        "name": env!("CARGO_PKG_NAME")
    })
}

// Command with async operation
#[tauri::command]
async fn delayed_message(delay_ms: u64, message: String) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
    format!("Delayed message: {}", message)
}

// Command that returns Result
#[tauri::command]
fn validate_input(input: String) -> Result<String, String> {
    if input.trim().is_empty() {
        Err("Input cannot be empty".to_string())
    } else if input.len() > 100 {
        Err("Input too long (max 100 characters)".to_string())
    } else {
        Ok(format!("Valid input: {}", input))
    }
}

// File operations
#[tauri::command]
async fn read_file_content(path: String) -> Result<String, String> {
    use std::fs;

    match fs::read_to_string(&path) {
        Ok(content) => Ok(content),
        Err(e) => Err(format!("Failed to read file: {}", e))
    }
}

#[tauri::command]
async fn write_file_content(path: String, content: String) -> Result<(), String> {
    use std::fs;

    match fs::write(&path, content) {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("Failed to write file: {}", e))
    }
}

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            // Get the main window
            let window = app.get_window("main").unwrap();

            // Set window title
            window.set_title("Tauri Hello World").unwrap();

            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            greet,
            get_system_info,
            delayed_message,
            validate_input,
            read_file_content,
            write_file_content
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

// 4. build.rs - Build Script
fn main() {
    tauri_build::build()
}

// 5. HTML Frontend (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tauri App</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .input-group {
            margin: 20px 0;
        }
        input, button {
            padding: 10px;
            margin: 5px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        button {
            background-color: #007bff;
            color: white;
            cursor: pointer;
        }
        button:hover {
            background-color: #0056b3;
        }
        .result {
            margin-top: 10px;
            padding: 10px;
            background-color: #f8f9fa;
            border-radius: 5px;
            border-left: 4px solid #007bff;
        }
        .error {
            border-left-color: #dc3545;
            background-color: #f8d7da;
            color: #721c24;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Tauri Hello World Application</h1>

        <div class="input-group">
            <input type="text" id="nameInput" placeholder="Enter your name" value="Tauri">
            <button onclick="greetUser()">Greet</button>
        </div>
        <div id="greetResult" class="result" style="display: none;"></div>

        <div class="input-group">
            <button onclick="getSystemInfo()">Get System Info</button>
        </div>
        <div id="systemInfoResult" class="result" style="display: none;"></div>

        <div class="input-group">
            <input type="text" id="delayMessage" placeholder="Message for delay" value="Hello from the future!">
            <input type="number" id="delayMs" placeholder="Delay (ms)" value="2000">
            <button onclick="sendDelayedMessage()">Send Delayed Message</button>
        </div>
        <div id="delayResult" class="result" style="display: none;"></div>

        <div class="input-group">
            <input type="text" id="validationInput" placeholder="Text to validate (max 100 chars)">
            <button onclick="validateInput()">Validate</button>
        </div>
        <div id="validationResult" class="result" style="display: none;"></div>

        <div class="input-group">
            <input type="text" id="filePath" placeholder="File path">
            <button onclick="readFile()">Read File</button>
            <button onclick="writeFile()">Write File</button>
        </div>
        <div id="fileResult" class="result" style="display: none;"></div>
    </div>

    <script>
        const { invoke } = window.__TAURI__.core;

        async function greetUser() {
            const name = document.getElementById('nameInput').value;
            const resultDiv = document.getElementById('greetResult');

            try {
                const response = await invoke('greet', { name });
                resultDiv.textContent = response;
                resultDiv.className = 'result';
                resultDiv.style.display = 'block';
            } catch (error) {
                resultDiv.textContent = 'Error: ' + error;
                resultDiv.className = 'result error';
                resultDiv.style.display = 'block';
            }
        }

        async function getSystemInfo() {
            const resultDiv = document.getElementById('systemInfoResult');

            try {
                const info = await invoke('get_system_info');
                const formatted = 'Platform: ' + info.platform +
                               ', Arch: ' + info.arch +
                               ', Version: ' + info.version;
                resultDiv.textContent = formatted;
                resultDiv.className = 'result';
                resultDiv.style.display = 'block';
            } catch (error) {
                resultDiv.textContent = 'Error: ' + error;
                resultDiv.className = 'result error';
                resultDiv.style.display = 'block';
            }
        }

        async function sendDelayedMessage() {
            const message = document.getElementById('delayMessage').value;
            const delayMs = parseInt(document.getElementById('delayMs').value);
            const resultDiv = document.getElementById('delayResult');

            resultDiv.textContent = 'Sending delayed message...';
            resultDiv.className = 'result';
            resultDiv.style.display = 'block';

            try {
                const response = await invoke('delayed_message', { delayMs, message });
                resultDiv.textContent = response;
            } catch (error) {
                resultDiv.textContent = 'Error: ' + error;
                resultDiv.className = 'result error';
            }
        }

        async function validateInput() {
            const input = document.getElementById('validationInput').value;
            const resultDiv = document.getElementById('validationResult');

            try {
                const response = await invoke('validate_input', { input });
                resultDiv.textContent = response;
                resultDiv.className = 'result';
                resultDiv.style.display = 'block';
            } catch (error) {
                resultDiv.textContent = 'Validation Error: ' + error;
                resultDiv.className = 'result error';
                resultDiv.style.display = 'block';
            }
        }

        async function readFile() {
            const path = document.getElementById('filePath').value;
            const resultDiv = document.getElementById('fileResult');

            if (!path) {
                resultDiv.textContent = 'Please enter a file path';
                resultDiv.className = 'result error';
                resultDiv.style.display = 'block';
                return;
            }

            try {
                const content = await invoke('read_file_content', { path });
                resultDiv.textContent = 'File content:\n' + content;
                resultDiv.className = 'result';
                resultDiv.style.display = 'block';
            } catch (error) {
                resultDiv.textContent = 'Error reading file: ' + error;
                resultDiv.className = 'result error';
                resultDiv.style.display = 'block';
            }
        }

        async function writeFile() {
            const path = document.getElementById('filePath').value;
            const resultDiv = document.getElementById('fileResult');

            if (!path) {
                resultDiv.textContent = 'Please enter a file path';
                resultDiv.className = 'result error';
                resultDiv.style.display = 'block';
                return;
            }

            const content = 'Hello from Tauri! Written at ' + new Date().toISOString();

            try {
                await invoke('write_file_content', { path, content });
                resultDiv.textContent = 'File written successfully!';
                resultDiv.className = 'result';
                resultDiv.style.display = 'block';
            } catch (error) {
                resultDiv.textContent = 'Error writing file: ' + error;
                resultDiv.className = 'result error';
                resultDiv.style.display = 'block';
            }
        }
    </script>
</body>
</html>

// 6. package.json for Frontend Dependencies
{
  "name": "tauri-app",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "tauri": "tauri"
  },
  "dependencies": {
    "@tauri-apps/api": "^1.0.0"
  },
  "devDependencies": {
    "@tauri-apps/cli": "^1.0.0",
    "vite": "^4.0.0"
  }
}

// 7. Development Commands
/*
# Install dependencies
npm install

# Start development server
npm run tauri dev

# Build application
npm run tauri build

# Development only (frontend)
npm run dev
*/

💻 Tauri File Manager Application rust

🟡 intermediate ⭐⭐⭐⭐

Complete file manager application with drag-and-drop, file operations, and system integration

⏱️ 45 min 🏷️ tauri, file manager, desktop, rust
Prerequisites: Tauri basics, Rust async programming, Advanced HTML/CSS/JavaScript
// Tauri File Manager Application

// 1. main.rs - Enhanced Rust Backend
#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

use tauri::{Manager, Menu, MenuItem, Submenu};
use std::path::PathBuf;
use std::fs;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct FileInfo {
    name: String,
    path: String,
    is_dir: bool,
    size: u64,
    modified: String,
    extension: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
struct DirectoryContents {
    path: String,
    files: Vec<FileInfo>,
    parent: Option<String>,
}

// List directory contents
#[tauri::command]
async fn list_directory(path: String) -> Result<DirectoryContents, String> {
    let dir_path = PathBuf::from(&path);

    if !dir_path.exists() || !dir_path.is_dir() {
        return Err("Invalid directory path".to_string());
    }

    let mut files = Vec::new();

    match fs::read_dir(&dir_path) {
        Ok(entries) => {
            for entry in entries {
                if let Ok(entry) = entry {
                    let metadata = match entry.metadata() {
                        Ok(meta) => meta,
                        Err(_) => continue,
                    };

                    let file_name = entry.file_name().to_string_lossy().to_string();
                    let file_path = entry.path().to_string_lossy().to_string();
                    let is_dir = metadata.is_dir();
                    let size = metadata.len() as u64;

                    let modified = metadata.modified()
                        .ok()
                        .and_then(|time| {
                            time.duration_since(std::time::SystemTime::UNIX_EPOCH)
                                .ok()
                                .map(|d| d.as_secs())
                        })
                        .map(|timestamp| {
                            chrono::DateTime::from_timestamp(timestamp as i64, 0)
                                .unwrap_or_default()
                                .format("%Y-%m-%d %H:%M:%S")
                                .to_string()
                        })
                        .unwrap_or_else(|| "Unknown".to_string());

                    let extension = if !is_dir {
                        PathBuf::from(&file_name)
                            .extension()
                            .and_then(|ext| ext.to_str())
                            .map(|s| s.to_lowercase())
                    } else {
                        None
                    };

                    files.push(FileInfo {
                        name: file_name,
                        path: file_path,
                        is_dir,
                        size,
                        modified,
                        extension,
                    });
                }
            }
        }
        Err(e) => return Err(format!("Failed to read directory: {}", e)),
    }

    // Sort files: directories first, then files, both alphabetically
    files.sort_by(|a, b| {
        match (a.is_dir, b.is_dir) {
            (true, false) => std::cmp::Ordering::Less,
            (false, true) => std::cmp::Ordering::Greater,
            _ => a.name.cmp(&b.name),
        }
    });

    let parent = dir_path.parent()
        .map(|p| p.to_string_lossy().to_string());

    Ok(DirectoryContents {
        path,
        files,
        parent,
    })
}

// Create directory
#[tauri::command]
async fn create_directory(path: String) -> Result<(), String> {
    match fs::create_dir(&path) {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("Failed to create directory: {}", e))
    }
}

// Delete file or directory
#[tauri::command]
async fn delete_item(path: String, is_dir: bool) -> Result<(), String> {
    let path_buf = PathBuf::from(&path);

    let result = if is_dir {
        fs::remove_dir_all(&path_buf)
    } else {
        fs::remove_file(&path_buf)
    };

    match result {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("Failed to delete item: {}", e))
    }
}

// Copy file
#[tauri::command]
async fn copy_file(source: String, destination: String) -> Result<(), String> {
    match fs::copy(&source, &destination) {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("Failed to copy file: {}", e))
    }
}

// Move/rename file
#[tauri::command]
async fn move_file(source: String, destination: String) -> Result<(), String> {
    match fs::rename(&source, &destination) {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("Failed to move file: {}", e))
    }
}

// Get file content (for text files)
#[tauri::command]
async fn get_file_content(path: String) -> Result<String, String> {
    match fs::read_to_string(&path) {
        Ok(content) => Ok(content),
        Err(e) => Err(format!("Failed to read file: {}", e))
    }
}

// Save file content
#[tauri::command]
async fn save_file_content(path: String, content: String) -> Result<(), String> {
    match fs::write(&path, content) {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("Failed to write file: {}", e))
    }
}

// Get system drives/volumes
#[tauri::command]
async fn get_system_drives() -> Result<Vec<String>, String> {
    #[cfg(windows)]
    {
        use std::ffi::OsStr;
        use std::os::windows::ffi::OsStrExt;

        let mut drives = Vec::new();
        let mut buffer = [0u16; 256];

        unsafe {
            let result = windows_sys::Win32::Storage::FileSystem::GetLogicalDriveStringsW(
                buffer.len() as u32,
                buffer.as_mut_ptr()
            );

            if result > 0 {
                let mut pos = 0;
                while pos < result as usize {
                    let mut end = pos;
                    while buffer[end] != 0 && end < buffer.len() {
                        end += 1;
                    }

                    if end > pos {
                        let drive_str = OsStr::new_wide(&buffer[pos..end])
                            .to_string_lossy()
                            .to_string();
                        drives.push(drive_str);
                    }

                    pos = end + 1;
                }
            }
        }

        Ok(drives)
    }

    #[cfg(unix)]
    {
        Ok(vec!["/".to_string()])
    }

    #[cfg(not(any(windows, unix)))]
    {
        Ok(vec![])
    }
}

// Open file with default application
#[tauri::command]
async fn open_file_with_default(path: String) -> Result<(), String> {
    #[cfg(windows)]
    {
        use std::process::Command;

        match Command::new("cmd")
            .args(["/C", "start", "", &path])
            .spawn()
        {
            Ok(_) => Ok(()),
            Err(e) => Err(format!("Failed to open file: {}", e))
        }
    }

    #[cfg(unix)]
    {
        use std::process::Command;

        match Command::new("open")
            .arg(&path)
            .spawn()
        {
            Ok(_) => Ok(()),
            Err(e) => Err(format!("Failed to open file: {}", e))
        }
    }

    #[cfg(not(any(windows, unix)))]
    {
        Err("Platform not supported".to_string())
    }
}

fn main() {
    // Create application menu
    let menu = Menu::new()
        .add_submenu(Submenu::new(
            "File",
            Menu::new()
                .add_native_item(MenuItem::Separator)
                .add_item(MenuItem::new("Exit", true, None, None, None, None, None, None))
        ))
        .add_submenu(Submenu::new(
            "Help",
            Menu::new()
                .add_item(MenuItem::new("About", true, None, None, None, None, None, None))
        ));

    tauri::Builder::default()
        .menu(menu)
        .setup(|app| {
            let window = app.get_window("main").unwrap();

            // Set window properties
            window.set_title("Tauri File Manager").unwrap();
            window.set_min_size(Some((800.0, 600.0)).into()).unwrap();

            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            list_directory,
            create_directory,
            delete_item,
            copy_file,
            move_file,
            get_file_content,
            save_file_content,
            get_system_drives,
            open_file_with_default
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

// 2. HTML Frontend with Enhanced UI
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tauri File Manager</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            height: 100vh;
            display: flex;
            flex-direction: column;
            background-color: #f5f5f5;
        }

        .header {
            background: #2c3e50;
            color: white;
            padding: 10px 20px;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .path-bar {
            flex: 1;
            background: #34495e;
            border: none;
            color: white;
            padding: 8px 12px;
            border-radius: 4px;
            font-family: inherit;
        }

        .main-container {
            flex: 1;
            display: flex;
            overflow: hidden;
        }

        .sidebar {
            width: 200px;
            background: white;
            border-right: 1px solid #ddd;
            overflow-y: auto;
        }

        .drive-item {
            padding: 10px 20px;
            cursor: pointer;
            border-bottom: 1px solid #f0f0f0;
        }

        .drive-item:hover {
            background: #e3f2fd;
        }

        .drive-item.active {
            background: #2196f3;
            color: white;
        }

        .file-list {
            flex: 1;
            background: white;
            overflow: auto;
        }

        .file-item {
            display: flex;
            align-items: center;
            padding: 12px 20px;
            border-bottom: 1px solid #f0f0f0;
            cursor: pointer;
            user-select: none;
        }

        .file-item:hover {
            background: #f5f5f5;
        }

        .file-item.selected {
            background: #e3f2fd;
        }

        .file-item.dragging {
            opacity: 0.5;
        }

        .file-icon {
            width: 20px;
            height: 20px;
            margin-right: 12px;
            font-size: 16px;
        }

        .file-info {
            flex: 1;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .file-name {
            font-weight: 500;
        }

        .file-meta {
            font-size: 12px;
            color: #666;
            display: flex;
            gap: 15px;
        }

        .context-menu {
            position: fixed;
            background: white;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            z-index: 1000;
            display: none;
        }

        .context-menu-item {
            padding: 10px 20px;
            cursor: pointer;
            border-bottom: 1px solid #f0f0f0;
        }

        .context-menu-item:hover {
            background: #f0f0f0;
        }

        .context-menu-item:last-child {
            border-bottom: none;
        }

        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
            z-index: 2000;
            align-items: center;
            justify-content: center;
        }

        .modal-content {
            background: white;
            padding: 30px;
            border-radius: 8px;
            min-width: 400px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.2);
        }

        .modal-header {
            margin-bottom: 20px;
        }

        .modal-body {
            margin-bottom: 20px;
        }

        .modal-footer {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
        }

        input, textarea {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-family: inherit;
        }

        button {
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-family: inherit;
        }

        .btn-primary {
            background: #2196f3;
            color: white;
        }

        .btn-primary:hover {
            background: #1976d2;
        }

        .btn-secondary {
            background: #6c757d;
            color: white;
        }

        .btn-secondary:hover {
            background: #5a6268;
        }

        .btn-danger {
            background: #dc3545;
            color: white;
        }

        .btn-danger:hover {
            background: #c82333;
        }

        .toolbar {
            background: white;
            padding: 10px 20px;
            border-bottom: 1px solid #ddd;
            display: flex;
            gap: 10px;
        }

        .toolbar button {
            padding: 8px 16px;
        }

        .loading {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 100%;
            font-size: 18px;
            color: #666;
        }
    </style>
</head>
<body>
    <div class="header">
        <input type="text" class="path-bar" id="pathBar" readonly>
        <button onclick="navigateUp()">↑</button>
        <button onclick="refreshDirectory()">↻</button>
    </div>

    <div class="toolbar">
        <button onclick="createFolder()">📁 New Folder</button>
        <button onclick="createFile()">📄 New File</button>
        <button onclick="deleteSelected()">🗑️ Delete</button>
        <button onclick="copySelected()">📋 Copy</button>
        <button onclick="pasteItems()">📄 Paste</button>
    </div>

    <div class="main-container">
        <div class="sidebar" id="sidebar">
            <!-- Drives will be populated here -->
        </div>

        <div class="file-list" id="fileList">
            <div class="loading">Loading...</div>
        </div>
    </div>

    <div class="context-menu" id="contextMenu">
        <div class="context-menu-item" onclick="openItem()">Open</div>
        <div class="context-menu-item" onclick="renameItem()">Rename</div>
        <div class="context-menu-item" onclick="deleteItem()">Delete</div>
        <div class="context-menu-item" onclick="copyItem()">Copy</div>
    </div>

    <!-- Create/Name Modal -->
    <div class="modal" id="nameModal">
        <div class="modal-content">
            <div class="modal-header">
                <h2 id="modalTitle">Create New</h2>
            </div>
            <div class="modal-body">
                <input type="text" id="nameInput" placeholder="Enter name">
            </div>
            <div class="modal-footer">
                <button class="btn-secondary" onclick="closeModal()">Cancel</button>
                <button class="btn-primary" onclick="confirmNameAction()">Create</button>
            </div>
        </div>
    </div>

    <!-- File Editor Modal -->
    <div class="modal" id="editorModal">
        <div class="modal-content" style="width: 80%; height: 80%;">
            <div class="modal-header">
                <h2 id="editorTitle">Edit File</h2>
            </div>
            <div class="modal-body" style="height: calc(100% - 120px);">
                <textarea id="editorContent" style="height: 100%; resize: none;"></textarea>
            </div>
            <div class="modal-footer">
                <button class="btn-secondary" onclick="closeEditor()">Cancel</button>
                <button class="btn-primary" onclick="saveFile()">Save</button>
            </div>
        </div>
    </div>

    <script>
        const { invoke } = window.__TAURI__.core;

        let currentPath = '';
        let selectedFile = null;
        let clipboard = null;

        // Initialize application
        async function init() {
            await loadSystemDrives();
            setupEventListeners();
        }

        // Load system drives
        async function loadSystemDrives() {
            try {
                const drives = await invoke('get_system_drives');
                const sidebar = document.getElementById('sidebar');
                sidebar.innerHTML = '';

                drives.forEach(drive => {
                    const item = document.createElement('div');
                    item.className = 'drive-item';
                    item.textContent = drive;
                    item.onclick = () => navigateToPath(drive);
                    sidebar.appendChild(item);
                });

                // Navigate to first drive
                if (drives.length > 0) {
                    navigateToPath(drives[0]);
                }
            } catch (error) {
                console.error('Failed to load drives:', error);
            }
        }

        // Navigate to specific path
        async function navigateToPath(path) {
            currentPath = path;
            document.getElementById('pathBar').value = path;

            // Update sidebar active state
            document.querySelectorAll('.drive-item').forEach(item => {
                item.classList.toggle('active', item.textContent.trim() === path.trim());
            });

            await loadDirectory(path);
        }

        // Load directory contents
        async function loadDirectory(path) {
            const fileList = document.getElementById('fileList');
            fileList.innerHTML = '<div class="loading">Loading...</div>';

            try {
                const contents = await invoke('list_directory', { path });
                renderFileList(contents);
            } catch (error) {
                fileList.innerHTML = '<div class="loading">Error: ' + error + '</div>';
            }
        }

        // Render file list
        function renderFileList(contents) {
            const fileList = document.getElementById('fileList');
            fileList.innerHTML = '';

            if (contents.parent) {
                const parentItem = createFileItem({
                    name: '..',
                    path: contents.parent,
                    is_dir: true,
                    size: 0,
                    modified: '',
                    extension: null
                });
                fileList.appendChild(parentItem);
            }

            contents.files.forEach(file => {
                const fileItem = createFileItem(file);
                fileList.appendChild(fileItem);
            });
        }

        // Create file item element
        function createFileItem(file) {
            const item = document.createElement('div');
            item.className = 'file-item';
            item.dataset.path = file.path;
            item.dataset.isDirectory = file.is_dir;

            const icon = file.is_dir ? '📁' : getFileIcon(file.extension);
            const size = file.is_dir ? '—' : formatFileSize(file.size);

            item.innerHTML = `
                <div class="file-icon">${icon}</div>
                <div class="file-info">
                    <span class="file-name">${file.name}</span>
                    <div class="file-meta">
                        <span>${size}</span>
                        <span>${file.modified}</span>
                    </div>
                </div>
            `;

            item.onclick = () => handleFileClick(file);
            item.ondblclick = () => handleFileDoubleClick(file);
            item.oncontextmenu = (e) => showContextMenu(e, file);

            // Drag and drop
            item.draggable = true;
            item.ondragstart = (e) => handleDragStart(e, file);
            item.ondragover = (e) => handleDragOver(e);
            item.ondrop = (e) => handleDrop(e, file);
            item.ondragend = handleDragEnd;

            return item;
        }

        // Get file icon based on extension
        function getFileIcon(extension) {
            const iconMap = {
                'txt': '📄',
                'pdf': '📑',
                'doc': '📝',
                'docx': '📝',
                'xls': '📊',
                'xlsx': '📊',
                'jpg': '🖼️',
                'jpeg': '🖼️',
                'png': '🖼️',
                'gif': '🖼️',
                'mp4': '🎬',
                'mp3': '🎵',
                'zip': '🗜️',
                'exe': '⚙️',
                'js': '📜',
                'html': '🌐',
                'css': '🎨',
                'json': '📋',
                'md': '📖'
            };

            return iconMap[extension] || '📄';
        }

        // Format file size
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 B';

            const k = 1024;
            const sizes = ['B', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));

            return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
        }

        // Handle file click (selection)
        function handleFileClick(file) {
            document.querySelectorAll('.file-item').forEach(item => {
                item.classList.remove('selected');
            });

            const item = document.querySelector(`[data-path="${file.path}"]`);
            if (item) {
                item.classList.add('selected');
                selectedFile = file;
            }
        }

        // Handle file double click (open)
        async function handleFileDoubleClick(file) {
            if (file.is_dir) {
                await navigateToPath(file.path);
            } else {
                await openFile(file);
            }
        }

        // Open file
        async function openFile(file) {
            const textExtensions = ['txt', 'js', 'html', 'css', 'json', 'md', 'xml', 'csv', 'log'];

            if (textExtensions.includes(file.extension || '')) {
                try {
                    const content = await invoke('get_file_content', { path: file.path });
                    showFileEditor(file, content);
                } catch (error) {
                    console.error('Failed to open file:', error);
                }
            } else {
                try {
                    await invoke('open_file_with_default', { path: file.path });
                } catch (error) {
                    console.error('Failed to open file with default app:', error);
                }
            }
        }

        // Show file editor
        function showFileEditor(file, content) {
            document.getElementById('editorTitle').textContent = `Edit: ${file.name}`;
            document.getElementById('editorContent').value = content;
            document.getElementById('editorModal').style.display = 'flex';

            // Store current file path for saving
            window.currentEditingFile = file.path;
        }

        // Save file
        async function saveFile() {
            if (!window.currentEditingFile) return;

            const content = document.getElementById('editorContent').value;

            try {
                await invoke('save_file_content', {
                    path: window.currentEditingFile,
                    content
                });
                closeEditor();
            } catch (error) {
                alert('Failed to save file: ' + error);
            }
        }

        // Close editor
        function closeEditor() {
            document.getElementById('editorModal').style.display = 'none';
            delete window.currentEditingFile;
        }

        // Navigate up
        function navigateUp() {
            const parts = currentPath.split(/[/\\]/);
            if (parts.length > 1) {
                parts.pop();
                const parentPath = parts.join('/') || (currentPath.startsWith('/') ? '/' : '');
                navigateToPath(parentPath);
            }
        }

        // Refresh directory
        function refreshDirectory() {
            loadDirectory(currentPath);
        }

        // Context menu
        function showContextMenu(event, file) {
            event.preventDefault();
            selectedFile = file;

            const menu = document.getElementById('contextMenu');
            menu.style.display = 'block';
            menu.style.left = event.pageX + 'px';
            menu.style.top = event.pageY + 'px';
        }

        // Close context menu
        function hideContextMenu() {
            document.getElementById('contextMenu').style.display = 'none';
        }

        // Context menu actions
        async function openItem() {
            if (selectedFile) {
                await handleFileDoubleClick(selectedFile);
            }
            hideContextMenu();
        }

        async function deleteItem() {
            if (selectedFile && confirm(`Delete '${selectedFile.name}'?`)) {
                try {
                    await invoke('delete_item', {
                        path: selectedFile.path,
                        is_dir: selectedFile.is_dir
                    });
                    await loadDirectory(currentPath);
                } catch (error) {
                    alert('Failed to delete: ' + error);
                }
            }
            hideContextMenu();
        }

        function renameItem() {
            if (selectedFile) {
                showNameModal(`Rename '${selectedFile.name}'`, selectedFile.name, (newName) => {
                    const newPath = currentPath + '/' + newName;
                    moveSelectedFile(newPath);
                });
            }
            hideContextMenu();
        }

        function copyItem() {
            if (selectedFile) {
                clipboard = { ...selectedFile };
            }
            hideContextMenu();
        }

        // Drag and drop handlers
        function handleDragStart(event, file) {
            event.dataTransfer.effectAllowed = 'move';
            event.dataTransfer.setData('text/plain', file.path);
            event.target.classList.add('dragging');
        }

        function handleDragOver(event) {
            event.preventDefault();
            event.dataTransfer.dropEffect = 'move';
        }

        async function handleDrop(event, targetFile) {
            event.preventDefault();

            if (!targetFile.is_dir) return;

            const sourcePath = event.dataTransfer.getData('text/plain');

            try {
                const fileName = sourcePath.split(/[/\\]/).pop();
                const destinationPath = targetFile.path + '/' + fileName;

                await invoke('move_file', { source: sourcePath, destination: destinationPath });
                await loadDirectory(currentPath);
            } catch (error) {
                alert('Failed to move file: ' + error);
            }
        }

        function handleDragEnd(event) {
            event.target.classList.remove('dragging');
        }

        // Modal functions
        function showNameModal(title, defaultValue, callback) {
            document.getElementById('modalTitle').textContent = title;
            document.getElementById('nameInput').value = defaultValue || '';
            document.getElementById('nameModal').style.display = 'flex';

            window.nameModalCallback = callback;
        }

        function closeModal() {
            document.getElementById('nameModal').style.display = 'none';
            delete window.nameModalCallback;
        }

        function confirmNameAction() {
            const name = document.getElementById('nameInput').value.trim();

            if (name && window.nameModalCallback) {
                window.nameModalCallback(name);
            }

            closeModal();
        }

        // Toolbar actions
        function createFolder() {
            showNameModal('Create New Folder', '', (name) => {
                const path = currentPath + '/' + name;
                invoke('create_directory', { path })
                    .then(() => loadDirectory(currentPath))
                    .catch(error => alert('Failed to create folder: ' + error));
            });
        }

        function createFile() {
            showNameModal('Create New File', 'untitled.txt', (name) => {
                const path = currentPath + '/' + name;
                invoke('save_file_content', { path, content: '' })
                    .then(() => loadDirectory(currentPath))
                    .catch(error => alert('Failed to create file: ' + error));
            });
        }

        function deleteSelected() {
            if (selectedFile) {
                deleteItem();
            }
        }

        function copySelected() {
            if (selectedFile) {
                clipboard = { ...selectedFile };
            }
        }

        async function pasteItems() {
            if (!clipboard) return;

            const destinationPath = currentPath + '/' + clipboard.name;

            try {
                await invoke('copy_file', {
                    source: clipboard.path,
                    destination: destinationPath
                });
                await loadDirectory(currentPath);
            } catch (error) {
                alert('Failed to paste: ' + error);
            }
        }

        // Setup event listeners
        function setupEventListeners() {
            // Hide context menu on click outside
            document.addEventListener('click', hideContextMenu);

            // Keyboard shortcuts
            document.addEventListener('keydown', (event) => {
                if (event.key === 'Delete' && selectedFile) {
                    deleteSelected();
                } else if (event.ctrlKey && event.key === 'c' && selectedFile) {
                    copySelected();
                } else if (event.ctrlKey && event.key === 'v' && clipboard) {
                    pasteItems();
                }
            });

            // Modal close on escape
            document.addEventListener('keydown', (event) => {
                if (event.key === 'Escape') {
                    closeModal();
                    closeEditor();
                }
            });
        }

        // Initialize when DOM is loaded
        document.addEventListener('DOMContentLoaded', init);
    </script>
</body>
</html>

💻 Tauri System Monitor Application rust

🔴 complex ⭐⭐⭐⭐⭐

Real-time system monitoring dashboard with CPU, memory, disk usage and process management

⏱️ 60 min 🏷️ tauri, system monitor, rust, performance
Prerequisites: Advanced Tauri, Rust async programming, Chart.js, System monitoring concepts
// Tauri System Monitor Application

// 1. Cargo.toml with additional dependencies
[package]
name = "tauri-system-monitor"
version = "0.1.0"
edition = "2021"

[dependencies]
tauri = { version = "1.0", features = ["api-all"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sysinfo = "0.29"
chrono = { version = "0.4", features = ["serde"] }

[build-dependencies]
tauri-build = { version = "1.0", features = [] }

[features]
default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]

// 2. main.rs - System Monitor Backend
#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

use tauri::{Manager, State, Window};
use sysinfo::{System, SystemExt, ProcessExt, CpuExt, DiskExt, NetworkExt};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use serde::{Deserialize, Serialize};

// Shared system state
pub struct AppState {
    system: Arc<Mutex<System>>,
}

#[derive(Debug, Serialize, Deserialize)]
struct SystemInfo {
    cpu_usage: f32,
    memory_total: u64,
    memory_used: u64,
    memory_usage: f32,
    disk_total: u64,
    disk_used: u64,
    disk_usage: f32,
    uptime: u64,
    process_count: usize,
    load_average: [f64; 3],
}

#[derive(Debug, Serialize, Deserialize)]
struct ProcessInfo {
    pid: u32,
    name: String,
    cpu_usage: f32,
    memory_usage: u64,
    memory_percent: f32,
    status: String,
    start_time: u64,
}

#[derive(Debug, Serialize, Deserialize)]
struct DiskInfo {
    name: String,
    mount_point: String,
    file_system: String,
    total_space: u64,
    available_space: u64,
    used_space: u64,
    usage_percent: f32,
}

#[derive(Debug, Serialize, Deserialize)]
struct NetworkInfo {
    interface: String,
    data_received: u64,
    data_transmitted: u64,
    packets_received: u64,
    packets_transmitted: u64,
    errors_on_received: u64,
    errors_on_transmitted: u64,
}

// Get overall system information
#[tauri::command]
async fn get_system_info(state: State<'_, AppState>) -> Result<SystemInfo, String> {
    let system = state.system.lock().map_err(|e| e.to_string())?;
    system.refresh_all();

    let cpu_usage = system.global_cpu_info().cpu_usage();
    let memory_total = system.total_memory();
    let memory_used = system.used_memory();
    let memory_usage = (memory_used as f32 / memory_total as f32) * 100.0;

    // Calculate disk usage
    let mut disk_total = 0u64;
    let mut disk_used = 0u64;

    for disk in system.disks() {
        disk_total += disk.total_space();
        disk_used += disk.total_space() - disk.available_space();
    }

    let disk_usage = if disk_total > 0 {
        (disk_used as f32 / disk_total as f32) * 100.0
    } else {
        0.0
    };

    let uptime = system.uptime();
    let process_count = system.processes().len();

    // Get load average (Unix systems only)
    let load_average = sysinfo::get_current_pid()
        .ok()
        .and_then(|pid| system.process(pid))
        .map(|process| {
            // This is a simplified approach - in production you'd use platform-specific APIs
            [0.0, 0.0, 0.0] // Placeholder
        })
        .unwrap_or([0.0, 0.0, 0.0]);

    Ok(SystemInfo {
        cpu_usage,
        memory_total,
        memory_used,
        memory_usage,
        disk_total,
        disk_used,
        disk_usage,
        uptime,
        process_count,
        load_average,
    })
}

// Get process list
#[tauri::command]
async fn get_processes(state: State<'_, AppState>) -> Result<Vec<ProcessInfo>, String> {
    let system = state.system.lock().map_err(|e| e.to_string())?;
    system.refresh_processes();

    let total_memory = system.total_memory();
    let mut processes = Vec::new();

    for (pid, process) in system.processes() {
        let memory_percent = (process.memory() as f32 / total_memory as f32) * 100.0;

        processes.push(ProcessInfo {
            pid: pid.as_u32(),
            name: process.name().to_string(),
            cpu_usage: process.cpu_usage(),
            memory_usage: process.memory(),
            memory_percent,
            status: format!("{:?}", process.status()),
            start_time: process.start_time(),
        });
    }

    // Sort by CPU usage (descending)
    processes.sort_by(|a, b| b.cpu_usage.partial_cmp(&a.cpu_usage).unwrap_or(std::cmp::Ordering::Equal));

    // Limit to top 50 processes
    processes.truncate(50);

    Ok(processes)
}

// Get disk information
#[tauri::command]
async fn get_disks(state: State<'_, AppState>) -> Result<Vec<DiskInfo>, String> {
    let system = state.system.lock().map_err(|e| e.to_string())?;
    system.refresh_disks();

    let mut disks = Vec::new();

    for disk in system.disks() {
        let total_space = disk.total_space();
        let available_space = disk.available_space();
        let used_space = total_space - available_space;
        let usage_percent = if total_space > 0 {
            (used_space as f32 / total_space as f32) * 100.0
        } else {
            0.0
        };

        disks.push(DiskInfo {
            name: disk.name().to_string_lossy().to_string(),
            mount_point: disk.mount_point().to_string_lossy().to_string(),
            file_system: std::str::from_utf8(disk.file_system()).unwrap_or("Unknown").to_string(),
            total_space,
            available_space,
            used_space,
            usage_percent,
        });
    }

    Ok(disks)
}

// Get network information
#[tauri::command]
async fn get_network_info(state: State<'_, AppState>) -> Result<Vec<NetworkInfo>, String> {
    let system = state.system.lock().map_err(|e| e.to_string())?;
    system.refresh_networks();

    let mut networks = Vec::new();

    for (interface_name, data) in system.networks() {
        networks.push(NetworkInfo {
            interface: interface_name.clone(),
            data_received: data.total_received(),
            data_transmitted: data.total_transmitted(),
            packets_received: data.total_packets_received(),
            packets_transmitted: data.total_packets_transmitted(),
            errors_on_received: data.errors_on_received(),
            errors_on_transmitted: data.errors_on_transmitted(),
        });
    }

    Ok(networks)
}

// Kill process
#[tauri::command]
async fn kill_process(pid: u32, state: State<'_, AppState>) -> Result<bool, String> {
    let system = state.system.lock().map_err(|e| e.to_string())?;

    // Convert u32 to sysinfo's Pid type
    if let Some(sys_pid) = sysinfo::Pid::from_u32(pid) {
        if system.process(sys_pid).is_some() {
            // In a real implementation, you would use platform-specific APIs to kill the process
            // For now, we'll just return true
            Ok(true)
        } else {
            Err("Process not found".to_string())
        }
    } else {
        Err("Invalid PID".to_string())
    }
}

// Start background monitoring
#[tauri::command]
async fn start_monitoring(window: Window, state: State<'_, AppState>) -> Result<(), String> {
    let window_clone = window.clone();
    let system_state = state.inner().clone();

    thread::spawn(move || {
        loop {
            // Update system info every 2 seconds
            if let Err(_) = window_clone.emit("system-update", "refresh") {
                // Window closed, exit thread
                break;
            }

            thread::sleep(Duration::from_secs(2));
        }
    });

    Ok(())
}

fn main() {
    let mut system = System::new();
    system.refresh_all();

    let app_state = AppState {
        system: Arc::new(Mutex::new(system)),
    };

    tauri::Builder::default()
        .manage(app_state)
        .setup(|app| {
            let window = app.get_window("main").unwrap();

            // Set window properties
            window.set_title("System Monitor").unwrap();
            window.set_min_size(Some((1000.0, 700.0)).into()).unwrap();

            // Start monitoring
            let state = app.state::<AppState>();
            let window_clone = window.clone();
            tauri::async_runtime::spawn(async move {
                if let Err(e) = start_monitoring(window_clone, state).await {
                    eprintln!("Failed to start monitoring: {}", e);
                }
            });

            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            get_system_info,
            get_processes,
            get_disks,
            get_network_info,
            kill_process,
            start_monitoring
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

// 3. System Monitor Frontend
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>System Monitor</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: #1e1e1e;
            color: #ffffff;
            overflow: hidden;
        }

        .container {
            height: 100vh;
            display: flex;
            flex-direction: column;
        }

        .header {
            background: #2d2d30;
            padding: 15px 20px;
            border-bottom: 1px solid #3e3e42;
        }

        .header h1 {
            font-size: 18px;
            font-weight: 400;
        }

        .main-content {
            flex: 1;
            display: flex;
            overflow: hidden;
        }

        .sidebar {
            width: 250px;
            background: #252526;
            border-right: 1px solid #3e3e42;
            overflow-y: auto;
        }

        .nav-item {
            padding: 12px 20px;
            cursor: pointer;
            border-left: 3px solid transparent;
            transition: all 0.2s;
        }

        .nav-item:hover {
            background: #2d2d30;
        }

        .nav-item.active {
            background: #2d2d30;
            border-left-color: #007acc;
        }

        .content {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
        }

        .dashboard {
            display: none;
        }

        .dashboard.active {
            display: block;
        }

        .metrics-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }

        .metric-card {
            background: #2d2d30;
            border: 1px solid #3e3e42;
            border-radius: 8px;
            padding: 20px;
        }

        .metric-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
        }

        .metric-title {
            font-size: 14px;
            color: #cccccc;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .metric-value {
            font-size: 24px;
            font-weight: bold;
            color: #ffffff;
        }

        .metric-unit {
            font-size: 14px;
            color: #cccccc;
            margin-left: 5px;
        }

        .chart-container {
            height: 200px;
            margin-top: 15px;
        }

        .process-table {
            width: 100%;
            background: #2d2d30;
            border: 1px solid #3e3e42;
            border-radius: 8px;
            overflow: hidden;
        }

        .process-table table {
            width: 100%;
            border-collapse: collapse;
        }

        .process-table th,
        .process-table td {
            padding: 12px 15px;
            text-align: left;
            border-bottom: 1px solid #3e3e42;
        }

        .process-table th {
            background: #3e3e42;
            font-weight: 600;
            color: #cccccc;
            text-transform: uppercase;
            font-size: 12px;
            letter-spacing: 0.5px;
        }

        .process-table tr:hover {
            background: #1e1e1e;
        }

        .progress-bar {
            width: 100%;
            height: 8px;
            background: #3e3e42;
            border-radius: 4px;
            overflow: hidden;
        }

        .progress-fill {
            height: 100%;
            background: #007acc;
            transition: width 0.3s ease;
        }

        .progress-fill.high {
            background: #d9534f;
        }

        .progress-fill.medium {
            background: #f0ad4e;
        }

        .status-indicator {
            display: inline-block;
            width: 8px;
            height: 8px;
            border-radius: 50%;
            margin-right: 8px;
        }

        .status-running {
            background: #5cb85c;
        }

        .status-sleeping {
            background: #f0ad4e;
        }

        .status-zombie {
            background: #d9534f;
        }

        .refresh-btn {
            background: #007acc;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .refresh-btn:hover {
            background: #005a9e;
        }

        .tab-container {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
        }

        .tab {
            background: #2d2d30;
            border: 1px solid #3e3e42;
            padding: 10px 20px;
            border-radius: 4px;
            cursor: pointer;
            transition: all 0.2s;
        }

        .tab.active {
            background: #007acc;
            border-color: #007acc;
        }

        .tab:hover:not(.active) {
            background: #3e3e42;
        }

        @keyframes pulse {
            0% { opacity: 1; }
            50% { opacity: 0.5; }
            100% { opacity: 1; }
        }

        .updating {
            animation: pulse 1s infinite;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🖥️ System Monitor</h1>
        </div>

        <div class="main-content">
            <div class="sidebar">
                <div class="nav-item active" data-panel="overview">📊 Overview</div>
                <div class="nav-item" data-panel="processes">⚙️ Processes</div>
                <div class="nav-item" data-panel="disks">💾 Disks</div>
                <div class="nav-item" data-panel="network">🌐 Network</div>
            </div>

            <div class="content">
                <!-- Overview Panel -->
                <div class="dashboard active" id="overview">
                    <div class="metrics-grid">
                        <div class="metric-card">
                            <div class="metric-header">
                                <span class="metric-title">CPU Usage</span>
                                <span class="metric-value" id="cpuUsage">0%</span>
                            </div>
                            <div class="progress-bar">
                                <div class="progress-fill" id="cpuProgress"></div>
                            </div>
                            <div class="chart-container">
                                <canvas id="cpuChart"></canvas>
                            </div>
                        </div>

                        <div class="metric-card">
                            <div class="metric-header">
                                <span class="metric-title">Memory Usage</span>
                                <span class="metric-value">
                                    <span id="memoryUsed">0</span>
                                    <span class="metric-unit">GB</span>
                                </span>
                            </div>
                            <div class="progress-bar">
                                <div class="progress-fill" id="memoryProgress"></div>
                            </div>
                            <div class="chart-container">
                                <canvas id="memoryChart"></canvas>
                            </div>
                        </div>

                        <div class="metric-card">
                            <div class="metric-header">
                                <span class="metric-title">Disk Usage</span>
                                <span class="metric-value" id="diskUsage">0%</span>
                            </div>
                            <div class="progress-bar">
                                <div class="progress-fill" id="diskProgress"></div>
                            </div>
                            <div class="chart-container">
                                <canvas id="diskChart"></canvas>
                            </div>
                        </div>

                        <div class="metric-card">
                            <div class="metric-header">
                                <span class="metric-title">System Info</span>
                                <button class="refresh-btn" onclick="refreshSystemInfo()">Refresh</button>
                            </div>
                            <div style="margin-top: 15px;">
                                <div style="margin-bottom: 10px;">
                                    <strong>Uptime:</strong> <span id="uptime">0h 0m</span>
                                </div>
                                <div style="margin-bottom: 10px;">
                                    <strong>Processes:</strong> <span id="processCount">0</span>
                                </div>
                                <div style="margin-bottom: 10px;">
                                    <strong>Total Memory:</strong> <span id="totalMemory">0 GB</span>
                                </div>
                                <div>
                                    <strong>Total Disk:</strong> <span id="totalDisk">0 GB</span>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Processes Panel -->
                <div class="dashboard" id="processes">
                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                        <h2>Running Processes</h2>
                        <button class="refresh-btn" onclick="refreshProcesses()">Refresh</button>
                    </div>

                    <div class="tab-container">
                        <div class="tab active" onclick="setProcessSort('cpu')">Sort by CPU</div>
                        <div class="tab" onclick="setProcessSort('memory')">Sort by Memory</div>
                        <div class="tab" onclick="setProcessSort('name')">Sort by Name</div>
                    </div>

                    <div class="process-table">
                        <table>
                            <thead>
                                <tr>
                                    <th>PID</th>
                                    <th>Name</th>
                                    <th>CPU %</th>
                                    <th>Memory</th>
                                    <th>Memory %</th>
                                    <th>Status</th>
                                    <th>Action</th>
                                </tr>
                            </thead>
                            <tbody id="processTableBody">
                                <tr>
                                    <td colspan="7" style="text-align: center;">Loading...</td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>

                <!-- Disks Panel -->
                <div class="dashboard" id="disks">
                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                        <h2>Disk Usage</h2>
                        <button class="refresh-btn" onclick="refreshDisks()">Refresh</button>
                    </div>

                    <div id="diskInfo">
                        <div class="process-table">
                            <table>
                                <thead>
                                    <tr>
                                        <th>Name</th>
                                        <th>Mount Point</th>
                                        <th>File System</th>
                                        <th>Used</th>
                                        <th>Available</th>
                                        <th>Total</th>
                                        <th>Usage</th>
                                    </tr>
                                </thead>
                                <tbody id="diskTableBody">
                                    <tr>
                                        <td colspan="7" style="text-align: center;">Loading...</td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>

                <!-- Network Panel -->
                <div class="dashboard" id="network">
                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                        <h2>Network Activity</h2>
                        <button class="refresh-btn" onclick="refreshNetwork()">Refresh</button>
                    </div>

                    <div id="networkInfo">
                        <div class="process-table">
                            <table>
                                <thead>
                                    <tr>
                                        <th>Interface</th>
                                        <th>Received</th>
                                        <th>Transmitted</th>
                                        <th>Packets Rx</th>
                                        <th>Packets Tx</th>
                                        <th>Errors Rx</th>
                                        <th>Errors Tx</th>
                                    </tr>
                                </thead>
                                <tbody id="networkTableBody">
                                    <tr>
                                        <td colspan="7" style="text-align: center;">Loading...</td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        const { invoke } = window.__TAURI__.core;

        let currentPanel = 'overview';
        let processSortBy = 'cpu';
        let charts = {};
        let updateInterval;

        // Initialize application
        async function init() {
            setupEventListeners();
            await loadInitialData();
            startAutoUpdate();
        }

        // Setup event listeners
        function setupEventListeners() {
            // Navigation
            document.querySelectorAll('.nav-item').forEach(item => {
                item.addEventListener('click', () => {
                    const panel = item.dataset.panel;
                    switchPanel(panel);
                });
            });
        }

        // Switch between panels
        function switchPanel(panel) {
            currentPanel = panel;

            // Update navigation
            document.querySelectorAll('.nav-item').forEach(item => {
                item.classList.toggle('active', item.dataset.panel === panel);
            });

            // Update content
            document.querySelectorAll('.dashboard').forEach(dashboard => {
                dashboard.classList.toggle('active', dashboard.id === panel);
            });

            // Load panel-specific data
            switch (panel) {
                case 'processes':
                    refreshProcesses();
                    break;
                case 'disks':
                    refreshDisks();
                    break;
                case 'network':
                    refreshNetwork();
                    break;
                default:
                    refreshSystemInfo();
            }
        }

        // Load initial data
        async function loadInitialData() {
            await refreshSystemInfo();
            await refreshProcesses();
            await refreshDisks();
            await refreshNetwork();
            initializeCharts();
        }

        // Refresh system information
        async function refreshSystemInfo() {
            try {
                const info = await invoke('get_system_info');
                updateSystemInfo(info);
                updateCharts(info);
            } catch (error) {
                console.error('Failed to get system info:', error);
            }
        }

        // Update system information display
        function updateSystemInfo(info) {
            // CPU
            const cpuUsage = Math.round(info.cpu_usage);
            document.getElementById('cpuUsage').textContent = cpuUsage + '%';
            updateProgressBar('cpuProgress', cpuUsage);

            // Memory
            const memoryUsedGB = (info.memory_used / (1024 * 1024 * 1024)).toFixed(1);
            const totalMemoryGB = (info.memory_total / (1024 * 1024 * 1024)).toFixed(1);
            const memoryUsage = Math.round(info.memory_usage);

            document.getElementById('memoryUsed').textContent = memoryUsedGB;
            document.getElementById('totalMemory').textContent = totalMemoryGB + ' GB';
            updateProgressBar('memoryProgress', memoryUsage);

            // Disk
            const diskUsage = Math.round(info.disk_usage);
            const totalDiskGB = (info.disk_total / (1024 * 1024 * 1024)).toFixed(1);

            document.getElementById('diskUsage').textContent = diskUsage + '%';
            document.getElementById('totalDisk').textContent = totalDiskGB + ' GB';
            updateProgressBar('diskProgress', diskUsage);

            // Other info
            const uptime = formatUptime(info.uptime);
            document.getElementById('uptime').textContent = uptime;
            document.getElementById('processCount').textContent = info.process_count;
        }

        // Update progress bar
        function updateProgressBar(id, percentage) {
            const bar = document.getElementById(id);
            bar.style.width = percentage + '%';

            // Change color based on usage
            bar.className = 'progress-fill';
            if (percentage >= 80) {
                bar.classList.add('high');
            } else if (percentage >= 60) {
                bar.classList.add('medium');
            }
        }

        // Format uptime
        function formatUptime(seconds) {
            const hours = Math.floor(seconds / 3600);
            const minutes = Math.floor((seconds % 3600) / 60);
            return `${hours}h ${minutes}m`;
        }

        // Initialize charts
        function initializeCharts() {
            const chartOptions = {
                responsive: true,
                maintainAspectRatio: false,
                scales: {
                    y: {
                        beginAtZero: true,
                        max: 100,
                        ticks: {
                            color: '#cccccc'
                        },
                        grid: {
                            color: '#3e3e42'
                        }
                    },
                    x: {
                        ticks: {
                            color: '#cccccc'
                        },
                        grid: {
                            color: '#3e3e42'
                        }
                    }
                },
                plugins: {
                    legend: {
                        display: false
                    }
                }
            };

            // CPU Chart
            const cpuCtx = document.getElementById('cpuChart').getContext('2d');
            charts.cpu = new Chart(cpuCtx, {
                type: 'line',
                data: {
                    labels: Array(20).fill(''),
                    datasets: [{
                        data: Array(20).fill(0),
                        borderColor: '#007acc',
                        backgroundColor: 'rgba(0, 122, 204, 0.1)',
                        borderWidth: 2,
                        tension: 0.4,
                        fill: true
                    }]
                },
                options: chartOptions
            });

            // Memory Chart
            const memoryCtx = document.getElementById('memoryChart').getContext('2d');
            charts.memory = new Chart(memoryCtx, {
                type: 'line',
                data: {
                    labels: Array(20).fill(''),
                    datasets: [{
                        data: Array(20).fill(0),
                        borderColor: '#5cb85c',
                        backgroundColor: 'rgba(92, 184, 92, 0.1)',
                        borderWidth: 2,
                        tension: 0.4,
                        fill: true
                    }]
                },
                options: chartOptions
            });

            // Disk Chart
            const diskCtx = document.getElementById('diskChart').getContext('2d');
            charts.disk = new Chart(diskCtx, {
                type: 'line',
                data: {
                    labels: Array(20).fill(''),
                    datasets: [{
                        data: Array(20).fill(0),
                        borderColor: '#f0ad4e',
                        backgroundColor: 'rgba(240, 173, 78, 0.1)',
                        borderWidth: 2,
                        tension: 0.4,
                        fill: true
                    }]
                },
                options: chartOptions
            });
        }

        // Update charts with new data
        function updateCharts(info) {
            if (charts.cpu) {
                const cpuData = charts.cpu.data.datasets[0].data;
                cpuData.shift();
                cpuData.push(Math.round(info.cpu_usage));
                charts.cpu.update('none');
            }

            if (charts.memory) {
                const memoryData = charts.memory.data.datasets[0].data;
                memoryData.shift();
                memoryData.push(Math.round(info.memory_usage));
                charts.memory.update('none');
            }

            if (charts.disk) {
                const diskData = charts.disk.data.datasets[0].data;
                diskData.shift();
                diskData.push(Math.round(info.disk_usage));
                charts.disk.update('none');
            }
        }

        // Refresh processes
        async function refreshProcesses() {
            try {
                const processes = await invoke('get_processes');
                displayProcesses(processes);
            } catch (error) {
                console.error('Failed to get processes:', error);
            }
        }

        // Display processes in table
        function displayProcesses(processes) {
            const tbody = document.getElementById('processTableBody');
            tbody.innerHTML = '';

            processes.forEach(process => {
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td>${process.pid}</td>
                    <td>${process.name}</td>
                    <td>${process.cpu_usage.toFixed(1)}%</td>
                    <td>${formatBytes(process.memory_usage)}</td>
                    <td>${process.memory_percent.toFixed(1)}%</td>
                    <td>
                        <span class="status-indicator ${getStatusClass(process.status)}"></span>
                        ${process.status}
                    </td>
                    <td>
                        <button class="refresh-btn" onclick="killProcess(${process.pid})">Kill</button>
                    </td>
                `;
                tbody.appendChild(row);
            });
        }

        // Get status class for styling
        function getStatusClass(status) {
            if (status.includes('Run')) return 'status-running';
            if (status.includes('Sleep')) return 'status-sleeping';
            if (status.includes('Zombie')) return 'status-zombie';
            return '';
        }

        // Format bytes to human readable
        function formatBytes(bytes) {
            if (bytes === 0) return '0 B';

            const k = 1024;
            const sizes = ['B', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));

            return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
        }

        // Kill process
        async function killProcess(pid) {
            if (confirm(`Are you sure you want to kill process ${pid}?`)) {
                try {
                    await invoke('kill_process', { pid });
                    await refreshProcesses();
                } catch (error) {
                    alert('Failed to kill process: ' + error);
                }
            }
        }

        // Set process sort
        function setProcessSort(sortBy) {
            processSortBy = sortBy;

            // Update tab UI
            document.querySelectorAll('.tab').forEach(tab => {
                tab.classList.remove('active');
            });
            event.target.classList.add('active');

            refreshProcesses();
        }

        // Refresh disks
        async function refreshDisks() {
            try {
                const disks = await invoke('get_disks');
                displayDisks(disks);
            } catch (error) {
                console.error('Failed to get disk info:', error);
            }
        }

        // Display disk information
        function displayDisks(disks) {
            const tbody = document.getElementById('diskTableBody');
            tbody.innerHTML = '';

            disks.forEach(disk => {
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td>${disk.name}</td>
                    <td>${disk.mount_point}</td>
                    <td>${disk.file_system}</td>
                    <td>${formatBytes(disk.used_space)}</td>
                    <td>${formatBytes(disk.available_space)}</td>
                    <td>${formatBytes(disk.total_space)}</td>
                    <td>
                        <div class="progress-bar" style="width: 100px;">
                            <div class="progress-fill ${disk.usage_percent > 80 ? 'high' : disk.usage_percent > 60 ? 'medium' : ''}"
                                 style="width: ${disk.usage_percent}%"></div>
                        </div>
                        ${disk.usage_percent.toFixed(1)}%
                    </td>
                `;
                tbody.appendChild(row);
            });
        }

        // Refresh network info
        async function refreshNetwork() {
            try {
                const networks = await invoke('get_network_info');
                displayNetworks(networks);
            } catch (error) {
                console.error('Failed to get network info:', error);
            }
        }

        // Display network information
        function displayNetworks(networks) {
            const tbody = document.getElementById('networkTableBody');
            tbody.innerHTML = '';

            networks.forEach(network => {
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td>${network.interface}</td>
                    <td>${formatBytes(network.data_received)}</td>
                    <td>${formatBytes(network.data_transmitted)}</td>
                    <td>${network.packets_received.toLocaleString()}</td>
                    <td>${network.packets_transmitted.toLocaleString()}</td>
                    <td>${network.errors_on_received}</td>
                    <td>${network.errors_on_transmitted}</td>
                `;
                tbody.appendChild(row);
            });
        }

        // Start auto-update
        function startAutoUpdate() {
            updateInterval = setInterval(async () => {
                if (currentPanel === 'overview') {
                    await refreshSystemInfo();
                }
            }, 2000);
        }

        // Listen for system updates from backend
        if (window.__TAURI__) {
            window.__TAURI__.event.listen('system-update', async () => {
                if (currentPanel === 'overview') {
                    await refreshSystemInfo();
                }
            });
        }

        // Initialize when DOM is loaded
        document.addEventListener('DOMContentLoaded', init);
    </script>
</body>
</html>