diff --git a/app/src/api/cmd.ts b/app/src/api/cmd.ts index 2b8076f4..777f33f7 100644 --- a/app/src/api/cmd.ts +++ b/app/src/api/cmd.ts @@ -12,6 +12,7 @@ const mode = import.meta.env.MODE; if (mode === "super") { root = "https://app.superadmin.sphinx.chat/api"; + // root = "http://localhost:8005/api"; } type CmdType = @@ -78,7 +79,12 @@ export type Cmd = | "GetApiToken" | "AddNewSwarm" | "UpdateSwarm" - | "DeleteSwarm"; + | "DeleteSwarm" + | "GetChildSwarmConfig" + | "GetChildSwarmContainers" + | "StopChildSwarmContainers" + | "StartChildSwarmContainers" + | "UpdateChildSwarmContainers"; interface CmdData { cmd: Cmd; diff --git a/app/src/api/swarm.ts b/app/src/api/swarm.ts index 8d8c1596..f5806c82 100644 --- a/app/src/api/swarm.ts +++ b/app/src/api/swarm.ts @@ -251,3 +251,41 @@ export async function update_user({ }) { return await swarmCmd("UpdateUser", { pubkey, name, role, id }); } + +export async function get_child_swarm_config({ host }: { host: string }) { + return await swarmCmd("GetChildSwarmConfig", { host }); +} + +export async function get_child_swarm_containers({ host }: { host: string }) { + return await swarmCmd("GetChildSwarmContainers", { host }); +} + +export async function stop_child_swarm_containers({ + nodes, + host, +}: { + nodes: string[]; + host: string; +}) { + return await swarmCmd("StopChildSwarmContainers", { nodes, host }); +} + +export async function start_child_swarm_containers({ + nodes, + host, +}: { + nodes: string[]; + host: string; +}) { + return await swarmCmd("StartChildSwarmContainers", { nodes, host }); +} + +export async function update_child_swarm_containers({ + nodes, + host, +}: { + nodes: string[]; + host: string; +}) { + return await swarmCmd("UpdateChildSwarmContainers", { nodes, host }); +} diff --git a/src/bin/super/cmd.rs b/src/bin/super/cmd.rs index a40ac602..6248a37e 100644 --- a/src/bin/super/cmd.rs +++ b/src/bin/super/cmd.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use serde_json::Value; #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type", content = "data")] @@ -67,6 +68,16 @@ pub enum SwarmCmd { UpdateSwarm(UpdateSwarmInfo), DeleteSwarm(DeleteSwarmInfo), SetChildSwarm(ChildSwarm), + GetChildSwarmConfig(ChildSwarmIdentifier), + GetChildSwarmContainers(ChildSwarmIdentifier), + StopChildSwarmContainers(AccessNodesInfo), + StartChildSwarmContainers(AccessNodesInfo), + UpdateChildSwarmContainers(AccessNodesInfo), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ChildSwarmIdentifier { + pub host: String, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -74,3 +85,21 @@ pub struct AddSwarmResponse { pub success: bool, pub message: String, } + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SuperSwarmResponse { + pub success: bool, + pub message: String, + pub data: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct LoginResponse { + pub token: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AccessNodesInfo { + pub host: String, + pub nodes: Vec, +} diff --git a/src/bin/super/mod.rs b/src/bin/super/mod.rs index f15c6180..08a3c2fa 100644 --- a/src/bin/super/mod.rs +++ b/src/bin/super/mod.rs @@ -5,12 +5,15 @@ mod routes; mod state; mod util; -use cmd::AddSwarmResponse; +use cmd::{AddSwarmResponse, SuperSwarmResponse}; use cmd::{Cmd, SwarmCmd}; use sphinx_swarm::utils::getenv; use state::RemoteStack; use state::Super; -use util::add_new_swarm_details; +use util::{ + accessing_child_container_controller, add_new_swarm_details, get_child_swarm_config, + get_child_swarm_containers, +}; use crate::checker::swarm_checker; use anyhow::{anyhow, Context, Result}; @@ -72,24 +75,22 @@ pub async fn put_config_file(project: &str, rs: &Super) { fn access(cmd: &Cmd, state: &Super, user_id: &Option) -> bool { // login needs no auth - if let Cmd::Swarm(c) = cmd { - if let SwarmCmd::Login(_) = c { - return true; - } - - if let SwarmCmd::SetChildSwarm(info) = c { - //get x-super-token - let token = getenv("SUPER_TOKEN").unwrap_or("".to_string()); - if token.is_empty() { - return false; - } - //verify token - if token != info.token { - return false; + match cmd { + Cmd::Swarm(c) => match c { + SwarmCmd::Login(_) => return true, + SwarmCmd::SetChildSwarm(info) => { + //get x-super-token + let token = getenv("SUPER_TOKEN").unwrap_or("".to_string()); + if token.is_empty() { + return false; + } + if token != info.token { + return false; + } + return true; } - - return true; - } + _ => {} + }, } // user id required if not SwarmCmd::Login if user_id.is_none() { @@ -228,6 +229,69 @@ pub async fn super_handle( Some(serde_json::to_string(&hm)?) } + SwarmCmd::GetChildSwarmConfig(info) => { + let res: SuperSwarmResponse; + //find node + match state.find_swarm_by_host(&info.host) { + Some(swarm) => match get_child_swarm_config(&swarm).await { + Ok(result) => res = result, + Err(err) => { + res = SuperSwarmResponse { + success: false, + message: err.to_string(), + data: None, + } + } + }, + None => { + res = SuperSwarmResponse { + success: false, + message: "Swarm does not exist".to_string(), + data: None, + } + } + } + Some(serde_json::to_string(&res)?) + } + SwarmCmd::GetChildSwarmContainers(info) => { + let res: SuperSwarmResponse; + match state.find_swarm_by_host(&info.host) { + Some(swarm) => match get_child_swarm_containers(&swarm).await { + Ok(result) => res = result, + Err(err) => { + res = SuperSwarmResponse { + success: false, + message: err.to_string(), + data: None, + } + } + }, + None => { + res = SuperSwarmResponse { + success: false, + message: "Swarm does not exist".to_string(), + data: None, + } + } + } + Some(serde_json::to_string(&res)?) + } + SwarmCmd::StopChildSwarmContainers(info) => { + let res = accessing_child_container_controller(&state, info, "StopContainer").await; + + Some(serde_json::to_string(&res)?) + } + SwarmCmd::StartChildSwarmContainers(info) => { + let res = + accessing_child_container_controller(&state, info, "StartContainer").await; + + Some(serde_json::to_string(&res)?) + } + SwarmCmd::UpdateChildSwarmContainers(info) => { + let res = accessing_child_container_controller(&state, info, "UpdateNode").await; + + Some(serde_json::to_string(&res)?) + } }, }; diff --git a/src/bin/super/routes.rs b/src/bin/super/routes.rs index 6c9e3c75..f80ffbb3 100644 --- a/src/bin/super/routes.rs +++ b/src/bin/super/routes.rs @@ -47,7 +47,7 @@ pub async fn launch_rocket( } #[get("/cmd?&")] -pub async fn cmd( +async fn cmd( sender: &State>, tag: &str, txt: &str, @@ -60,7 +60,7 @@ pub async fn cmd( } #[rocket::post("/super/add_new_swarm", data = "")] -pub async fn add_new_swarm( +async fn add_new_swarm( sender: &State>, body: Json, verify_super_token: VerifySuperToken, diff --git a/src/bin/super/superapp/src/App.svelte b/src/bin/super/superapp/src/App.svelte index 7dbc1d9a..d2f8c16e 100644 --- a/src/bin/super/superapp/src/App.svelte +++ b/src/bin/super/superapp/src/App.svelte @@ -5,12 +5,17 @@ import User from "carbon-icons-svelte/lib/User.svelte"; import { OverflowMenu, OverflowMenuItem } from "carbon-components-svelte"; import ChangePassword from "./ChangePassword.svelte"; + import ViewNodes from "./ViewNodes.svelte"; let page = "main"; async function backToMain() { page = "main"; } + async function viewNode() { + page = "view_nodes"; + } + function handleChangePassword() { page = "change_password"; } @@ -38,8 +43,10 @@
{#if page === "change_password"} + {:else if page === "view_nodes"} + {:else} - + {/if}
{/if} diff --git a/src/bin/super/superapp/src/Remotes.svelte b/src/bin/super/superapp/src/Remotes.svelte index 743dde10..abbe91d1 100644 --- a/src/bin/super/superapp/src/Remotes.svelte +++ b/src/bin/super/superapp/src/Remotes.svelte @@ -18,6 +18,7 @@ import { onMount } from "svelte"; import type { Remote } from "./types/types"; import { splitHost } from "./utils/index"; + import { selectedNode } from "./store"; let open_create_edit = false; let open_delete = false; @@ -35,6 +36,8 @@ let selectedRowIds = []; + export let viewNode = () => {}; + async function getConfig() { const conf = await api.swarm.get_config(); if (conf && conf.stacks && conf.stacks.length) { @@ -229,6 +232,12 @@ function handleOnClose() { clear_swarm_data(); } + + function handleViewNodes(id: string) { + console.log(id); + selectedNode.set(id); + viewNode(); + }
@@ -256,6 +265,7 @@ { key: "ec2", value: "Instance" }, { key: "tribes", value: "Tribes" }, { key: "health", value: "Health" }, + { key: "nodes", value: "Nodes" }, { key: "edit", value: "Edit" }, { key: "delete", value: "Delete" }, ]} @@ -285,6 +295,10 @@ + {:else if cell.key === "nodes"} + {:else if cell.key === "delete"} + {:else if cell.value === "exited"} + + {:else} + + {/if} + {:else if cell.key === "update"} + + {:else} + {cell.value} + {/if} + + + {/if} +
+ + diff --git a/src/bin/super/superapp/src/store.ts b/src/bin/super/superapp/src/store.ts index 5e027f73..ed8a078b 100644 --- a/src/bin/super/superapp/src/store.ts +++ b/src/bin/super/superapp/src/store.ts @@ -8,6 +8,8 @@ export const remotes = writable([]); export const activeUser = writable(); +export const selectedNode = writable(); + export const tribes = writable<{ [k: string]: Tribe[] }>({}); export const saveUserToStore = async (user: string = "") => { diff --git a/src/bin/super/util.rs b/src/bin/super/util.rs index 7e0da87e..02b07539 100644 --- a/src/bin/super/util.rs +++ b/src/bin/super/util.rs @@ -1,4 +1,13 @@ -use crate::cmd::AddSwarmResponse; +use std::collections::HashMap; + +use anyhow::{anyhow, Error}; +use reqwest::Response; +use serde_json::Value; +use sphinx_swarm::cmd::{send_cmd_request, Cmd, LoginInfo, SwarmCmd, UpdateNode}; +use sphinx_swarm::config::Stack; +use sphinx_swarm::utils::make_reqwest_client; + +use crate::cmd::{AccessNodesInfo, AddSwarmResponse, LoginResponse, SuperSwarmResponse}; use crate::state::{RemoteStack, Super}; pub fn add_new_swarm_details( @@ -23,3 +32,193 @@ pub fn add_new_swarm_details( } } } + +pub async fn login_to_child_swarm(swarm_details: &RemoteStack) -> Result { + let client = make_reqwest_client(); + + let base_route = get_child_base_route(&swarm_details.host); + let route = format!("{}/login", base_route); + + if let None = &swarm_details.user { + return Err(anyhow!("Swarm Username is missing")); + } + + if let None = &swarm_details.pass { + return Err(anyhow!("Swarm Password is missing")); + } + + let body = LoginInfo { + username: swarm_details.user.clone().unwrap(), + password: swarm_details.pass.clone().unwrap(), + }; + + return match client.post(route.as_str()).json(&body).send().await { + Ok(res) => { + if res.status().clone() != 200 { + return Err(anyhow!( + "{} Status code from login into child swarm", + res.status().clone() + )); + } + let login_json: LoginResponse = res.json().await?; + + Ok(login_json.token) + } + Err(err) => { + log::error!("Error trying to login: {:?}", err); + Err(anyhow!("error trying to login")) + } + }; +} + +pub async fn get_child_swarm_config( + swarm_details: &RemoteStack, +) -> Result { + let token = login_to_child_swarm(swarm_details).await?; + // let res = handle_get_child_swarm_config(&swarm_details.host, &token).await?; + let cmd = Cmd::Swarm(SwarmCmd::GetConfig); + let res = swarm_cmd(cmd, &swarm_details.host, &token).await?; + + if res.status().clone() != 200 { + return Err(anyhow!(format!( + "{} status code gotten from get child swarm config", + res.status() + ))); + }; + + let stack: Stack = res.json().await?; + + let nodes = serde_json::to_value(stack.nodes)?; + + Ok(SuperSwarmResponse { + success: true, + message: "child swarm config successfully retrieved".to_string(), + data: Some(nodes), + }) +} + +async fn swarm_cmd(cmd: Cmd, host: &str, token: &str) -> Result { + let url = get_child_base_route(host); + let cmd_res = send_cmd_request(cmd, "SWARM", &url, Some("x-jwt"), Some(&token)).await?; + Ok(cmd_res) +} + +pub fn get_child_base_route(host: &str) -> String { + return format!("https://app.{}/api", host); + + // return format!("http://{}/api", host); +} + +pub async fn get_child_swarm_containers( + swarm_details: &RemoteStack, +) -> Result { + let token = login_to_child_swarm(swarm_details).await?; + let cmd = Cmd::Swarm(SwarmCmd::ListContainers); + let res = swarm_cmd(cmd, &swarm_details.host, &token).await?; + + if res.status().clone() != 200 { + return Err(anyhow!(format!( + "{} status code gotten from get child swarm container", + res.status() + ))); + } + + let containers: Value = res.json().await?; + + Ok(SuperSwarmResponse { + success: true, + message: "child swarm containers successfully retrieved".to_string(), + data: Some(containers), + }) +} + +pub async fn access_child_swarm_containers( + swarm_details: &RemoteStack, + nodes: Vec, + cmd_text: &str, +) -> Result { + let token = login_to_child_swarm(swarm_details).await?; + let mut errors: HashMap = HashMap::new(); + + for node in nodes { + let cmd: Cmd; + if cmd_text == "UpdateNode" { + cmd = Cmd::Swarm(SwarmCmd::UpdateNode(UpdateNode { + id: node.clone(), + version: "latest".to_string(), + })); + } else if cmd_text == "StartContainer" { + cmd = Cmd::Swarm(SwarmCmd::StartContainer(node.clone())) + } else { + cmd = Cmd::Swarm(SwarmCmd::StopContainer(node.clone())) + } + + match swarm_cmd(cmd, &swarm_details.host, &token).await { + Ok(res) => { + if res.status().clone() != 200 { + errors.insert( + node.clone(), + format!( + "{} status error trying to {} {}", + res.status(), + &cmd_text, + node.clone() + ), + ); + } + } + Err(err) => { + log::error!("Error trying to {}: {}", &cmd_text, &err); + errors.insert(node, err.to_string()); + } + } + } + + if !errors.is_empty() { + match serde_json::to_value(errors) { + Ok(error_map) => { + return Ok(SuperSwarmResponse { + success: false, + message: format!("Error occured trying to {}", cmd_text), + data: Some(error_map), + }); + } + Err(err) => { + return Err(anyhow!("Error parsing error: {}", err.to_string())); + } + }; + } + Ok(SuperSwarmResponse { + success: true, + message: format!("{} executed successfully", cmd_text), + data: None, + }) +} + +pub async fn accessing_child_container_controller( + state: &Super, + info: AccessNodesInfo, + cmd: &str, +) -> SuperSwarmResponse { + let res: SuperSwarmResponse; + match state.find_swarm_by_host(&info.host) { + Some(swarm) => match access_child_swarm_containers(&swarm, info.nodes, cmd).await { + Ok(result) => res = result, + Err(err) => { + res = SuperSwarmResponse { + success: false, + message: err.to_string(), + data: None, + } + } + }, + None => { + res = SuperSwarmResponse { + success: false, + message: "Swarm does not exist".to_string(), + data: None, + } + } + } + res +} diff --git a/src/cmd.rs b/src/cmd.rs index 55cecd5d..11836efa 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -5,7 +5,6 @@ use std::collections::HashMap; use crate::{images::Image, utils::make_reqwest_client}; use anyhow::Context; use serde::{Deserialize, Serialize}; -use serde_json::Value; use sphinx_auther::secp256k1::PublicKey; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -218,19 +217,6 @@ pub struct GetInvoice { pub payment_hash: Option, } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SendCmdData { - pub cmd: String, - pub content: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct CmdRequest { - #[serde(rename = "type")] - cmd_type: String, - data: SendCmdData, -} - #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "cmd", content = "content")] pub enum BitcoindCmd { @@ -300,15 +286,14 @@ impl Cmd { } pub async fn send_cmd_request( - cmd_type: String, - data: SendCmdData, + cmd: Cmd, tag: &str, url: &str, header_name: Option<&str>, header_value: Option<&str>, ) -> Result { - let request = CmdRequest { cmd_type, data }; - let txt = serde_json::to_string(&request).context("could not stringify request")?; + // let request = CmdRequest { cmd_type, data }; + let txt = serde_json::to_string(&cmd).context("could not stringify request")?; let client = make_reqwest_client(); diff --git a/src/handler.rs b/src/handler.rs index cc6c0604..72e2dd83 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -46,6 +46,8 @@ fn access(cmd: &Cmd, state: &State, user_id: &Option) -> bool { SwarmCmd::StopContainer(_) => true, SwarmCmd::GetConfig => true, SwarmCmd::UpdateSwarm => true, + SwarmCmd::ListContainers => true, + SwarmCmd::UpdateNode(_) => true, _ => false, }, _ => false, diff --git a/src/secondbrain.rs b/src/secondbrain.rs index c56e69df..d621542e 100644 --- a/src/secondbrain.rs +++ b/src/secondbrain.rs @@ -16,7 +16,7 @@ pub fn only_second_brain(network: &str, host: Option, lightning_provider .map(|n| Node::Internal(n.to_owned())) .collect(), host, - users: vec![Default::default()], + users: vec![Default::default(), create_super_user()], jwt_key: secrets::random_word(16), ready: false, ip: env_no_empty("IP"),