🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples Tauri
Exemples de développement d'applications de bureau légères utilisant le framework Tauri avec backend Rust et frontend web
💻 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"
[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"] }
[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>