297 lines
11 KiB
C++
297 lines
11 KiB
C++
#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 <unistd.h>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <filesystem>
|
|
#include <mutex>
|
|
#include <libassert/assert.hpp>
|
|
|
|
namespace dropshell {
|
|
|
|
int list_handler(const CommandContext& ctx);
|
|
void show_server_details(const std::string& server_name);
|
|
void list_servers();
|
|
|
|
static std::vector<std::string> 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<std::string, shared_commands::ServiceStatus> tServiceStatusMap;
|
|
std::vector<tServiceStatusMap> service_status_maps;
|
|
|
|
typedef struct {dropshell::ServerConfig server; dropshell::UserConfig user;} server_user_pair;
|
|
std::vector<server_user_pair> 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_user_pairs.size() << " agents: " << std::flush;
|
|
int checked = 0;
|
|
|
|
|
|
// First, check which servers are online (with caching per server, not per user)
|
|
std::map<std::string, bool> server_online_status;
|
|
// Pre-compute disabled status to avoid threading issues with config access
|
|
std::set<std::string> 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<int> 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<std::mutex> 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<std::mutex> 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<std::string, shared_commands::ServiceStatus> 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<std::mutex> 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<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server_name);
|
|
|
|
std::set<int> 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<LocalServiceInfo> 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
|