feat: add slice calculator integration and marketplace SLA for grid nodes
This commit is contained in:
@@ -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,16 +2025,8 @@ 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 }
|
||||
),
|
||||
status: crate::models::user::NodeStatus::Online,
|
||||
capacity: crate::models::user::NodeCapacity {
|
||||
// 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,
|
||||
@@ -1784,24 +2034,44 @@ impl ResourceProviderService {
|
||||
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: 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);
|
||||
|
||||
|
50
src/static/js/dashboard-layout-init.js
Normal file
50
src/static/js/dashboard-layout-init.js
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
})();
|
@@ -224,15 +224,5 @@
|
||||
{% block scripts %}
|
||||
<script src="/static/js/dashboard_layout.js"></script>
|
||||
<script src="/static/js/messaging-system.js"></script>
|
||||
<script>
|
||||
// Dynamic user detection for messaging system
|
||||
try {
|
||||
// Initialize messaging system with dynamic user detection
|
||||
if (window.MessagingSystem) {
|
||||
window.MessagingSystem.initializeWithDynamicUser();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize messaging system:', error);
|
||||
}
|
||||
</script>
|
||||
<script src="/static/js/dashboard-layout-init.js"></script>
|
||||
{% endblock %}
|
@@ -303,9 +303,12 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-primary" data-action="node.view" data-node-id="{{ node.id }}">
|
||||
<button class="btn btn-outline-primary" onclick="viewNodeDetails('{{ node.id }}')" title="View Details">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger" onclick="deleteNodeConfiguration('{{ node.id }}')" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@@ -150,6 +150,232 @@
|
||||
"group_slice_price": null,
|
||||
"last_seen": "2025-09-09T03:03:44.241206636Z",
|
||||
"health_score": 100.0
|
||||
},
|
||||
{
|
||||
"id": "grid_node_10",
|
||||
"location": "Unknown, Belgium",
|
||||
"status": "Online",
|
||||
"capacity": {
|
||||
"cpu_cores": 8,
|
||||
"memory_gb": 15,
|
||||
"storage_gb": 402559,
|
||||
"bandwidth_mbps": 1000,
|
||||
"ssd_storage_gb": 223,
|
||||
"hdd_storage_gb": 402336,
|
||||
"ram_gb": 15
|
||||
},
|
||||
"used_capacity": {
|
||||
"cpu_cores": 4,
|
||||
"memory_gb": 10,
|
||||
"storage_gb": 137,
|
||||
"bandwidth_mbps": 0,
|
||||
"ssd_storage_gb": 137,
|
||||
"hdd_storage_gb": 0,
|
||||
"ram_gb": 10
|
||||
},
|
||||
"uptime_percentage": 99.0,
|
||||
"farming_start_date": "2025-08-10T03:10:19.936729614Z",
|
||||
"last_updated": "2025-09-09T03:10:19.936733378Z",
|
||||
"utilization_7_day_avg": 65.0,
|
||||
"slice_formats_supported": [
|
||||
"1x1",
|
||||
"2x2",
|
||||
"4x4"
|
||||
],
|
||||
"rental_options": null,
|
||||
"total_base_slices": 0,
|
||||
"allocated_base_slices": 0,
|
||||
"earnings_today_usd": 0.0,
|
||||
"grid_node_id": "10",
|
||||
"available_combinations": [],
|
||||
"slice_allocations": [],
|
||||
"slice_last_calculated": null,
|
||||
"marketplace_sla": {
|
||||
"id": "sla-repair-e3b05c2c-5eb6-4304-93c0-7a9c7348636a",
|
||||
"name": "Repaired Node SLA",
|
||||
"uptime_guarantee": 99.8,
|
||||
"response_time_hours": 24,
|
||||
"resolution_time_hours": 48,
|
||||
"penalty_rate": 0.01,
|
||||
"uptime_guarantee_percentage": 99.8,
|
||||
"base_slice_price": 0.5,
|
||||
"bandwidth_guarantee_mbps": 100.0,
|
||||
"last_updated": "2025-09-09T03:13:53.570999450Z"
|
||||
},
|
||||
"slice_pricing": {
|
||||
"base_price_per_hour": 1.0,
|
||||
"currency": "USD",
|
||||
"pricing_multiplier": 1.0
|
||||
},
|
||||
"grid_data": {
|
||||
"capacity": {
|
||||
"bandwidth_mbps": 1000,
|
||||
"cpu_cores": 8,
|
||||
"hdd_storage_gb": 402336,
|
||||
"memory_gb": 15,
|
||||
"ram_gb": 15,
|
||||
"ssd_storage_gb": 223,
|
||||
"storage_gb": 402559
|
||||
},
|
||||
"certification_type": "Diy",
|
||||
"city": "Unknown",
|
||||
"country": "Belgium",
|
||||
"farm_id": 1,
|
||||
"farm_name": "Freefarm",
|
||||
"farming_policy_id": 1,
|
||||
"grid_node_id": 10,
|
||||
"last_updated": "2025-09-09T03:10:19.936676347Z",
|
||||
"location": "Unknown, Belgium",
|
||||
"node_id": 10,
|
||||
"public_ips": [
|
||||
"192.168.1.100"
|
||||
],
|
||||
"status": "Online",
|
||||
"total_resources": {
|
||||
"bandwidth_mbps": 1000,
|
||||
"cpu_cores": 8,
|
||||
"hdd_storage_gb": 402336,
|
||||
"memory_gb": 15,
|
||||
"ram_gb": 15,
|
||||
"ssd_storage_gb": 223,
|
||||
"storage_gb": 402559
|
||||
},
|
||||
"uptime": 99.5,
|
||||
"used_resources": {
|
||||
"bandwidth_mbps": 0,
|
||||
"cpu_cores": 4,
|
||||
"hdd_storage_gb": 0,
|
||||
"memory_gb": 10,
|
||||
"ram_gb": 10,
|
||||
"ssd_storage_gb": 137,
|
||||
"storage_gb": 137
|
||||
}
|
||||
},
|
||||
"slice_formats": null,
|
||||
"name": "Grid Node 10",
|
||||
"region": "Belgium",
|
||||
"node_type": "MyceliumNode",
|
||||
"staking_options": null,
|
||||
"availability_status": "Available",
|
||||
"node_group_id": null,
|
||||
"group_assignment_date": null,
|
||||
"group_slice_format": null,
|
||||
"group_slice_price": null,
|
||||
"last_seen": "2025-09-09T03:10:19.936736220Z",
|
||||
"health_score": 100.0
|
||||
},
|
||||
{
|
||||
"id": "grid_node_12",
|
||||
"location": "Unknown, Belgium",
|
||||
"status": "Online",
|
||||
"capacity": {
|
||||
"cpu_cores": 8,
|
||||
"memory_gb": 15,
|
||||
"storage_gb": 402559,
|
||||
"bandwidth_mbps": 1000,
|
||||
"ssd_storage_gb": 223,
|
||||
"hdd_storage_gb": 402336,
|
||||
"ram_gb": 15
|
||||
},
|
||||
"used_capacity": {
|
||||
"cpu_cores": 5,
|
||||
"memory_gb": 12,
|
||||
"storage_gb": 165,
|
||||
"bandwidth_mbps": 0,
|
||||
"ssd_storage_gb": 165,
|
||||
"hdd_storage_gb": 0,
|
||||
"ram_gb": 12
|
||||
},
|
||||
"uptime_percentage": 99.0,
|
||||
"farming_start_date": "2025-08-10T03:30:24.732131670Z",
|
||||
"last_updated": "2025-09-09T03:30:24.732143334Z",
|
||||
"utilization_7_day_avg": 65.0,
|
||||
"slice_formats_supported": [
|
||||
"1x1",
|
||||
"2x2",
|
||||
"4x4"
|
||||
],
|
||||
"rental_options": null,
|
||||
"total_base_slices": 0,
|
||||
"allocated_base_slices": 0,
|
||||
"earnings_today_usd": 0.0,
|
||||
"grid_node_id": "12",
|
||||
"available_combinations": [],
|
||||
"slice_allocations": [],
|
||||
"slice_last_calculated": null,
|
||||
"marketplace_sla": {
|
||||
"id": "sla-repair-25c05a40-790f-4a80-8ef8-fa12a726c0ff",
|
||||
"name": "Repaired Node SLA",
|
||||
"uptime_guarantee": 99.8,
|
||||
"response_time_hours": 24,
|
||||
"resolution_time_hours": 48,
|
||||
"penalty_rate": 0.01,
|
||||
"uptime_guarantee_percentage": 99.8,
|
||||
"base_slice_price": 0.5,
|
||||
"bandwidth_guarantee_mbps": 100.0,
|
||||
"last_updated": "2025-09-09T03:30:27.416132016Z"
|
||||
},
|
||||
"slice_pricing": {
|
||||
"base_price_per_hour": 1.0,
|
||||
"currency": "USD",
|
||||
"pricing_multiplier": 1.0
|
||||
},
|
||||
"grid_data": {
|
||||
"capacity": {
|
||||
"bandwidth_mbps": 1000,
|
||||
"cpu_cores": 8,
|
||||
"hdd_storage_gb": 402336,
|
||||
"memory_gb": 15,
|
||||
"ram_gb": 15,
|
||||
"ssd_storage_gb": 223,
|
||||
"storage_gb": 402559
|
||||
},
|
||||
"certification_type": "Diy",
|
||||
"city": "Unknown",
|
||||
"country": "Belgium",
|
||||
"farm_id": 1,
|
||||
"farm_name": "Freefarm",
|
||||
"farming_policy_id": 1,
|
||||
"grid_node_id": 12,
|
||||
"last_updated": "2025-09-09T03:30:24.731928799Z",
|
||||
"location": "Unknown, Belgium",
|
||||
"node_id": 12,
|
||||
"public_ips": [
|
||||
"192.168.1.100"
|
||||
],
|
||||
"status": "Online",
|
||||
"total_resources": {
|
||||
"bandwidth_mbps": 1000,
|
||||
"cpu_cores": 8,
|
||||
"hdd_storage_gb": 402336,
|
||||
"memory_gb": 15,
|
||||
"ram_gb": 15,
|
||||
"ssd_storage_gb": 223,
|
||||
"storage_gb": 402559
|
||||
},
|
||||
"uptime": 99.5,
|
||||
"used_resources": {
|
||||
"bandwidth_mbps": 0,
|
||||
"cpu_cores": 5,
|
||||
"hdd_storage_gb": 0,
|
||||
"memory_gb": 12,
|
||||
"ram_gb": 12,
|
||||
"ssd_storage_gb": 165,
|
||||
"storage_gb": 165
|
||||
}
|
||||
},
|
||||
"slice_formats": null,
|
||||
"name": "Grid Node 12",
|
||||
"region": "Belgium",
|
||||
"node_type": "MyceliumNode",
|
||||
"staking_options": null,
|
||||
"availability_status": "Available",
|
||||
"node_group_id": null,
|
||||
"group_assignment_date": null,
|
||||
"group_slice_format": null,
|
||||
"group_slice_price": null,
|
||||
"last_seen": "2025-09-09T03:30:24.732150580Z",
|
||||
"health_score": 100.0
|
||||
}
|
||||
],
|
||||
"resource_provider_earnings": [],
|
||||
|
15
user_data/user123_at_example_com_cart.json
Normal file
15
user_data/user123_at_example_com_cart.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"user_id": "user123@example.com",
|
||||
"items": [
|
||||
{
|
||||
"product_id": "user_user0_example_com_fa373486",
|
||||
"quantity": 1,
|
||||
"selected_specifications": {},
|
||||
"added_at": "2025-09-09T03:32:30.573242755Z",
|
||||
"updated_at": "2025-09-09T03:32:30.573242755Z"
|
||||
}
|
||||
],
|
||||
"session_id": null,
|
||||
"created_at": "2025-09-09T03:32:30.573133861Z",
|
||||
"updated_at": "2025-09-09T03:32:30.573271484Z"
|
||||
}
|
@@ -18,7 +18,121 @@
|
||||
"deleted": null,
|
||||
"deleted_at": null,
|
||||
"deletion_reason": null,
|
||||
"nodes": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "grid_node_2007",
|
||||
"location": "Unknown, Belgium",
|
||||
"status": "Online",
|
||||
"capacity": {
|
||||
"cpu_cores": 24,
|
||||
"memory_gb": 188,
|
||||
"storage_gb": 8859,
|
||||
"bandwidth_mbps": 1000,
|
||||
"ssd_storage_gb": 476,
|
||||
"hdd_storage_gb": 8383,
|
||||
"ram_gb": 188
|
||||
},
|
||||
"used_capacity": {
|
||||
"cpu_cores": 9,
|
||||
"memory_gb": 55,
|
||||
"storage_gb": 343,
|
||||
"bandwidth_mbps": 0,
|
||||
"ssd_storage_gb": 343,
|
||||
"hdd_storage_gb": 0,
|
||||
"ram_gb": 55
|
||||
},
|
||||
"uptime_percentage": 99.0,
|
||||
"farming_start_date": "2025-08-10T03:27:08.942256607Z",
|
||||
"last_updated": "2025-09-09T03:27:08.942268123Z",
|
||||
"utilization_7_day_avg": 65.0,
|
||||
"slice_formats_supported": [
|
||||
"1x1",
|
||||
"2x2",
|
||||
"4x4"
|
||||
],
|
||||
"rental_options": null,
|
||||
"total_base_slices": 0,
|
||||
"allocated_base_slices": 0,
|
||||
"earnings_today_usd": 0.0,
|
||||
"grid_node_id": "2007",
|
||||
"available_combinations": [],
|
||||
"slice_allocations": [],
|
||||
"slice_last_calculated": null,
|
||||
"marketplace_sla": {
|
||||
"id": "sla-repair-8fa25e61-493a-46cc-b942-ce8cef923ae6",
|
||||
"name": "Repaired Node SLA",
|
||||
"uptime_guarantee": 99.8,
|
||||
"response_time_hours": 24,
|
||||
"resolution_time_hours": 48,
|
||||
"penalty_rate": 0.01,
|
||||
"uptime_guarantee_percentage": 99.8,
|
||||
"base_slice_price": 0.5,
|
||||
"bandwidth_guarantee_mbps": 100.0,
|
||||
"last_updated": "2025-09-09T03:27:12.489433171Z"
|
||||
},
|
||||
"slice_pricing": {
|
||||
"base_price_per_hour": 1.0,
|
||||
"currency": "USD",
|
||||
"pricing_multiplier": 1.0
|
||||
},
|
||||
"grid_data": {
|
||||
"capacity": {
|
||||
"bandwidth_mbps": 1000,
|
||||
"cpu_cores": 24,
|
||||
"hdd_storage_gb": 8383,
|
||||
"memory_gb": 188,
|
||||
"ram_gb": 188,
|
||||
"ssd_storage_gb": 476,
|
||||
"storage_gb": 8859
|
||||
},
|
||||
"certification_type": "Diy",
|
||||
"city": "Unknown",
|
||||
"country": "Belgium",
|
||||
"farm_id": 1,
|
||||
"farm_name": "Freefarm",
|
||||
"farming_policy_id": 1,
|
||||
"grid_node_id": 2007,
|
||||
"last_updated": "2025-09-09T03:27:08.942100965Z",
|
||||
"location": "Unknown, Belgium",
|
||||
"node_id": 2007,
|
||||
"public_ips": [
|
||||
"192.168.1.100"
|
||||
],
|
||||
"status": "Online",
|
||||
"total_resources": {
|
||||
"bandwidth_mbps": 1000,
|
||||
"cpu_cores": 24,
|
||||
"hdd_storage_gb": 8383,
|
||||
"memory_gb": 188,
|
||||
"ram_gb": 188,
|
||||
"ssd_storage_gb": 476,
|
||||
"storage_gb": 8859
|
||||
},
|
||||
"uptime": 99.5,
|
||||
"used_resources": {
|
||||
"bandwidth_mbps": 0,
|
||||
"cpu_cores": 9,
|
||||
"hdd_storage_gb": 0,
|
||||
"memory_gb": 55,
|
||||
"ram_gb": 55,
|
||||
"ssd_storage_gb": 343,
|
||||
"storage_gb": 343
|
||||
}
|
||||
},
|
||||
"slice_formats": null,
|
||||
"name": "Grid Node 2007",
|
||||
"region": "Belgium",
|
||||
"node_type": "MyceliumNode",
|
||||
"staking_options": null,
|
||||
"availability_status": "Available",
|
||||
"node_group_id": null,
|
||||
"group_assignment_date": null,
|
||||
"group_slice_format": null,
|
||||
"group_slice_price": null,
|
||||
"last_seen": "2025-09-09T03:27:08.942273898Z",
|
||||
"health_score": 100.0
|
||||
}
|
||||
],
|
||||
"resource_provider_earnings": [],
|
||||
"resource_provider_settings": null,
|
||||
"slice_products": [],
|
||||
@@ -29,7 +143,91 @@
|
||||
"active_product_rentals": [],
|
||||
"resource_provider_rental_earnings": [],
|
||||
"node_rentals": [],
|
||||
"node_groups": [],
|
||||
"node_groups": [
|
||||
{
|
||||
"id": "compute",
|
||||
"name": "Compute",
|
||||
"description": "General compute workloads",
|
||||
"node_ids": [],
|
||||
"group_type": {
|
||||
"Default": "compute"
|
||||
},
|
||||
"updated_at": "2025-09-09T03:26:57.639840151Z",
|
||||
"created_at": "2025-09-09T03:26:57.639836172Z",
|
||||
"group_config": {
|
||||
"group_name": "Compute Nodes",
|
||||
"max_nodes": 100,
|
||||
"allocation_strategy": "balanced",
|
||||
"auto_scaling": true,
|
||||
"preferred_slice_formats": [
|
||||
"1x1",
|
||||
"2x2"
|
||||
],
|
||||
"default_pricing": 50.0,
|
||||
"resource_optimization": {
|
||||
"cpu_optimization": 0.5,
|
||||
"memory_optimization": 0.5,
|
||||
"storage_optimization": 0.5,
|
||||
"network_optimization": 0.5
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "storage",
|
||||
"name": "Storage",
|
||||
"description": "Storage and data workloads",
|
||||
"node_ids": [],
|
||||
"group_type": {
|
||||
"Default": "storage"
|
||||
},
|
||||
"updated_at": "2025-09-09T03:26:57.639846621Z",
|
||||
"created_at": "2025-09-09T03:26:57.639846284Z",
|
||||
"group_config": {
|
||||
"group_name": "Storage Nodes",
|
||||
"max_nodes": 50,
|
||||
"allocation_strategy": "storage_optimized",
|
||||
"auto_scaling": false,
|
||||
"preferred_slice_formats": [
|
||||
"1x1"
|
||||
],
|
||||
"default_pricing": 30.0,
|
||||
"resource_optimization": {
|
||||
"cpu_optimization": 0.3,
|
||||
"memory_optimization": 0.3,
|
||||
"storage_optimization": 0.8,
|
||||
"network_optimization": 0.6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ai-gpu",
|
||||
"name": "AI/GPU",
|
||||
"description": "AI and GPU-intensive workloads",
|
||||
"node_ids": [],
|
||||
"group_type": {
|
||||
"Default": "ai-gpu"
|
||||
},
|
||||
"updated_at": "2025-09-09T03:26:57.639850994Z",
|
||||
"created_at": "2025-09-09T03:26:57.639850646Z",
|
||||
"group_config": {
|
||||
"group_name": "AI/GPU Nodes",
|
||||
"max_nodes": 20,
|
||||
"allocation_strategy": "gpu_optimized",
|
||||
"auto_scaling": true,
|
||||
"preferred_slice_formats": [
|
||||
"4x4",
|
||||
"8x8"
|
||||
],
|
||||
"default_pricing": 200.0,
|
||||
"resource_optimization": {
|
||||
"cpu_optimization": 0.8,
|
||||
"memory_optimization": 0.7,
|
||||
"storage_optimization": 0.4,
|
||||
"network_optimization": 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"slice_rentals": [],
|
||||
"slice_assignments": [],
|
||||
"display_currency": "MC",
|
||||
|
Reference in New Issue
Block a user