Exemples Tauri

Exemples de développement d'applications de bureau légères utilisant le framework Tauri avec backend Rust et frontend web

Key Facts

Category
Desktop Development
Items
3
Format Families
sample

Sample Overview

Exemples de développement d'applications de bureau légères utilisant le framework Tauri avec backend Rust et frontend web This sample set belongs to Desktop Development and can be used to test related workflows inside Elysia Tools.

💻 Application Tauri Hello World rust

🟢 simple ⭐⭐

Configuration d'application Tauri de base avec configuration minimale et communication simple Rust-JavaScript

⏱️ 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"
tokio = { version = "1", features = ["time"] }

[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
*/

💻 Application Gestionnaire de Fichiers Tauri rust

🟡 intermediate ⭐⭐⭐⭐

Application complète de gestionnaire de fichiers avec glisser-déposer, opérations de fichier et intégration système

⏱️ 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>

💻 Application Moniteur Système Tauri rust

🔴 complex ⭐⭐⭐⭐⭐

Tableau de bord de surveillance système en temps réel avec CPU, utilisation mémoire, disque et gestion des processus

⏱️ 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"] }
tokio = { version = "1", features = ["time"] }

[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>