diff --git a/src/services/resource_provider.rs b/src/services/resource_provider.rs index ad24765..ee94fc4 100644 --- a/src/services/resource_provider.rs +++ b/src/services/resource_provider.rs @@ -1301,25 +1301,83 @@ impl ResourceProviderService { // Fetch node data from grid let grid_data = self.grid_service.fetch_node_data(grid_node_id).await?; - // Create FarmNode from grid data - let node = FarmNodeBuilder::new() - .id(format!("grid_node_{}", grid_node_id)) - .name(format!("Grid Node {}", grid_node_id)) - .location(format!("{}, {}", - if grid_data.city.is_empty() { "Unknown City" } else { &grid_data.city }, - if grid_data.country.is_empty() { "Unknown Country" } else { &grid_data.country } - )) - .status(NodeStatus::Online) - .capacity(grid_data.total_resources.clone()) - .used_capacity(grid_data.used_resources.clone()) - .uptime_percentage(99.0) // Default uptime - .earnings_today_usd(Decimal::ZERO) - .health_score(100.0) - .region(if grid_data.country.is_empty() { "Unknown".to_string() } else { grid_data.country.clone() }) - .node_type("MyceliumNode".to_string()) - .grid_node_id(grid_node_id) - .grid_data(grid_data) - .build()?; + // FIX: Calculate slice data using the slice calculator + let total_base_slices = self.slice_calculator.calculate_max_base_slices(&grid_data.total_resources); + + // Generate unique node ID for SLA + let node_id = format!("grid_node_{}", grid_node_id); + let node_id_for_sla = node_id.clone(); + + // Create FarmNode from grid data with proper slice calculations + let mut node = FarmNode { + id: node_id, + name: if grid_data.farm_name.is_empty() { format!("Grid Node {}", grid_node_id) } else { grid_data.farm_name.clone() }, + location: { + let city = if grid_data.city.is_empty() { "Unknown" } else { &grid_data.city }; + let country = if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country }; + if city == "Unknown" { + country.to_string() + } else { + format!("{}, {}", city, country) + } + }, + status: NodeStatus::Online, // Assume online if we can fetch from grid + capacity: grid_data.total_resources.clone(), + used_capacity: grid_data.used_resources.clone(), + uptime_percentage: 99.8, // Clean uptime value for grid nodes + farming_start_date: Utc::now() - chrono::Duration::days(30), // Default farming start + last_updated: grid_data.last_updated, + last_seen: Some(Utc::now()), + health_score: 98.5, + utilization_7_day_avg: 65.0, // Default utilization + slice_formats_supported: vec!["1x1".to_string(), "2x2".to_string(), "4x4".to_string()], + rental_options: None, + earnings_today_usd: Decimal::ZERO, + region: if grid_data.country.is_empty() { "Unknown".to_string() } else { grid_data.country.clone() }, + node_type: "MyceliumNode".to_string(), + slice_formats: None, // Not used in new slice system + staking_options: None, + availability_status: crate::models::user::NodeAvailabilityStatus::Available, + grid_node_id: Some(grid_node_id.to_string()), + grid_data: Some(serde_json::to_value(&grid_data).unwrap_or_default()), + node_group_id: None, + group_assignment_date: None, + group_slice_format: None, + group_slice_price: None, + + // FIX: Marketplace SLA field with clean uptime value + marketplace_sla: Some(MarketplaceSLA { + id: format!("sla-{}", node_id_for_sla), + name: "Standard Marketplace SLA".to_string(), + uptime_guarantee: 99.8, + response_time_hours: 24, + resolution_time_hours: 48, + penalty_rate: 0.01, + uptime_guarantee_percentage: 99.8, + bandwidth_guarantee_mbps: grid_data.total_resources.bandwidth_mbps as f32, + base_slice_price: SlicePricing::default().base_price_per_hour, + last_updated: Utc::now(), + }), + + // FIX: Automatic slice management fields + total_base_slices: total_base_slices as i32, + allocated_base_slices: 0, + slice_allocations: Vec::new(), + available_combinations: Vec::new(), // Will be calculated below + slice_pricing: Some(serde_json::to_value(&SlicePricing::default()).unwrap_or_default()), + slice_last_calculated: Some(Utc::now()), + }; + + // FIX: Generate initial slice combinations + let combinations = self.slice_calculator.generate_slice_combinations( + node.total_base_slices as u32, + node.allocated_base_slices as u32, + &node, + user_email + ); + node.available_combinations = combinations.iter() + .map(|c| serde_json::to_value(c).unwrap_or_default()) + .collect(); // Save to persistent storage let mut persistent_data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); @@ -1356,26 +1414,82 @@ impl ResourceProviderService { } let grid_data = self.grid_service.fetch_node_data(grid_node_id).await.map_err(|e| e.to_string())?; - // Build node - let node = FarmNodeBuilder::new() - .id(node_id) - .name(format!("Grid Node {}", grid_node_id)) - .location(format!("{}, {}", - if grid_data.city.is_empty() { "Unknown City" } else { &grid_data.city }, - if grid_data.country.is_empty() { "Unknown Country" } else { &grid_data.country } - )) - .status(NodeStatus::Online) - .capacity(grid_data.total_resources.clone()) - .used_capacity(grid_data.used_resources.clone()) - .uptime_percentage(99.0) - .earnings_today_usd(Decimal::ZERO) - .health_score(100.0) - .region(if grid_data.country.is_empty() { "Unknown".to_string() } else { grid_data.country.clone() }) - .node_type("MyceliumNode".to_string()) - .grid_node_id(grid_node_id) - .grid_data(grid_data) - .build() - .map_err(|e| e.to_string())?; + // FIX: Calculate slice data using the slice calculator (same as add_node_from_grid) + let total_base_slices = self.slice_calculator.calculate_max_base_slices(&grid_data.total_resources); + + // Generate unique node ID for SLA + let node_id_for_sla = node_id.clone(); + + // Build node with proper slice calculations + let mut node = FarmNode { + id: node_id, + name: if grid_data.farm_name.is_empty() { format!("Grid Node {}", grid_node_id) } else { grid_data.farm_name.clone() }, + location: { + let city = if grid_data.city.is_empty() { "Unknown" } else { &grid_data.city }; + let country = if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country }; + if city == "Unknown" { + country.to_string() + } else { + format!("{}, {}", city, country) + } + }, + status: NodeStatus::Online, // Assume online if we can fetch from grid + capacity: grid_data.total_resources.clone(), + used_capacity: grid_data.used_resources.clone(), + uptime_percentage: 99.8, // Clean uptime value for grid nodes + farming_start_date: Utc::now() - chrono::Duration::days(30), // Default farming start + last_updated: grid_data.last_updated, + last_seen: Some(Utc::now()), + health_score: 98.5, + utilization_7_day_avg: 65.0, // Default utilization + slice_formats_supported: vec!["1x1".to_string(), "2x2".to_string(), "4x4".to_string()], + rental_options: None, + earnings_today_usd: Decimal::ZERO, + region: if grid_data.country.is_empty() { "Unknown".to_string() } else { grid_data.country.clone() }, + node_type: "MyceliumNode".to_string(), + slice_formats: None, // Not used in new slice system + staking_options: None, + availability_status: crate::models::user::NodeAvailabilityStatus::Available, + grid_node_id: Some(grid_node_id.to_string()), + grid_data: Some(serde_json::to_value(&grid_data).unwrap_or_default()), + node_group_id: None, + group_assignment_date: None, + group_slice_format: None, + group_slice_price: None, + + // FIX: Marketplace SLA field with clean uptime value + marketplace_sla: Some(MarketplaceSLA { + id: format!("sla-{}", node_id_for_sla), + name: "Standard Marketplace SLA".to_string(), + uptime_guarantee: 99.8, + response_time_hours: 24, + resolution_time_hours: 48, + penalty_rate: 0.01, + uptime_guarantee_percentage: 99.8, + bandwidth_guarantee_mbps: grid_data.total_resources.bandwidth_mbps as f32, + base_slice_price: SlicePricing::default().base_price_per_hour, + last_updated: Utc::now(), + }), + + // FIX: Automatic slice management fields + total_base_slices: total_base_slices as i32, + allocated_base_slices: 0, + slice_allocations: Vec::new(), + available_combinations: Vec::new(), // Will be calculated below + slice_pricing: Some(serde_json::to_value(&SlicePricing::default()).unwrap_or_default()), + slice_last_calculated: Some(Utc::now()), + }; + + // FIX: Generate initial slice combinations + let combinations = self.slice_calculator.generate_slice_combinations( + node.total_base_slices as u32, + node.allocated_base_slices as u32, + &node, + user_email + ); + node.available_combinations = combinations.iter() + .map(|c| serde_json::to_value(c).unwrap_or_default()) + .collect(); persistent_data.nodes.push(node.clone()); added_nodes.push(node); @@ -1472,6 +1586,50 @@ impl ResourceProviderService { // Apply slice formats if provided if !slice_formats.is_empty() { node.slice_formats = Some(slice_formats.clone()); } + // FIX: Calculate and set slice data + let total_base_slices = self.slice_calculator.calculate_max_base_slices(&node.capacity); + node.total_base_slices = total_base_slices as i32; + node.allocated_base_slices = 0; + node.slice_allocations = Vec::new(); + + // Generate slice combinations + let combinations = self.slice_calculator.generate_slice_combinations( + total_base_slices, + 0, // No allocated slices yet + &node, + user_email + ); + node.available_combinations = combinations.iter() + .map(|c| serde_json::to_value(c).unwrap_or_default()) + .collect(); + node.slice_last_calculated = Some(Utc::now()); + + // Set marketplace SLA if not already set + if node.marketplace_sla.is_none() { + node.marketplace_sla = Some(MarketplaceSLA { + id: format!("sla-{}", node.id), + name: "Standard Marketplace SLA".to_string(), + uptime_guarantee: 99.8, + response_time_hours: 24, + resolution_time_hours: 48, + penalty_rate: 0.01, + uptime_guarantee_percentage: 99.8, + bandwidth_guarantee_mbps: node.capacity.bandwidth_mbps as f32, + base_slice_price: SlicePricing::default().base_price_per_hour, + last_updated: Utc::now(), + }); + } + + // Set default slice pricing if not already set + if node.slice_pricing.is_none() { + let default_pricing = SlicePricing { + base_price_per_hour: rust_decimal::Decimal::try_from(0.50).unwrap_or_default(), + currency: "USD".to_string(), + pricing_multiplier: rust_decimal::Decimal::from(1), + }; + node.slice_pricing = Some(serde_json::to_value(&default_pricing).unwrap_or_default()); + } + persistent_data.nodes.push(node.clone()); added_nodes.push(node); } @@ -1554,6 +1712,34 @@ impl ResourceProviderService { node.slice_formats = Some(slice_formats.clone()); } + // SLICE CALCULATION FIX: Calculate and set slice data + let total_base_slices = self.slice_calculator.calculate_max_base_slices(&node.capacity); + node.total_base_slices = total_base_slices as i32; + node.allocated_base_slices = 0; + node.slice_allocations = Vec::new(); + + // Generate slice combinations + let combinations = self.slice_calculator.generate_slice_combinations( + total_base_slices, + 0, // No allocated slices yet + &node, + user_email + ); + node.available_combinations = combinations.iter() + .map(|c| serde_json::to_value(c).unwrap_or_default()) + .collect(); + node.slice_last_calculated = Some(Utc::now()); + + // Set default slice pricing if not already set + if node.slice_pricing.is_none() { + let default_pricing = crate::services::slice_calculator::SlicePricing { + base_price_per_hour: rust_decimal::Decimal::try_from(0.50).unwrap_or_default(), + currency: "USD".to_string(), + pricing_multiplier: rust_decimal::Decimal::from(1), + }; + node.slice_pricing = Some(serde_json::to_value(&default_pricing).unwrap_or_default()); + } + // Push to in-memory and batch list persistent_data.nodes.push(node.clone()); added_nodes.push(node); @@ -1654,6 +1840,34 @@ impl ResourceProviderService { node.slice_formats = Some(slice_formats.clone()); } + // SLICE CALCULATION FIX: Calculate and set slice data for individual pricing method + let total_base_slices = self.slice_calculator.calculate_max_base_slices(&node.capacity); + node.total_base_slices = total_base_slices as i32; + node.allocated_base_slices = 0; + node.slice_allocations = Vec::new(); + + // Generate slice combinations + let combinations = self.slice_calculator.generate_slice_combinations( + total_base_slices, + 0, // No allocated slices yet + &node, + user_email + ); + node.available_combinations = combinations.iter() + .map(|c| serde_json::to_value(c).unwrap_or_default()) + .collect(); + node.slice_last_calculated = Some(Utc::now()); + + // Set default slice pricing if not already set + if node.slice_pricing.is_none() { + let default_pricing = crate::services::slice_calculator::SlicePricing { + base_price_per_hour: rust_decimal::Decimal::try_from(0.50).unwrap_or_default(), + currency: "USD".to_string(), + pricing_multiplier: rust_decimal::Decimal::from(1), + }; + node.slice_pricing = Some(serde_json::to_value(&default_pricing).unwrap_or_default()); + } + persistent_data.nodes.push(node.clone()); added_nodes.push(node); } @@ -1721,6 +1935,50 @@ impl ResourceProviderService { node.slice_formats = Some(slice_formats); } + // FIX: Calculate and set slice data + let total_base_slices = self.slice_calculator.calculate_max_base_slices(&node.capacity); + node.total_base_slices = total_base_slices as i32; + node.allocated_base_slices = 0; + node.slice_allocations = Vec::new(); + + // Generate slice combinations + let combinations = self.slice_calculator.generate_slice_combinations( + total_base_slices, + 0, // No allocated slices yet + &node, + user_email + ); + node.available_combinations = combinations.iter() + .map(|c| serde_json::to_value(c).unwrap_or_default()) + .collect(); + node.slice_last_calculated = Some(Utc::now()); + + // Set marketplace SLA if not already set + if node.marketplace_sla.is_none() { + node.marketplace_sla = Some(MarketplaceSLA { + id: format!("sla-{}", node.id), + name: "Standard Marketplace SLA".to_string(), + uptime_guarantee: 99.8, + response_time_hours: 24, + resolution_time_hours: 48, + penalty_rate: 0.01, + uptime_guarantee_percentage: 99.8, + bandwidth_guarantee_mbps: node.capacity.bandwidth_mbps as f32, + base_slice_price: SlicePricing::default().base_price_per_hour, + last_updated: Utc::now(), + }); + } + + // Set default slice pricing if not already set + if node.slice_pricing.is_none() { + let default_pricing = SlicePricing { + base_price_per_hour: rust_decimal::Decimal::try_from(0.50).unwrap_or_default(), + currency: "USD".to_string(), + pricing_multiplier: rust_decimal::Decimal::from(1), + }; + node.slice_pricing = Some(serde_json::to_value(&default_pricing).unwrap_or_default()); + } + // Add node to user's data let mut persistent_data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); @@ -1767,41 +2025,53 @@ impl ResourceProviderService { None }; - // Create the node - let node = FarmNode { - id: format!("grid_node_{}", grid_node_id), - name: format!("Grid Node {}", grid_node_id), - location: format!("{}, {}", - if grid_data.city.is_empty() { "Unknown" } else { &grid_data.city }, - if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country } - ), + // FIX: Calculate slice data using the slice calculator + let node_capacity = crate::models::user::NodeCapacity { + cpu_cores: grid_data.total_resources.cpu_cores, + memory_gb: grid_data.total_resources.memory_gb, + storage_gb: grid_data.total_resources.storage_gb, + bandwidth_mbps: grid_data.total_resources.bandwidth_mbps, + ssd_storage_gb: grid_data.total_resources.ssd_storage_gb, + hdd_storage_gb: grid_data.total_resources.hdd_storage_gb, + ram_gb: grid_data.total_resources.ram_gb, + }; + + let total_base_slices = self.slice_calculator.calculate_max_base_slices(&node_capacity); + let node_id = format!("grid_node_{}", grid_node_id); + let bandwidth_mbps = node_capacity.bandwidth_mbps; // Store before move + + // Create the node with proper slice calculations + let mut node = FarmNode { + id: node_id.clone(), + name: if grid_data.farm_name.is_empty() { format!("Grid Node {}", grid_node_id) } else { grid_data.farm_name.clone() }, + location: { + let city = if grid_data.city.is_empty() { "Unknown" } else { &grid_data.city }; + let country = if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country }; + if city == "Unknown" { + country.to_string() + } else { + format!("{}, {}", city, country) + } + }, status: crate::models::user::NodeStatus::Online, - capacity: crate::models::user::NodeCapacity { - cpu_cores: grid_data.total_resources.cpu_cores, - memory_gb: grid_data.total_resources.memory_gb, - storage_gb: grid_data.total_resources.storage_gb, - bandwidth_mbps: grid_data.total_resources.bandwidth_mbps, - ssd_storage_gb: grid_data.total_resources.ssd_storage_gb, - hdd_storage_gb: grid_data.total_resources.hdd_storage_gb, - ram_gb: grid_data.total_resources.ram_gb, - }, + capacity: node_capacity, used_capacity: crate::models::user::NodeCapacity { - cpu_cores: 0, - memory_gb: 0, - storage_gb: 0, - bandwidth_mbps: 0, - ssd_storage_gb: 0, - hdd_storage_gb: 0, - ram_gb: 0, + cpu_cores: grid_data.used_resources.cpu_cores, + memory_gb: grid_data.used_resources.memory_gb, + storage_gb: grid_data.used_resources.storage_gb, + bandwidth_mbps: grid_data.used_resources.bandwidth_mbps, + ssd_storage_gb: grid_data.used_resources.ssd_storage_gb, + hdd_storage_gb: grid_data.used_resources.hdd_storage_gb, + ram_gb: grid_data.used_resources.ram_gb, }, - uptime_percentage: 99.0, + uptime_percentage: 99.8, // Clean uptime value for grid nodes farming_start_date: grid_data.last_updated, last_updated: grid_data.last_updated, - utilization_7_day_avg: 0.0, - slice_formats_supported: if slice_formats.is_empty() { Vec::new() } else { slice_formats.clone() }, + utilization_7_day_avg: 65.0, // Default utilization + slice_formats_supported: if slice_formats.is_empty() { vec!["1x1".to_string(), "2x2".to_string(), "4x4".to_string()] } else { slice_formats.clone() }, earnings_today_usd: Decimal::ZERO, last_seen: Some(Utc::now()), - health_score: 100.0, + health_score: 98.5, region: if grid_data.country.is_empty() { "Unknown" } else { &grid_data.country }.to_string(), node_type: "MyceliumNode".to_string(), slice_formats: if slice_formats.is_empty() { None } else { Some(slice_formats.clone()) }, @@ -1815,17 +2085,40 @@ impl ResourceProviderService { group_slice_format: None, group_slice_price: None, - // NEW: Marketplace SLA field (None for basic grid nodes) - marketplace_sla: None, + // FIX: Marketplace SLA field with proper data + marketplace_sla: Some(MarketplaceSLA { + id: format!("sla-{}", node_id), + name: "Standard Marketplace SLA".to_string(), + uptime_guarantee: 99.8, + response_time_hours: 24, + resolution_time_hours: 48, + penalty_rate: 0.01, + uptime_guarantee_percentage: 99.8, + bandwidth_guarantee_mbps: bandwidth_mbps as f32, + base_slice_price: SlicePricing::default().base_price_per_hour, + last_updated: Utc::now(), + }), - total_base_slices: 0, + // FIX: Automatic slice management fields with proper calculations + total_base_slices: total_base_slices as i32, allocated_base_slices: 0, slice_allocations: Vec::new(), - available_combinations: Vec::new(), - slice_pricing: Some(serde_json::to_value(&crate::services::slice_calculator::SlicePricing::default()).unwrap_or_default()), - slice_last_calculated: None, + available_combinations: Vec::new(), // Will be calculated below + slice_pricing: Some(serde_json::to_value(&SlicePricing::default()).unwrap_or_default()), + slice_last_calculated: Some(Utc::now()), }; + // FIX: Generate initial slice combinations + let combinations = self.slice_calculator.generate_slice_combinations( + node.total_base_slices as u32, + node.allocated_base_slices as u32, + &node, + user_email + ); + node.available_combinations = combinations.iter() + .map(|c| serde_json::to_value(c).unwrap_or_default()) + .collect(); + // Save to persistent storage let mut persistent_data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); diff --git a/src/static/js/dashboard-layout-init.js b/src/static/js/dashboard-layout-init.js new file mode 100644 index 0000000..c3a02cb --- /dev/null +++ b/src/static/js/dashboard-layout-init.js @@ -0,0 +1,50 @@ +/** + * Dashboard Layout Initialization - CSP-compliant external script + * Handles messaging system initialization and other dashboard-wide functionality + */ + +(function() { + 'use strict'; + + /** + * Initialize messaging system with proper error handling + */ + function initializeMessagingSystem() { + try { + // Check if MessagingSystem is available + if (typeof MessagingSystem !== 'undefined') { + // Create global messaging system instance if it doesn't exist + if (!window.messagingSystem) { + console.log('🔧 Creating MessagingSystem instance for dashboard'); + window.messagingSystem = new MessagingSystem(); + } else { + console.log('✅ MessagingSystem already initialized'); + } + } else { + console.warn('⚠️ MessagingSystem class not available - messaging features may not work'); + } + } catch (error) { + console.error('❌ Failed to initialize messaging system:', error); + } + } + + /** + * Initialize dashboard layout components + */ + function initializeDashboard() { + // Initialize messaging system + initializeMessagingSystem(); + + // Add any other dashboard-wide initialization here + console.log('✅ Dashboard layout initialized'); + } + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeDashboard); + } else { + // DOM is already ready + initializeDashboard(); + } + +})(); \ No newline at end of file diff --git a/src/views/dashboard/layout.html b/src/views/dashboard/layout.html index f3cd05d..04b8e14 100644 --- a/src/views/dashboard/layout.html +++ b/src/views/dashboard/layout.html @@ -224,15 +224,5 @@ {% block scripts %} - + {% endblock %} \ No newline at end of file diff --git a/src/views/dashboard/resource_provider.html b/src/views/dashboard/resource_provider.html index 203efc5..84779e1 100644 --- a/src/views/dashboard/resource_provider.html +++ b/src/views/dashboard/resource_provider.html @@ -303,9 +303,12 @@