🎯 Exemplos recomendados
Balanced sample collections from various categories for you to explore
Exemplos Tauri
Exemplos de desenvolvimento de aplicativos de desktop leves usando o framework Tauri com backend Rust e frontend web
💻 Aplicação Tauri Hello World rust
🟢 simple
⭐⭐
Configuração básica de aplicação Tauri com configuração mínima e comunicação simples 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
*/
💻 Aplicação Gerenciador de Arquivos Tauri rust
🟡 intermediate
⭐⭐⭐⭐
Aplicação completa de gerenciador de arquivos com arrastar e soltar, operações de arquivo e integração do sistema
⏱️ 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>
💻 Aplicação Monitor de Sistema Tauri rust
🔴 complex
⭐⭐⭐⭐⭐
Dashboard de monitoramento de sistema em tempo real com CPU, uso de memória, disco e gerenciamento de processos
⏱️ 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>