Files
dropshell/source/src/commands/list.cpp
Your Name 23fb68903c
All checks were successful
Build-Test-Publish / build (linux/amd64) (push) Successful in 1m0s
Build-Test-Publish / build (linux/arm64) (push) Successful in 1m18s
nice disable of servers
2025-09-20 14:48:24 +12:00

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