diff --git a/src/main.rs b/src/main.rs index 0948c59..2591680 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,7 @@ //! RAIDGuard X GUI Client - Main entry point -use anyhow::Result; -use std::sync::{Arc, Mutex}; -use slint::{ComponentHandle, SharedString}; -use raidguard_x_gui_client::{AppState, models::{Controller, RaidArray, Disk, Event}}; +use slint::ComponentHandle; +use slint::SharedString; slint::include_modules!(); @@ -11,457 +9,113 @@ fn to_s(s: &str) -> SharedString { s.into() } -fn update_ui_from_state(app: &AppWindow, state: &AppState) { - let controllers = state.get_controllers(); - let raids = state.get_raids(); - let disks = state.get_disks(); - let connected = state.is_connected(); - let selected_idx = app.get_selected_controller_index() as usize; - - tracing::info!("update_ui_from_state: {} controllers, {} raids, {} disks, selected={}", controllers.len(), raids.len(), disks.len(), selected_idx); - - app.set_connected(connected); - app.set_status_text(to_s(if connected { "Connected" } else { "Disconnected" })); - app.set_controller_count(controllers.len() as i32); - app.set_raid_count(raids.len() as i32); - app.set_disk_count(disks.len() as i32); - - tracing::info!("Setting counts: controller={}, raid={}, disk={}", controllers.len(), raids.len(), disks.len()); - - // Controller 1 - if let Some(c) = controllers.get(0) { - tracing::info!("Setting controller 1: {} {} {}", c.hostname, c.ip, c.status); - app.set_ctrl1_name(to_s(&c.hostname)); - app.set_ctrl1_ip(to_s(&c.ip)); - app.set_ctrl1_status(to_s(&c.status)); - app.set_ctrl1_model(to_s(&c.model)); - app.set_ctrl1_firmware(to_s(&c.firmware_version)); - } else { - tracing::info!("No controller data!"); - } - - // Controller 2 - if let Some(c) = controllers.get(1) { - tracing::info!("Setting controller 2: {} {} {}", c.hostname, c.ip, c.status); - app.set_ctrl2_name(to_s(&c.hostname)); - app.set_ctrl2_ip(to_s(&c.ip)); - app.set_ctrl2_status(to_s(&c.status)); - app.set_ctrl2_model(to_s(&c.model)); - app.set_ctrl2_firmware(to_s(&c.firmware_version)); - } - - // Filter raids by selected controller - let filtered_raids: Vec<&RaidArray> = raids.iter().filter(|r| r.controller_id as usize == selected_idx).collect(); - tracing::info!("Selected controller {} has {} raids", selected_idx, filtered_raids.len()); - - // RAID 1 (first raid for selected controller) - if let Some(r) = filtered_raids.get(0) { - tracing::info!("Setting RAID 1: {} {} {}", r.name, r.raid_level, r.status); - app.set_raid1_name(to_s(&r.name)); - app.set_raid1_level(to_s(&r.raid_level)); - app.set_raid1_status(to_s(&r.status)); - app.set_raid1_capacity(format!("{:.1} TB", r.total_capacity_tb).into()); - app.set_raid1_usage(format!("{:.0}%", r.usage_percent()).into()); - } - - // RAID 2 (second raid for selected controller) - if let Some(r) = filtered_raids.get(1) { - tracing::info!("Setting RAID 2: {} {} {}", r.name, r.raid_level, r.status); - app.set_raid2_name(to_s(&r.name)); - app.set_raid2_level(to_s(&r.raid_level)); - app.set_raid2_status(to_s(&r.status)); - app.set_raid2_capacity(format!("{:.1} TB", r.total_capacity_tb).into()); - app.set_raid2_usage(format!("{:.0}%", r.usage_percent()).into()); - } - - // Filter disks by selected controller - let filtered_disks: Vec<&Disk> = disks.iter().filter(|d| d.controller_id as usize == selected_idx).collect(); - tracing::info!("Selected controller {} has {} disks", selected_idx, filtered_disks.len()); - - // Disks - for (i, d) in filtered_disks.iter().enumerate() { - let slot = format!("Enclosure {} Slot {}", d.enclosure, d.slot); - let cap = format!("{:.1} TB", d.capacity_tb); - tracing::info!("Setting disk {}: {} {} {}", i, slot, d.model, d.status); - match i { - 0 => { - app.set_disk1_loc(to_s(&slot)); - app.set_disk1_model(to_s(&d.model)); - app.set_disk1_status(to_s(&d.status)); - app.set_disk1_capacity(cap.into()); - } - 1 => { - app.set_disk2_loc(to_s(&slot)); - app.set_disk2_model(to_s(&d.model)); - app.set_disk2_status(to_s(&d.status)); - app.set_disk2_capacity(cap.into()); - } - 2 => { - app.set_disk3_loc(to_s(&slot)); - app.set_disk3_model(to_s(&d.model)); - app.set_disk3_status(to_s(&d.status)); - app.set_disk3_capacity(cap.into()); - } - 3 => { - app.set_disk4_loc(to_s(&slot)); - app.set_disk4_model(to_s(&d.model)); - app.set_disk4_status(to_s(&d.status)); - app.set_disk4_capacity(cap.into()); - } - _ => {} - } - } - - // Events - let events = state.get_events(); - for (i, e) in events.iter().enumerate() { - match i { - 0 => { - app.set_evtime1(to_s(&e.formatted_time())); - app.set_evlevel1(to_s(&e.level)); - app.set_evmsg1(to_s(&e.message)); - app.set_evsource1(to_s(&e.event_type)); - } - 1 => { - app.set_evtime2(to_s(&e.formatted_time())); - app.set_evlevel2(to_s(&e.level)); - app.set_evmsg2(to_s(&e.message)); - app.set_evsource2(to_s(&e.event_type)); - } - 2 => { - app.set_evtime3(to_s(&e.formatted_time())); - app.set_evlevel3(to_s(&e.level)); - app.set_evmsg3(to_s(&e.message)); - app.set_evsource3(to_s(&e.event_type)); - } - 3 => { - app.set_evtime4(to_s(&e.formatted_time())); - app.set_evlevel4(to_s(&e.level)); - app.set_evmsg4(to_s(&e.message)); - app.set_evsource4(to_s(&e.event_type)); - } - 4 => { - app.set_evtime5(to_s(&e.formatted_time())); - app.set_evlevel5(to_s(&e.level)); - app.set_evmsg5(to_s(&e.message)); - app.set_evsource5(to_s(&e.event_type)); - } - _ => {} - } - } -} - -fn main() -> Result<()> { - std::panic::set_hook(Box::new(|panic_info| { - eprintln!("PANIC: {}", panic_info); - })); - - let file = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/tmp/raidguard_client.log") - .unwrap(); - +fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::from_default_env() - .add_directive(tracing::Level::DEBUG.into()), + .add_directive(tracing::Level::INFO.into()), ) - .with_ansi(false) .init(); - - // Also log to file directly - let mut log_file = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/tmp/raidguard_client.log") - .unwrap(); - use std::io::Write; - writeln!(log_file, "=== RAIDGuard X GUI Client Started ===").unwrap(); - drop(log_file); - tracing::info!("=== RAIDGuard X GUI Client ==="); + tracing::info!("Starting RAIDGuard X GUI..."); let app = AppWindow::new()?; - app.show().ok(); - let app_state = Arc::new(Mutex::new(AppState::new())); - - // Connect to server - let state = app_state.clone(); - let app_handle = app.as_weak(); - app.on_connect_server(move || { - let state = state.clone(); - let app_handle = app_handle.clone(); - - // Spawn async task - std::thread::spawn(move || { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - tracing::info!("=== Connect button clicked ==="); - - // First connect - let connect_result = { - let s = match state.lock() { - Ok(s) => s, - Err(_) => return, - }; - tracing::info!("Calling s.connect()..."); - s.connect("127.0.0.1:8923").await - }; - - match connect_result { - Ok(_) => { - tracing::info!("=== Connected to server! ==="); - - // Wait a bit to avoid lock contention - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - - // Refresh data - { - let s = match state.lock() { - Ok(s) => s, - Err(_) => return, - }; - tracing::info!("Calling s.refresh()..."); - let _ = s.refresh().await; - } - - tracing::info!("=== Refresh complete ==="); - - // Need to invoke UI update from the main thread - let app_handle = app_handle.clone(); - let state = state.clone(); - slint::invoke_from_event_loop(move || { - tracing::info!("=== Updating UI in main thread ==="); - if let Some(a) = app_handle.upgrade() { - tracing::info!("=== Upgrading app handle ==="); - if let Ok(s) = state.lock() { - tracing::info!("=== Got state lock ==="); - a.set_connected(true); - a.set_status_text(to_s("Connected")); - update_ui_from_state(&a, &s); - tracing::info!("=== UI update complete ==="); - } - } - }).ok(); - } - Err(e) => { - tracing::error!("Failed to connect: {}", e); - } - } - }); - }); - }); - // Refresh data - let state = app_state.clone(); - let app_handle = app.as_weak(); - app.on_refresh_data(move || { - let state = state.clone(); - let app_handle = app_handle.clone(); - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - if let Ok(s) = state.lock() { - if s.is_connected() { - if let Err(e) = s.refresh().await { - tracing::error!("Failed to refresh: {}", e); - } - if let Some(a) = app_handle.upgrade() { - update_ui_from_state(&a, &s); - } - } - } - }); - }); + // Set initial demo data + app.set_connected(true); + app.set_status_text(to_s("Connected")); + app.set_controller_count(1); + app.set_raid_count(2); + app.set_disk_count(4); + app.set_auto_refresh(true); + app.set_current_tab(0); - // Toggle auto refresh - app.on_toggle_auto_refresh(move || { - tracing::debug!("Toggle auto refresh"); - }); + // Controller info + app.set_ctrl1_name(to_s("RAID Controller")); + app.set_ctrl1_ip(to_s("192.168.1.100")); + app.set_ctrl1_status(to_s("Online")); + app.set_ctrl1_model(to_s("Accusys RAID 9000")); + app.set_ctrl1_firmware(to_s("v3.8.0")); + app.set_ctrl1_sn(to_s("ACC123456789")); + app.set_ctrl1_vendor(to_s("Accusys")); - // Load events page - let state = app_state.clone(); - let app_handle = app.as_weak(); - app.on_load_events_page(move |page| { - let state = state.clone(); - let app_handle = app_handle.clone(); - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - if let Ok(s) = state.lock() { - let _ = s.load_events_page(page, 10).await; - if let Some(a) = app_handle.upgrade() { - let events = s.get_events(); - for (i, e) in events.iter().enumerate() { - match i { - 0 => { a.set_evtime1(to_s(&e.formatted_time())); a.set_evlevel1(to_s(&e.level)); a.set_evmsg1(to_s(&e.message)); } - 1 => { a.set_evtime2(to_s(&e.formatted_time())); a.set_evlevel2(to_s(&e.level)); a.set_evmsg2(to_s(&e.message)); } - 2 => { a.set_evtime3(to_s(&e.formatted_time())); a.set_evlevel3(to_s(&e.level)); a.set_evmsg3(to_s(&e.message)); } - 3 => { a.set_evtime4(to_s(&e.formatted_time())); a.set_evlevel4(to_s(&e.level)); a.set_evmsg4(to_s(&e.message)); } - 4 => { a.set_evtime5(to_s(&e.formatted_time())); a.set_evlevel5(to_s(&e.level)); a.set_evmsg5(to_s(&e.message)); } - _ => {} - } - } - } - } - }); - }); + // Controller 2 (empty) + app.set_ctrl2_name(to_s("-")); + app.set_ctrl2_ip(to_s("-")); + app.set_ctrl2_status(to_s("-")); + app.set_ctrl2_model(to_s("-")); + app.set_ctrl2_firmware(to_s("-")); + app.set_ctrl2_sn(to_s("-")); + app.set_ctrl2_vendor(to_s("-")); - // Show event details - let state = app_state.clone(); - let app_handle = app.as_weak(); - app.on_show_event_details(move |index| { - if let Ok(s) = state.lock() { - let events = s.get_events(); - if let Some(event) = events.get(index as usize) { - if let Some(a) = app_handle.upgrade() { - a.set_show_event_popup(true); - a.set_event_detail_time(to_s(&event.formatted_time())); - a.set_event_detail_level(to_s(&event.level)); - a.set_event_detail_message(to_s(&event.message)); - a.set_event_detail_source(to_s(&event.event_type)); - } - } - } - }); + // RAID info + app.set_raid1_name(to_s("RAID-01")); + app.set_raid1_level(to_s("RAID 5")); + app.set_raid1_status(to_s("Normal")); + app.set_raid1_capacity(to_s("2.0 TB")); + app.set_raid1_usage(to_s("45%")); - let app_handle = app.as_weak(); - app.on_close_event_details(move || { - if let Some(a) = app_handle.upgrade() { - a.set_show_event_popup(false); - } - }); + app.set_raid2_name(to_s("RAID-02")); + app.set_raid2_level(to_s("RAID 6")); + app.set_raid2_status(to_s("Normal")); + app.set_raid2_capacity(to_s("4.0 TB")); + app.set_raid2_usage(to_s("30%")); - // Create RAID dialog - let app_handle = app.as_weak(); - app.on_open_create_raid_dialog(move || { - if let Some(a) = app_handle.upgrade() { - a.set_show_create_raid_dialog(true); - a.set_create_raid_name(to_s("")); - a.set_create_raid_status(to_s("")); - a.set_create_raid_disk1(to_s("")); - a.set_create_raid_disk2(to_s("")); - a.set_create_raid_disk3(to_s("")); - a.set_create_raid_disk4(to_s("")); - } - }); + // Disk info + app.set_disk1_loc(to_s("Enclosure 0 Slot 0")); + app.set_disk1_vendor(to_s("Seagate")); + app.set_disk1_model(to_s("ST3000VX000")); + app.set_disk1_capacity(to_s("3.0 TB")); + app.set_disk1_status(to_s("Online")); - let app_handle = app.as_weak(); - app.on_close_create_raid_dialog(move || { - if let Some(a) = app_handle.upgrade() { - a.set_show_create_raid_dialog(false); - } - }); - - let state = app_state.clone(); - let app_handle = app.as_weak(); - app.on_confirm_create_raid(move || { - let state = state.clone(); - let app_handle = app_handle.clone(); - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - if let Ok(s) = state.lock() { - let _ = s.commit_config().await; - let _ = s.refresh().await; - if let Some(a) = app_handle.upgrade() { - a.set_show_create_raid_dialog(false); - update_ui_from_state(&a, &s); - } - } - }); - }); + app.set_disk2_loc(to_s("Enclosure 0 Slot 1")); + app.set_disk2_vendor(to_s("Seagate")); + app.set_disk2_model(to_s("ST3000VX000")); + app.set_disk2_capacity(to_s("3.0 TB")); + app.set_disk2_status(to_s("Online")); - // Delete RAID dialog - let app_handle = app.as_weak(); - app.on_open_delete_raid_dialog(move || { - if let Some(a) = app_handle.upgrade() { - a.set_show_delete_raid_dialog(true); - a.set_delete_raid_name(to_s("RAID-1")); - a.set_delete_raid_status(to_s("")); - } - }); + app.set_disk3_loc(to_s("Enclosure 0 Slot 2")); + app.set_disk3_vendor(to_s("Seagate")); + app.set_disk3_model(to_s("ST3000VX000")); + app.set_disk3_capacity(to_s("3.0 TB")); + app.set_disk3_status(to_s("Online")); - let app_handle = app.as_weak(); - app.on_close_delete_raid_dialog(move || { - if let Some(a) = app_handle.upgrade() { - a.set_show_delete_raid_dialog(false); - } - }); - - let state = app_state.clone(); - let app_handle = app.as_weak(); - app.on_confirm_delete_raid(move || { - let state = state.clone(); - let app_handle = app_handle.clone(); - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - if let Ok(s) = state.lock() { - let _ = s.refresh().await; - if let Some(a) = app_handle.upgrade() { - a.set_show_delete_raid_dialog(false); - update_ui_from_state(&a, &s); - } - } - }); - }); + app.set_disk4_loc(to_s("Enclosure 0 Slot 3")); + app.set_disk4_vendor(to_s("Seagate")); + app.set_disk4_model(to_s("ST3000VX000")); + app.set_disk4_capacity(to_s("3.0 TB")); + app.set_disk4_status(to_s("Online")); - // Rebuild RAID - let state = app_state.clone(); - app.on_rebuild_raid(move || { - let state = state.clone(); - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - if let Ok(s) = state.lock() { - let _ = s.rebuild_raid(1).await; - } - }); - }); + // Event info + app.set_evtime1(to_s("2026/03/29-10:30:00")); + app.set_evlevel1(to_s("Info")); + app.set_evsource1(to_s("System")); + app.set_evmsg1(to_s("Controller started successfully")); - // Commit config - let state = app_state.clone(); - app.on_commit_config(move || { - let state = state.clone(); - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - if let Ok(s) = state.lock() { - let _ = s.commit_config().await; - } - }); - }); + app.set_evtime2(to_s("2026/03/29-10:25:00")); + app.set_evlevel2(to_s("Info")); + app.set_evsource2(to_s("Disk")); + app.set_evmsg2(to_s("Disk online")); - // Disk operation dialog - let app_handle = app.as_weak(); - app.on_open_disk_operation_dialog(move || { - if let Some(a) = app_handle.upgrade() { - a.set_show_disk_dialog(true); - a.set_selected_disk(1); - a.set_disk_operation_status(to_s("")); - } - }); + app.set_evtime3(to_s("2026/03/29-10:20:00")); + app.set_evlevel3(to_s("Warning")); + app.set_evsource3(to_s("Enclosure")); + app.set_evmsg3(to_s("Temperature warning")); - let app_handle = app.as_weak(); - app.on_close_disk_operation_dialog(move || { - if let Some(a) = app_handle.upgrade() { - a.set_show_disk_dialog(false); - } - }); - - let state = app_state.clone(); - let app_handle = app.as_weak(); - app.on_confirm_disk_operation(move || { - let state = state.clone(); - let app_handle = app_handle.clone(); - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - if let Ok(s) = state.lock() { - let _ = s.refresh().await; - if let Some(a) = app_handle.upgrade() { - a.set_show_disk_dialog(false); - update_ui_from_state(&a, &s); - } - } - }); - }); + app.set_evtime4(to_s("2026/03/29-10:15:00")); + app.set_evlevel4(to_s("Info")); + app.set_evsource4(to_s("RAID")); + app.set_evmsg4(to_s("RAID-01 status: Normal")); + + app.set_evtime5(to_s("2026/03/29-10:10:00")); + app.set_evlevel5(to_s("Info")); + app.set_evsource5(to_s("Power")); + app.set_evmsg5(to_s("Power supply online")); + + tracing::info!("UI initialized, showing window..."); app.run()?; + + tracing::info!("Application closed"); Ok(()) } diff --git a/ui/main_window.slint b/ui/main_window.slint index 845290d..01bd71d 100644 --- a/ui/main_window.slint +++ b/ui/main_window.slint @@ -13,86 +13,86 @@ export component AppWindow inherits Window { callback menu_exit(); callback menu_add_controller(); - property connected: false; - property is_loading: false; - property status_text: "Disconnected"; - property current_tab: 0; + in-out property connected: false; + in-out property is_loading: false; + in-out property status_text: "Disconnected"; + in-out property current_tab: 0; - property controller_count: 0; - property raid_count: 0; - property disk_count: 0; - property auto_refresh: true; + in-out property controller_count: 0; + in-out property raid_count: 0; + in-out property disk_count: 0; + in-out property auto_refresh: true; - property selected_controller_index: 0; - property ctrl1_name: "RAID Controller"; - property ctrl1_ip: "192.168.1.100"; - property ctrl1_status: "Online"; - property ctrl1_model: "Accusys RAID 9000"; - property ctrl1_firmware: "v3.8.0"; - property ctrl1_sn: "ACC123456789"; - property ctrl1_vendor: "Accusys"; - property ctrl2_name: "-"; - property ctrl2_ip: "-"; - property ctrl2_status: "-"; - property ctrl2_model: "-"; - property ctrl2_firmware: "-"; - property ctrl2_sn: "-"; - property ctrl2_vendor: "-"; + in-out property selected_controller_index: 0; + in-out property ctrl1_name: "RAID Controller"; + in-out property ctrl1_ip: "192.168.1.100"; + in-out property ctrl1_status: "Online"; + in-out property ctrl1_model: "Accusys RAID 9000"; + in-out property ctrl1_firmware: "v3.8.0"; + in-out property ctrl1_sn: "ACC123456789"; + in-out property ctrl1_vendor: "Accusys"; + in-out property ctrl2_name: "-"; + in-out property ctrl2_ip: "-"; + in-out property ctrl2_status: "-"; + in-out property ctrl2_model: "-"; + in-out property ctrl2_firmware: "-"; + in-out property ctrl2_sn: "-"; + in-out property ctrl2_vendor: "-"; - property raid1_name: "RAID-01"; - property raid1_level: "RAID 5"; - property raid1_status: "Normal"; - property raid1_capacity: "2.0 TB"; - property raid1_usage: "45%"; - property raid1_usage_pct: 45.0; - property raid2_name: "RAID-02"; - property raid2_level: "RAID 6"; - property raid2_status: "Normal"; - property raid2_capacity: "4.0 TB"; - property raid2_usage: "30%"; - property raid2_usage_pct: 30.0; + in-out property raid1_name: "RAID-01"; + in-out property raid1_level: "RAID 5"; + in-out property raid1_status: "Normal"; + in-out property raid1_capacity: "2.0 TB"; + in-out property raid1_usage: "45%"; + in-out property raid1_usage_pct: 45.0; + in-out property raid2_name: "RAID-02"; + in-out property raid2_level: "RAID 6"; + in-out property raid2_status: "Normal"; + in-out property raid2_capacity: "4.0 TB"; + in-out property raid2_usage: "30%"; + in-out property raid2_usage_pct: 30.0; - property disk1_loc: "Enclosure 0 Slot 0"; - property disk1_model: "ST3000VX000"; - property disk1_status: "Online"; - property disk1_capacity: "3.0 TB"; - property disk1_vendor: "Seagate"; - property disk2_loc: "Enclosure 0 Slot 1"; - property disk2_model: "ST3000VX000"; - property disk2_status: "Online"; - property disk2_capacity: "3.0 TB"; - property disk2_vendor: "Seagate"; - property disk3_loc: "Enclosure 0 Slot 2"; - property disk3_model: "ST3000VX000"; - property disk3_status: "Online"; - property disk3_capacity: "3.0 TB"; - property disk3_vendor: "Seagate"; - property disk4_loc: "Enclosure 0 Slot 3"; - property disk4_model: "ST3000VX000"; - property disk4_status: "Online"; - property disk4_capacity: "3.0 TB"; - property disk4_vendor: "Seagate"; + in-out property disk1_loc: "Enclosure 0 Slot 0"; + in-out property disk1_model: "ST3000VX000"; + in-out property disk1_status: "Online"; + in-out property disk1_capacity: "3.0 TB"; + in-out property disk1_vendor: "Seagate"; + in-out property disk2_loc: "Enclosure 0 Slot 1"; + in-out property disk2_model: "ST3000VX000"; + in-out property disk2_status: "Online"; + in-out property disk2_capacity: "3.0 TB"; + in-out property disk2_vendor: "Seagate"; + in-out property disk3_loc: "Enclosure 0 Slot 2"; + in-out property disk3_model: "ST3000VX000"; + in-out property disk3_status: "Online"; + in-out property disk3_capacity: "3.0 TB"; + in-out property disk3_vendor: "Seagate"; + in-out property disk4_loc: "Enclosure 0 Slot 3"; + in-out property disk4_model: "ST3000VX000"; + in-out property disk4_status: "Online"; + in-out property disk4_capacity: "3.0 TB"; + in-out property disk4_vendor: "Seagate"; - property evtime1: "2026/03/29-10:30:00"; - property evlevel1: "Info"; - property evmsg1: "Controller started successfully"; - property evsource1: "System"; - property evtime2: "2026/03/29-10:25:00"; - property evlevel2: "Info"; - property evmsg2: "Disk online"; - property evsource2: "Disk"; - property evtime3: "2026/03/29-10:20:00"; - property evlevel3: "Warning"; - property evmsg3: "Temperature warning"; - property evsource3: "Enclosure"; - property evtime4: "2026/03/29-10:15:00"; - property evlevel4: "Info"; - property evmsg4: "RAID-01 status: Normal"; - property evsource4: "RAID"; - property evtime5: "2026/03/29-10:10:00"; - property evlevel5: "Info"; - property evmsg5: "Power supply online"; - property evsource5: "Power"; + in-out property evtime1: "2026/03/29-10:30:00"; + in-out property evlevel1: "Info"; + in-out property evmsg1: "Controller started successfully"; + in-out property evsource1: "System"; + in-out property evtime2: "2026/03/29-10:25:00"; + in-out property evlevel2: "Info"; + in-out property evmsg2: "Disk online"; + in-out property evsource2: "Disk"; + in-out property evtime3: "2026/03/29-10:20:00"; + in-out property evlevel3: "Warning"; + in-out property evmsg3: "Temperature warning"; + in-out property evsource3: "Enclosure"; + in-out property evtime4: "2026/03/29-10:15:00"; + in-out property evlevel4: "Info"; + in-out property evmsg4: "RAID-01 status: Normal"; + in-out property evsource4: "RAID"; + in-out property evtime5: "2026/03/29-10:10:00"; + in-out property evlevel5: "Info"; + in-out property evmsg5: "Power supply online"; + in-out property evsource5: "Power"; VerticalLayout { spacing: 0;