diff --git a/markbase-tauri/src-tauri/src/commands/acl.rs b/markbase-tauri/src-tauri/src/commands/acl.rs new file mode 100644 index 0000000..1d141b2 --- /dev/null +++ b/markbase-tauri/src-tauri/src/commands/acl.rs @@ -0,0 +1,148 @@ +use markbase_core::vfs::{VfsAcl, VfsAce, VfsAceType, VfsAceFlag, VfsAceMask, VfsBackend, local_fs::LocalFs}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Debug, Serialize, Deserialize)] +pub struct AceInfo { + pub ace_type: String, + pub flags: Vec, + pub mask: Vec, + pub principal: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AclInfo { + pub path: String, + pub aces: Vec, +} + +#[tauri::command] +pub async fn get_acl(user_id: String, path: String) -> Result { + let vfs = LocalFs::new(); + let path_buf = PathBuf::from(&path); + + let acl = vfs.get_acl(&path_buf) + .map_err(|e| format!("Failed to get ACL: {}", e))?; + + let aces = acl.aces.iter().map(|ace| { + AceInfo { + ace_type: match ace.ace_type { + VfsAceType::Allow => "Allow".to_string(), + VfsAceType::Deny => "Deny".to_string(), + VfsAceType::Audit => "Audit".to_string(), + VfsAceType::Alarm => "Alarm".to_string(), + }, + flags: ace.flags.iter().map(|f| match f { + VfsAceFlag::FileInherit => "FileInherit", + VfsAceFlag::DirectoryInherit => "DirectoryInherit", + VfsAceFlag::NoPropagateInherit => "NoPropagateInherit", + VfsAceFlag::InheritOnly => "InheritOnly", + VfsAceFlag::Inherited => "Inherited", + VfsAceFlag::SuccessfulAccess => "SuccessfulAccess", + VfsAceFlag::FailedAccess => "FailedAccess", + }.to_string()).collect(), + mask: ace.mask.iter().map(|m| match m { + VfsAceMask::ReadData => "ReadData", + VfsAceMask::WriteData => "WriteData", + VfsAceMask::Execute => "Execute", + VfsAceMask::ListDirectory => "ListDirectory", + VfsAceMask::AddFile => "AddFile", + VfsAceMask::AddSubdirectory => "AddSubdirectory", + VfsAceMask::DeleteChild => "DeleteChild", + VfsAceMask::Delete => "Delete", + VfsAceMask::ReadAttributes => "ReadAttributes", + VfsAceMask::WriteAttributes => "WriteAttributes", + VfsAceMask::ReadNfsAcl => "ReadAcl", + VfsAceMask::WriteNfsAcl => "WriteAcl", + VfsAceMask::ReadOwner => "ReadOwner", + VfsAceMask::WriteOwner => "WriteOwner", + VfsAceMask::Synchronize => "Synchronize", + VfsAceMask::FullControl => "FullControl", + }.to_string()).collect(), + principal: ace.principal.clone(), + } + }).collect(); + + Ok(AclInfo { + path, + aces, + }) +} + +#[tauri::command] +pub async fn set_acl(user_id: String, path: String, aces: Vec) -> Result<(), String> { + let vfs = LocalFs::new(); + let path_buf = PathBuf::from(&path); + + let vfs_aces = aces.iter().map(|ace| { + VfsAce { + ace_type: match ace.ace_type.as_str() { + "Allow" => VfsAceType::Allow, + "Deny" => VfsAceType::Deny, + "Audit" => VfsAceType::Audit, + "Alarm" => VfsAceType::Alarm, + _ => VfsAceType::Allow, + }, + flags: ace.flags.iter().map(|f| match f.as_str() { + "FileInherit" => VfsAceFlag::FileInherit, + "DirectoryInherit" => VfsAceFlag::DirectoryInherit, + "NoPropagateInherit" => VfsAceFlag::NoPropagateInherit, + "InheritOnly" => VfsAceFlag::InheritOnly, + "Inherited" => VfsAceFlag::Inherited, + "SuccessfulAccess" => VfsAceFlag::SuccessfulAccess, + "FailedAccess" => VfsAceFlag::FailedAccess, + _ => VfsAceFlag::FileInherit, + }).collect(), + mask: ace.mask.iter().map(|m| match m.as_str() { + "ReadData" => VfsAceMask::ReadData, + "WriteData" => VfsAceMask::WriteData, + "Execute" => VfsAceMask::Execute, + "ListDirectory" => VfsAceMask::ListDirectory, + "AddFile" => VfsAceMask::AddFile, + "AddSubdirectory" => VfsAceMask::AddSubdirectory, + "DeleteChild" => VfsAceMask::DeleteChild, + "Delete" => VfsAceMask::Delete, + "ReadAttributes" => VfsAceMask::ReadAttributes, + "WriteAttributes" => VfsAceMask::WriteAttributes, + "ReadAcl" => VfsAceMask::ReadNfsAcl, + "WriteAcl" => VfsAceMask::WriteNfsAcl, + "ReadOwner" => VfsAceMask::ReadOwner, + "WriteOwner" => VfsAceMask::WriteOwner, + "Synchronize" => VfsAceMask::Synchronize, + "FullControl" => VfsAceMask::FullControl, + _ => VfsAceMask::ReadData, + }).collect(), + principal: ace.principal.clone(), + } + }).collect(); + + let acl = VfsAcl { + aces: vfs_aces, + default_acl: None, + }; + + vfs.set_acl(&path_buf, &acl) + .map_err(|e| format!("Failed to set ACL: {}", e))?; + + Ok(()) +} + +#[tauri::command] +pub async fn check_acl(user_id: String, path: String, principal: String, mask: String) -> Result { + let vfs = LocalFs::new(); + let path_buf = PathBuf::from(&path); + + let vfs_mask = match mask.as_str() { + "ReadData" => VfsAceMask::ReadData, + "WriteData" => VfsAceMask::WriteData, + "Execute" => VfsAceMask::Execute, + "ReadAcl" => VfsAceMask::ReadNfsAcl, + "WriteAcl" => VfsAceMask::WriteNfsAcl, + _ => VfsAceMask::ReadData, + }; + + let result = vfs.check_acl(&path_buf, &principal, vfs_mask) + .map_err(|e| format!("Failed to check ACL: {}", e))?; + + Ok(result) +} \ No newline at end of file diff --git a/markbase-tauri/src-tauri/src/commands/file_ops.rs b/markbase-tauri/src-tauri/src/commands/file_ops.rs index f7fcc40..56524f7 100644 --- a/markbase-tauri/src-tauri/src/commands/file_ops.rs +++ b/markbase-tauri/src-tauri/src/commands/file_ops.rs @@ -293,4 +293,102 @@ fn build_tree( } Err("Root node not found".to_string()) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct FileMetadata { + pub name: String, + pub size: u64, + pub modified: String, + pub permissions: String, + pub file_type: String, +} + +#[tauri::command] +pub async fn read_file_content( + file_path: String, +) -> Result { + use std::fs; + + if !Path::new(&file_path).exists() { + return Err(format!("File not found: {}", file_path)); + } + + let content = fs::read_to_string(&file_path) + .map_err(|e| format!("Failed to read file: {}", e))?; + + Ok(content) +} + +#[tauri::command] +pub async fn get_file_metadata( + user_id: String, + file_uuid: String, +) -> Result { + use std::fs; + + let db_path = PathBuf::from("data/users") + .join(format!("{}.sqlite", user_id)); + + if !db_path.exists() { + return Err(format!("Database not found: {:?}", db_path)); + } + + let conn = Connection::open(&db_path) + .map_err(|e| format!("Failed to open database: {}", e))?; + + let (file_name, file_path, file_size, registered_at): (String, String, i64, String) = conn.query_row( + "SELECT original_name, file_path, file_size, registered_at + FROM file_registry + WHERE file_uuid = ?1", + [&file_uuid], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)) + ).map_err(|e| format!("Failed to query file metadata: {}", e))?; + + let metadata = fs::metadata(&file_path) + .map_err(|e| format!("Failed to get file metadata: {}", e))?; + + let modified = metadata.modified() + .map(|t| { + let datetime: std::time::SystemTime = t; + let timestamp = datetime.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); + timestamp.to_string() + }) + .unwrap_or_else(|_| "Unknown".to_string()); + + let ext = Path::new(&file_name) + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("") + .to_lowercase(); + + let file_type = if ext.is_empty() { + "file".to_string() + } else if ["jpg", "jpeg", "png", "gif", "bmp", "webp"].contains(&ext.as_str()) { + "image".to_string() + } else if ["mp4", "avi", "mov", "wmv", "flv", "mkv"].contains(&ext.as_str()) { + "video".to_string() + } else if ["mp3", "wav", "ogg", "flac"].contains(&ext.as_str()) { + "audio".to_string() + } else if ["pdf"].contains(&ext.as_str()) { + "pdf".to_string() + } else if ["txt", "md", "json", "xml", "yaml"].contains(&ext.as_str()) { + "text".to_string() + } else { + "file".to_string() + }; + + let permissions = if metadata.permissions().readonly() { + "Read-only".to_string() + } else { + "Read-write".to_string() + }; + + Ok(FileMetadata { + name: file_name, + size: file_size as u64, + modified, + permissions, + file_type, + }) } \ No newline at end of file diff --git a/markbase-tauri/src-tauri/src/commands/mod.rs b/markbase-tauri/src-tauri/src/commands/mod.rs index 32784d8..05cc5bd 100644 --- a/markbase-tauri/src-tauri/src/commands/mod.rs +++ b/markbase-tauri/src-tauri/src/commands/mod.rs @@ -9,6 +9,9 @@ pub mod backup; pub mod user_management; pub mod share_management; pub mod system_stats; +pub mod virtual_folders; +pub mod quota; +pub mod acl; pub use file_ops::*; pub use install::*; @@ -20,4 +23,7 @@ pub use monitor::*; pub use backup::*; pub use user_management::*; pub use share_management::*; -pub use system_stats::*; \ No newline at end of file +pub use system_stats::*; +pub use virtual_folders::*; +pub use quota::*; +pub use acl::*; \ No newline at end of file diff --git a/markbase-tauri/src-tauri/src/commands/quota.rs b/markbase-tauri/src-tauri/src/commands/quota.rs new file mode 100644 index 0000000..d82eead --- /dev/null +++ b/markbase-tauri/src-tauri/src/commands/quota.rs @@ -0,0 +1,97 @@ +use markbase_core::vfs::{VfsQuota, VfsQuotaUsage, VfsBackend, local_fs::LocalFs}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Debug, Serialize, Deserialize)] +pub struct QuotaInfo { + pub path: String, + pub space_limit: u64, + pub file_limit: u64, + pub soft_limit: u64, + pub grace_period: u64, + pub user_id: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct QuotaUsageInfo { + pub path: String, + pub space_used: u64, + pub files_used: u64, +} + +#[tauri::command] +pub async fn get_quota(user_id: String, path: String) -> Result { + let vfs = LocalFs::new(); + let path_buf = PathBuf::from(&path); + + let quota = vfs.get_quota(&path_buf) + .map_err(|e| format!("Failed to get quota: {}", e))?; + + Ok(QuotaInfo { + path, + space_limit: quota.space_limit, + file_limit: quota.file_limit, + soft_limit: quota.soft_limit, + grace_period: quota.grace_period, + user_id: quota.user_id, + }) +} + +#[tauri::command] +pub async fn set_quota( + user_id: String, + path: String, + space_limit: u64, + file_limit: u64, + soft_limit: u64, + grace_period: u64, +) -> Result<(), String> { + let vfs = LocalFs::new(); + let path_buf = PathBuf::from(&path); + + let quota = VfsQuota { + space_limit, + file_limit, + soft_limit, + grace_period, + user_id: Some(user_id), + }; + + vfs.set_quota(&path_buf, "a) + .map_err(|e| format!("Failed to set quota: {}", e))?; + + Ok(()) +} + +#[tauri::command] +pub async fn get_quota_usage(user_id: String, path: String) -> Result { + let vfs = LocalFs::new(); + let path_buf = PathBuf::from(&path); + + let usage = vfs.get_quota_usage(&path_buf) + .map_err(|e| format!("Failed to get quota usage: {}", e))?; + + Ok(QuotaUsageInfo { + path, + space_used: usage.space_used, + files_used: usage.files_used, + }) +} + +#[tauri::command] +pub async fn check_quota(user_id: String, path: String, size: u64) -> Result { + let vfs = LocalFs::new(); + let path_buf = PathBuf::from(&path); + + let quota = vfs.get_quota(&path_buf) + .map_err(|e| format!("Failed to get quota: {}", e))?; + + let usage = vfs.get_quota_usage(&path_buf) + .map_err(|e| format!("Failed to get quota usage: {}", e))?; + + let space_ok = quota.space_limit == 0 || usage.space_used + size <= quota.space_limit; + let files_ok = quota.file_limit == 0 || usage.files_used + 1 <= quota.file_limit; + + Ok(space_ok && files_ok) +} \ No newline at end of file diff --git a/markbase-tauri/src-tauri/src/commands/virtual_folders.rs b/markbase-tauri/src-tauri/src/commands/virtual_folders.rs new file mode 100644 index 0000000..1b854c0 --- /dev/null +++ b/markbase-tauri/src-tauri/src/commands/virtual_folders.rs @@ -0,0 +1,113 @@ +use markbase_core::vfs::virtual_fs::VirtualFs; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; +use rusqlite::Connection; + +#[derive(Debug, Serialize, Deserialize)] +pub struct VirtualFolder { + pub folder: String, + pub description: String, + pub created_at: String, +} + +#[tauri::command] +pub async fn list_virtual_folders(user_id: String) -> Result, String> { + let db_path = PathBuf::from("data/users") + .join(format!("{}.sqlite", user_id)); + + if !db_path.exists() { + return Err(format!("Database not found: {:?}", db_path)); + } + + let conn = Connection::open(&db_path) + .map_err(|e| format!("Failed to open database: {}", e))?; + + let mut stmt = conn.prepare( + "SELECT folder, description, created_at FROM virtual_folders ORDER BY folder" + ).map_err(|e| format!("Failed to prepare statement: {}", e))?; + + let folders = stmt.query_map([], |row| { + Ok(VirtualFolder { + folder: row.get::<_, String>(0)?, + description: row.get::<_, String>(1)?, + created_at: row.get::<_, String>(2)?, + }) + }).map_err(|e| format!("Failed to query folders: {}", e))?; + + let mut result = Vec::new(); + for folder in folders { + let folder_data = folder.map_err(|e| format!("Failed to get folder: {}", e))?; + result.push(folder_data); + } + + Ok(result) +} + +#[tauri::command] +pub async fn create_virtual_folder( + user_id: String, + folder: String, + description: String, +) -> Result<(), String> { + let db_path = PathBuf::from("data/users") + .join(format!("{}.sqlite", user_id)); + + if !db_path.exists() { + return Err(format!("Database not found: {:?}", db_path)); + } + + let conn = Connection::open(&db_path) + .map_err(|e| format!("Failed to open database: {}", e))?; + + conn.execute( + "INSERT OR IGNORE INTO virtual_folders (folder, description) VALUES (?1, ?2)", + rusqlite::params![folder, description], + ).map_err(|e| format!("Failed to create folder: {}", e))?; + + Ok(()) +} + +#[tauri::command] +pub async fn update_virtual_folder( + user_id: String, + folder: String, + description: String, +) -> Result<(), String> { + let db_path = PathBuf::from("data/users") + .join(format!("{}.sqlite", user_id)); + + if !db_path.exists() { + return Err(format!("Database not found: {:?}", db_path)); + } + + let conn = Connection::open(&db_path) + .map_err(|e| format!("Failed to open database: {}", e))?; + + conn.execute( + "UPDATE virtual_folders SET description = ?1 WHERE folder = ?2", + rusqlite::params![description, folder], + ).map_err(|e| format!("Failed to update folder: {}", e))?; + + Ok(()) +} + +#[tauri::command] +pub async fn delete_virtual_folder(user_id: String, folder: String) -> Result<(), String> { + let db_path = PathBuf::from("data/users") + .join(format!("{}.sqlite", user_id)); + + if !db_path.exists() { + return Err(format!("Database not found: {:?}", db_path)); + } + + let conn = Connection::open(&db_path) + .map_err(|e| format!("Failed to open database: {}", e))?; + + conn.execute( + "DELETE FROM virtual_folders WHERE folder = ?1", + rusqlite::params![folder], + ).map_err(|e| format!("Failed to delete folder: {}", e))?; + + Ok(()) +} \ No newline at end of file diff --git a/markbase-tauri/src-tauri/src/main.rs b/markbase-tauri/src-tauri/src/main.rs index 467bedd..42cc5fd 100644 --- a/markbase-tauri/src-tauri/src/main.rs +++ b/markbase-tauri/src-tauri/src/main.rs @@ -13,6 +13,8 @@ fn main() { search_files, download_file, open_file, + read_file_content, + get_file_metadata, check_system_environment, initialize_database, create_service_account, @@ -55,6 +57,17 @@ fn main() { get_system_stats, get_all_services_status, get_recent_activity, + list_virtual_folders, + create_virtual_folder, + update_virtual_folder, + delete_virtual_folder, + get_quota, + set_quota, + get_quota_usage, + check_quota, + get_acl, + set_acl, + check_acl, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/markbase-tauri/src/src/App.vue b/markbase-tauri/src/src/App.vue index 4b93043..5292765 100644 --- a/markbase-tauri/src/src/App.vue +++ b/markbase-tauri/src/src/App.vue @@ -1,6 +1,10 @@ + + + + \ No newline at end of file diff --git a/markbase-tauri/src/src/views/FilePreview.vue b/markbase-tauri/src/src/views/FilePreview.vue new file mode 100644 index 0000000..c6c0808 --- /dev/null +++ b/markbase-tauri/src/src/views/FilePreview.vue @@ -0,0 +1,347 @@ + + + + + \ No newline at end of file diff --git a/markbase-tauri/src/src/views/Home.vue b/markbase-tauri/src/src/views/Home.vue index f0c06ac..6a0920a 100644 --- a/markbase-tauri/src/src/views/Home.vue +++ b/markbase-tauri/src/src/views/Home.vue @@ -2,10 +2,10 @@ import { ref, onMounted, watch } from 'vue' import { useRouter } from 'vue-router' import { useAppStore } from '../stores/app' -import { invoke } from '@tauri-apps/api/tauri' +import { invoke } from '@tauri-apps/api/core' import { ElMessage } from 'element-plus' import { Folder, Document, Upload, Clock, UserFilled, FolderOpened, Monitor } from '@element-plus/icons-vue' -import { open } from '@tauri-apps/api/dialog' +import { open } from '@tauri-apps/plugin-dialog' const router = useRouter() const appStore = useAppStore() diff --git a/markbase-tauri/src/src/views/Quota.vue b/markbase-tauri/src/src/views/Quota.vue new file mode 100644 index 0000000..d7d21ea --- /dev/null +++ b/markbase-tauri/src/src/views/Quota.vue @@ -0,0 +1,226 @@ + + + + + \ No newline at end of file diff --git a/markbase-tauri/src/src/views/Shares.vue b/markbase-tauri/src/src/views/Shares.vue index aa38bb8..ae30401 100644 --- a/markbase-tauri/src/src/views/Shares.vue +++ b/markbase-tauri/src/src/views/Shares.vue @@ -8,7 +8,6 @@ import { Edit, Delete, Connection, - Network, Document, } from '@element-plus/icons-vue' diff --git a/markbase-tauri/src/src/views/VirtualFolders.vue b/markbase-tauri/src/src/views/VirtualFolders.vue new file mode 100644 index 0000000..1588a95 --- /dev/null +++ b/markbase-tauri/src/src/views/VirtualFolders.vue @@ -0,0 +1,211 @@ + + + + + \ No newline at end of file diff --git a/markbase-tauri/src/src/views/WebAdmin.vue b/markbase-tauri/src/src/views/WebAdmin.vue new file mode 100644 index 0000000..fec7287 --- /dev/null +++ b/markbase-tauri/src/src/views/WebAdmin.vue @@ -0,0 +1,130 @@ + + + + + \ No newline at end of file diff --git a/markbase-tauri/src/src/views/WebClient.vue b/markbase-tauri/src/src/views/WebClient.vue new file mode 100644 index 0000000..4b8cb1b --- /dev/null +++ b/markbase-tauri/src/src/views/WebClient.vue @@ -0,0 +1,1273 @@ + + + + + \ No newline at end of file