pub mod cli_handler; pub mod config; pub mod grpc_brain; pub mod grpc_dtpm; pub mod packaging; pub mod utils; use crate::snp; use crate::{constants::HRATLS_APP_PORT, utils::block_on}; use detee_shared::{ app_proto::{ AppContract as AppContractPB, AppNodeFilters, AppNodeListResp, AppResource, NewAppRes, }, sgx::types::brain::Resource, }; use grpc_brain::get_one_app_node; use serde::{Deserialize, Serialize}; use tabled::Tabled; #[derive(Tabled, Debug, Serialize, Deserialize)] pub struct AppContract { #[tabled(rename = "Location")] pub location: String, #[tabled(rename = "UUID")] pub uuid: String, pub name: String, #[tabled(rename = "Cores")] pub vcpu: u32, #[tabled(rename = "Mem (MB)")] pub memory_mb: u32, #[tabled(rename = "Disk (MB)")] pub disk_mb: u32, #[tabled(rename = "LP/h")] pub cost_h: String, #[tabled(rename = "time left", display_with = "display_mins")] pub time_left: u64, #[tabled(rename = "Node IP")] pub node_ip: String, #[tabled(rename = "Exposed ports", display_with = "display_ports")] pub exposed_host_ports: Vec<(u32, u32)>, } fn display_mins(minutes: &u64) -> String { let mins = minutes % 60; let hours = minutes / 60; format!("{hours}h {mins}m") } fn display_ports(ports: &[(u32, u32)]) -> String { ports.iter().map(|port| format!("({}:{})", port.0, port.1,)).collect::>().join(", ") } impl crate::HumanOutput for Vec { fn human_cli_print(&self) { let style = tabled::settings::Style::rounded(); let mut table = tabled::Table::new(self); table.with(style); println!("{table}"); } } impl From for AppContract { fn from(brain_app_contract: AppContractPB) -> Self { let node_pubkey = brain_app_contract.node_pubkey.clone(); let location = match block_on(get_one_app_node(AppNodeFilters { node_pubkey: node_pubkey.clone(), ..Default::default() })) { Ok(node) => format!("{}, {} ({})", node.city, node.region, node.country), Err(e) => { log::warn!("Could not get information about node {node_pubkey} fram brain: {e:?}"); String::new() } }; let AppResource { vcpu, memory_mb, disk_mb, .. } = brain_app_contract.resource.unwrap_or_default(); let exposed_host_ports = brain_app_contract .mapped_ports .iter() .map(|port| (port.host_port, port.app_port)) .collect::>(); Self { location, uuid: brain_app_contract.uuid, name: brain_app_contract.app_name, vcpu, memory_mb, disk_mb, cost_h: format!( "{:.4}", (brain_app_contract.nano_per_minute * 60) as f64 / 1_000_000_000.0 ), time_left: brain_app_contract.locked_nano / brain_app_contract.nano_per_minute, node_ip: brain_app_contract.public_ipv4, exposed_host_ports, } } } #[derive(Debug, Serialize, Deserialize)] pub struct AppDeployResponse { pub status: String, pub uuid: String, pub name: String, pub node_ip: String, pub hratls_port: u32, pub error: String, } impl crate::HumanOutput for AppDeployResponse { fn human_cli_print(&self) { println!("App deployd with UUID: {}", self.uuid); } } impl From for AppDeployResponse { fn from(value: NewAppRes) -> Self { Self { status: value.status, uuid: value.uuid, name: "".to_string(), node_ip: value.ip_address, hratls_port: value .mapped_ports .iter() .find(|port| port.app_port == HRATLS_APP_PORT) .map(|port| port.host_port) .unwrap_or(HRATLS_APP_PORT), error: value.error, } } } #[derive(Debug, Serialize, Deserialize)] pub struct AppDeleteResponse { pub uuid: String, pub message: String, } impl crate::HumanOutput for AppDeleteResponse { fn human_cli_print(&self) { println!("App deleted successfully: UUID: {}", self.uuid); } } pub async fn get_app_node( resource: Resource, // TODO: change this to utils or something location: snp::deploy::Location, ) -> Result { let app_node_filter = AppNodeFilters { vcpus: resource.vcpu, memory_mb: resource.memory_mb, storage_mb: resource.disk_mb, country: location.country.clone().unwrap_or_default(), region: location.region.clone().unwrap_or_default(), city: location.city.clone().unwrap_or_default(), ip: location.node_ip.clone().unwrap_or_default(), node_pubkey: String::new(), }; get_one_app_node(app_node_filter).await } pub fn inspect_node(ip: String) -> Result { let req = AppNodeFilters { ip, ..Default::default() }; block_on(get_one_app_node(req)) } #[derive(Tabled, Debug, Serialize, Deserialize)] pub struct TabledAppNode { #[tabled(rename = "Operator")] pub operator: String, #[tabled(rename = "City, Region, Country")] pub location: String, #[tabled(rename = "IP")] pub public_ip: String, #[tabled(rename = "Price per unit")] pub price: String, #[tabled(rename = "Reports")] pub reports: usize, } impl From for TabledAppNode { fn from(brain_node: AppNodeListResp) -> Self { Self { operator: brain_node.operator, location: brain_node.city + ", " + &brain_node.region + ", " + &brain_node.country, public_ip: brain_node.ip, price: format!("{} nanoLP/min", brain_node.price), reports: brain_node.reports.len(), } } } impl super::HumanOutput for Vec { fn human_cli_print(&self) { let nodes: Vec = self.iter().map(|n| n.clone().into()).collect(); let style = tabled::settings::Style::rounded(); let mut table = tabled::Table::new(nodes); table.with(style); println!("{table}"); } } pub fn print_nodes() -> Result, grpc_brain::Error> { log::debug!("This will support flags in the future, but we have only one node atm."); let req = AppNodeFilters { ..Default::default() }; block_on(grpc_brain::get_app_node_list(req)) }