Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion linkup-cli/src/commands/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{
collections::BTreeMap,
env,
io::{Write, stdout},
thread,
};

use anyhow::Result;
Expand All @@ -10,13 +11,17 @@ use colored::Colorize;
use linkup::Session;
use linkup_clients::LocalServerClient;
use serde::Serialize;
use url::Url;

use crate::{
services::{cloudflared, local_server},
state::State,
};

use super::local_dns;
use super::{
local_dns,
status::{ServerStatus, server_status},
};

#[derive(clap::Args)]
pub struct Args {
Expand Down Expand Up @@ -56,6 +61,18 @@ struct States {
items: BTreeMap<String, State>,
}

#[derive(Serialize)]
struct LinkupService {
url: Url,
status: ServerStatus,
}

#[derive(Serialize)]
struct LinkupServices {
remote_server: Option<LinkupService>,
tunnel: Option<LinkupService>,
}

#[derive(Serialize)]
enum LocalServer {
Stopped,
Expand All @@ -78,6 +95,7 @@ struct Health {
cli: Cli,
system: System,
states: States,
linkup_services: LinkupServices,
local_server: LocalServer,
cloudflared: Cloudflared,
}
Expand Down Expand Up @@ -152,6 +170,65 @@ impl States {
}
}

impl LinkupService {
fn load(url: Url) -> Self {
let status = server_status(url.as_str(), None, None);

Self { url, status }
}

fn write(&self, writer: &mut impl Write, name: &str, offset: usize) -> Result<()> {
writeln!(
writer,
"{:>offset$}{}: {} ({})",
"",
name,
self.status.colored().to_uppercase(),
self.url
)?;

Ok(())
}
}

impl LinkupServices {
fn load() -> Result<Self> {
let state = State::load().ok();

let remote_server_url = state
.as_ref()
.map(|state| state.linkup.worker_url.join("/linkup/check"))
.transpose()?;
let remote_server =
remote_server_url.map(|url| thread::spawn(move || LinkupService::load(url)));

let tunnel_url = state
.as_ref()
.filter(|state| state.should_use_tunnel())
.map(|state| state.get_tunnel_url().join("/linkup/check"))
.transpose()?;
let tunnel = tunnel_url.map(|url| thread::spawn(move || LinkupService::load(url)));

Ok(Self {
remote_server: remote_server
.map(|handle| handle.join().expect("Linkup service check panicked")),
tunnel: tunnel.map(|handle| handle.join().expect("Linkup service check panicked")),
})
}

fn write(&self, writer: &mut impl Write, offset: usize) -> Result<()> {
if let Some(remote_server) = &self.remote_server {
remote_server.write(writer, "Remote Server", offset)?;
}

if let Some(tunnel) = &self.tunnel {
tunnel.write(writer, "Tunnel", offset)?;
}

Ok(())
}
}

impl LocalServer {
async fn load() -> Result<Self> {
let local_server_client = LocalServerClient::new(&local_server::url());
Expand Down Expand Up @@ -275,6 +352,7 @@ impl Health {
cli: Cli::load()?,
system: System::load()?,
states: States::load()?,
linkup_services: LinkupServices::load()?,
local_server: LocalServer::load().await?,
cloudflared: Cloudflared::load()?,
})
Expand All @@ -290,6 +368,9 @@ impl Health {
write!(writer, "{}", "States:".bold())?;
self.states.write(writer, 2)?;

writeln!(writer, "{}", "Linkup Services:".bold())?;
self.linkup_services.write(writer, 2)?;

write!(writer, "{}", "Local Server:".bold())?;
self.local_server.write(writer, 2)?;

Expand Down
2 changes: 1 addition & 1 deletion linkup-cli/src/commands/status/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub enum ServerStatus {
}

impl ServerStatus {
pub(super) fn colored(&self) -> ColoredString {
pub fn colored(&self) -> ColoredString {
match self {
ServerStatus::Ok => "ok".blue(),
ServerStatus::Error => "error".yellow(),
Expand Down
42 changes: 2 additions & 40 deletions linkup-cli/src/commands/status/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,10 @@ pub async fn status(args: &Args) -> anyhow::Result<()> {

let config = load_config(&config_path).ok();

let user_services = build_user_services(session_detail.as_ref(), config.as_ref());
let internal_services = build_internal_services(&state);
let all_services: Vec<ServiceToCheck> =
user_services.into_iter().chain(internal_services).collect();
let services = build_user_services(session_detail.as_ref(), config.as_ref());

let (mut service_statuses, status_receiver) =
prepare_service_statuses(&target_session, all_services);
prepare_service_statuses(&target_session, services);

service_statuses.sort_by(|a, b| {
a.priority
Expand Down Expand Up @@ -261,41 +258,6 @@ fn build_user_services(
.collect()
}

fn build_internal_services(state: &State) -> Vec<ServiceToCheck> {
let local_url = services::local_server::url();

vec![
ServiceToCheck {
name: "linkup_local_server".to_string(),
url: local_url.join("/linkup/check").unwrap(),
component_kind: "local".to_string(),
health: None,
priority: 1,
},
ServiceToCheck {
name: "linkup_remote_server".to_string(),
url: state
.linkup
.worker_url
.join("/linkup/check")
.unwrap_or_else(|_| state.linkup.worker_url.clone()),
component_kind: "remote".to_string(),
health: None,
priority: 1,
},
ServiceToCheck {
name: "tunnel".to_string(),
url: state
.get_tunnel_url()
.join("/linkup/check")
.unwrap_or_else(|_| state.get_tunnel_url()),
component_kind: "remote".to_string(),
health: None,
priority: 1,
},
]
}

fn prepare_service_statuses(
session_name: &str,
services: Vec<ServiceToCheck>,
Expand Down