#include "command_registry.hpp" #include "config.hpp" #include "utils/utils.hpp" #include "utils/directories.hpp" #include "utils/execute.hpp" #include "shared_commands.hpp" #include "servers.hpp" #include "tableprint.hpp" #include "transwarp.hpp" #include "servers.hpp" #include "services.hpp" #include #include #include #include #include #include #include namespace dropshell { int list_handler(const CommandContext& ctx); void show_server_details(const std::string& server_name); void list_servers(); static std::vector list_name_list={"list","ls","info","-l"}; // Static registration struct ListCommandRegister { ListCommandRegister() { CommandRegistry::instance().register_command({ list_name_list, list_handler, shared_commands::std_autocomplete, false, // hidden true, // requires_config true, // requires_install 0, // min_args (after command) 2, // max_args (after command) "list [SERVER] [SERVICE]", "List server or service information and status", // heredoc R"( List details for servers and services controller by dropshell. list list all servers. list server list all services for the given server. list server service list the given service details on the given server. )" }); } } list_command_register; // ------------------------------------------------------------------------------------------------ // list command handler // ------------------------------------------------------------------------------------------------ int list_handler(const CommandContext& ctx) { if (ctx.args.size() == 0) { list_servers(); return 0; } if (ctx.args.size() == 1) { show_server_details(ctx.args[0]); return 0; } debug << "List handler called with " << ctx.args.size() << " args\n"; return 0; } // https://github.com/bloomen/transwarp?tab=readme-ov-file#range-functions void list_servers() { auto servers = get_configured_servers(); if (servers.empty()) { error << "No servers found" << std::endl; info << "Please run 'dropshell edit' to set up dropshell." << std::endl; info << "Then run 'dropshell create-server' to create a server." << std::endl; return; } tableprint tp("All DropShell Servers"); tp.add_row({"Name", "Address", "User", "Health", "Ports"}); typedef std::map tServiceStatusMap; std::vector service_status_maps; typedef struct {dropshell::ServerConfig server; dropshell::UserConfig user;} server_user_pair; std::vector server_user_pairs; for (const auto& server : servers) for (const auto& user : server.get_users()) server_user_pairs.push_back({server, user}); // mutex for the tableprint std::mutex tp_mutex; // mutex for server status maps std::mutex status_mutex; info << "Checking "< server_online_status; // Pre-compute disabled status to avoid threading issues with config access std::set disabled_servers; for (const auto& disabled : gConfig().get_disabled_servers()) { disabled_servers.insert(disabled); } transwarp::parallel exec{server_user_pairs.size()}; auto task = transwarp::for_each(exec, server_user_pairs.begin(), server_user_pairs.end(), [&](const server_user_pair& sup) { ServerConfig server_env(sup.server.get_server_name()); if (!server_env.is_valid()) { error << "Invalid server environment for " << sup.server.get_server_name() << std::endl; return; } std::string serviceticks = ""; std::string ports_used_str = ""; std::set ports_used; // Check if server is disabled (from pre-computed set, thread-safe read) bool is_disabled = (disabled_servers.find(sup.server.get_server_name()) != disabled_servers.end()); // Check if server is online (with caching to avoid redundant checks for multiple users) bool is_online = false; if (!is_disabled) { // First check if we already know the status bool already_checked = false; { std::lock_guard lock(status_mutex); auto it = server_online_status.find(sup.server.get_server_name()); if (it != server_online_status.end()) { is_online = it->second; already_checked = true; } } // If not already checked, do the SSH check outside the lock if (!already_checked) { // Quick connectivity check sSSHInfo sshinfo = server_env.get_SSH_INFO(sup.user.user); std::string test_cmd = "true"; bool check_result = execute_ssh_command(sshinfo, sCommand("", test_cmd, {}), cMode::Silent); // Store the result with lock { std::lock_guard lock(status_mutex); // Check again in case another thread checked it while we were waiting auto it = server_online_status.find(sup.server.get_server_name()); if (it == server_online_status.end()) { server_online_status[sup.server.get_server_name()] = check_result; is_online = check_result; } else { is_online = it->second; } } } } // Only check service status if server is online and not disabled if (is_online && !is_disabled) { std::map status = shared_commands::get_all_services_status(sup.server.get_server_name(),sup.user.user); for (const auto& [service_name, service_status] : status) { ports_used.insert(service_status.ports.begin(), service_status.ports.end()); serviceticks += shared_commands::HealthStatus2String(service_status.health) + " "; } for (const auto& port : ports_used) ports_used_str += std::to_string(port) + " "; } // Add red cross to address if server is offline std::string address_display = sup.server.get_SSH_HOST(); if (!is_online && !is_disabled) { address_display += " :cross:"; } // Add disabled marker to server name (invisible, used for row coloring) std::string server_name_display = sup.server.get_server_name(); if (is_disabled) { server_name_display += ":disabled:"; } // critical section { std::lock_guard lock(tp_mutex); tp.add_row({server_name_display, address_display, sup.user.user, serviceticks, ports_used_str}); ++checked; } // print out a tick character for each server checked. info << checked << " ✓ " << std::flush; }); task->wait(); info << std::endl << std::endl; tp.sort({0,2}); tp.print(); } void show_server_details(const std::string& server_name) { ServerConfig env(server_name); if (!env.is_valid()) { error << "Invalid server environment file: " << server_name << std::endl; return; } //--------------------- // Check if server is reachable via SSH ASSERT(env.get_users().size() > 0, "No users found for server " + server_name); sSSHInfo sshinfo = env.get_SSH_INFO(env.get_users()[0].user); ASSERT(sshinfo.valid(), "Invalid SSH info for server " + server_name); info << std::endl << "Server Status:" << std::endl; info << std::string(40, '-') << std::endl; // Try to connect to the server (using our improved timeout) bool is_online = execute_ssh_command(sshinfo, sCommand("", "true", {}), cMode::Silent); if (is_online) { info << "Status: Online" << std::endl; } else { warning << "Status: Offline" << std::endl; } info << std::endl; //--------------------- { std::cout << std::endl; tableprint tp("Server Configuration: " + server_name, true); tp.add_row({"Key", "Value"}); for (const auto& [key, value] : env.get_variables()) { if (key == "SSH_USERS") { int i=1; for (const auto& user : env.get_users()) { tp.add_row({"USERS -> USER[" + std::to_string(i) + "]", user.user}); tp.add_row({"USERS -> DIR[" + std::to_string(i) + "]", user.dir}); i++; } } else tp.add_row({key, value}); } tp.print(); } //--------------------- // list services, and run healthcheck on each { tableprint tp("Services: " + server_name, false); tp.add_row({"Status", "Service", "Template","Ports"}); if (is_online) { std::map status = shared_commands::get_all_services_status(server_name); std::set ports_used; std::string serviceticks = ""; for (const auto& [service_name, service_status] : status) { std::string healthy = shared_commands::HealthStatus2String(service_status.health); std::string ports_str = ""; for (const auto& port : service_status.ports) ports_str += std::to_string(port) + " "; std::string template_name = get_service_info(server_name,service_name).template_name; if (template_name.empty()) template_name = "Unknown"; tp.add_row({healthy, service_name, template_name, ports_str}); } // end of for (const auto& service : services) } else { // Server is offline, just list services without checking their status std::vector services = get_server_services_info(server_name); for (const auto& service : services) { tp.add_row({":cross:", service.service_name, service.template_name, "-"}); } } tp.print(); } // end of list services } // end of show_server_details } // namespace dropshell