feat: Add search command to find services across all servers
This commit is contained in:
166
source/src/commands/search.cpp
Normal file
166
source/src/commands/search.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "command_registry.hpp"
|
||||
#include "config.hpp"
|
||||
#include "servers.hpp"
|
||||
#include "services.hpp"
|
||||
#include "shared_commands.hpp"
|
||||
#include "tableprint.hpp"
|
||||
#include "transwarp.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <iostream>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
int search_handler(const CommandContext& ctx);
|
||||
|
||||
static std::vector<std::string> search_name_list = {"search", "find"};
|
||||
|
||||
struct SearchCommandRegister {
|
||||
SearchCommandRegister() {
|
||||
CommandRegistry::instance().register_command({
|
||||
search_name_list,
|
||||
search_handler,
|
||||
nullptr, // no autocomplete
|
||||
false, // hidden
|
||||
true, // requires_config
|
||||
true, // requires_install
|
||||
1, // min_args (search string required)
|
||||
1, // max_args
|
||||
"search STRING",
|
||||
"Search for services matching a pattern across all servers",
|
||||
R"(
|
||||
Search for services across all servers. Matches against both
|
||||
service name and template name (case-insensitive substring match).
|
||||
|
||||
search STRING Search all servers for matching services
|
||||
)"
|
||||
});
|
||||
}
|
||||
} search_command_register;
|
||||
|
||||
static bool icontains(const std::string& haystack, const std::string& needle) {
|
||||
if (needle.empty()) return true;
|
||||
auto it = std::search(haystack.begin(), haystack.end(),
|
||||
needle.begin(), needle.end(),
|
||||
[](char a, char b) { return std::tolower(a) == std::tolower(b); });
|
||||
return it != haystack.end();
|
||||
}
|
||||
|
||||
int search_handler(const CommandContext& ctx) {
|
||||
std::string search_term = ctx.args[0];
|
||||
|
||||
auto servers = get_configured_servers();
|
||||
if (servers.empty()) {
|
||||
error << "No servers found" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Build list of server/user pairs
|
||||
typedef struct { ServerConfig server; 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});
|
||||
|
||||
// Collect matching results
|
||||
struct SearchResult {
|
||||
std::string server_name;
|
||||
std::string service_name;
|
||||
std::string template_name;
|
||||
std::string health;
|
||||
std::string ports;
|
||||
};
|
||||
|
||||
std::vector<SearchResult> results;
|
||||
std::mutex results_mutex;
|
||||
|
||||
info << "Searching " << server_user_pairs.size() << " agents: " << std::flush;
|
||||
int checked = 0;
|
||||
|
||||
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) {
|
||||
std::string server_name = sup.server.get_server_name();
|
||||
|
||||
// Get local service info for name/template matching
|
||||
auto services = get_server_services_info(server_name);
|
||||
|
||||
// Filter to matching services first
|
||||
std::vector<LocalServiceInfo> matching;
|
||||
for (const auto& svc : services) {
|
||||
if (icontains(svc.service_name, search_term) || icontains(svc.template_name, search_term)) {
|
||||
matching.push_back(svc);
|
||||
}
|
||||
}
|
||||
|
||||
if (matching.empty()) {
|
||||
std::lock_guard<std::mutex> lock(results_mutex);
|
||||
++checked;
|
||||
info << checked << " \xe2\x9c\x93 " << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only query remote status if we have matches
|
||||
sSSHInfo sshinfo = sup.server.get_SSH_INFO(sup.user.user);
|
||||
bool is_online = execute_ssh_command(sshinfo, sCommand("", "true", {}), cMode::Silent);
|
||||
|
||||
std::map<std::string, shared_commands::ServiceStatus> status_map;
|
||||
if (is_online) {
|
||||
status_map = shared_commands::get_all_services_status(sup.server.get_server_name(), sup.user.user);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(results_mutex);
|
||||
for (const auto& svc : matching) {
|
||||
SearchResult r;
|
||||
r.server_name = server_name;
|
||||
r.service_name = svc.service_name;
|
||||
r.template_name = svc.template_name;
|
||||
|
||||
if (!is_online) {
|
||||
r.health = ":cross:";
|
||||
r.ports = "-";
|
||||
} else {
|
||||
auto it = status_map.find(svc.service_name);
|
||||
if (it != status_map.end()) {
|
||||
r.health = shared_commands::HealthStatus2String(it->second.health);
|
||||
std::string ports_str;
|
||||
for (const auto& port : it->second.ports) {
|
||||
if (!ports_str.empty()) ports_str += " ";
|
||||
ports_str += std::to_string(port);
|
||||
}
|
||||
r.ports = ports_str.empty() ? "-" : ports_str;
|
||||
} else {
|
||||
r.health = ":greytick:";
|
||||
r.ports = "-";
|
||||
}
|
||||
}
|
||||
|
||||
results.push_back(r);
|
||||
}
|
||||
++checked;
|
||||
info << checked << " \xe2\x9c\x93 " << std::flush;
|
||||
}
|
||||
});
|
||||
task->wait();
|
||||
info << std::endl << std::endl;
|
||||
|
||||
if (results.empty()) {
|
||||
info << "No services found matching: " << search_term << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
tableprint tp("Search results for: " + search_term);
|
||||
tp.add_row({"Server", "Service", "Template", "Status", "Ports"});
|
||||
for (const auto& r : results) {
|
||||
tp.add_row({r.server_name, r.service_name, r.template_name, r.health, r.ports});
|
||||
}
|
||||
tp.sort({0, 1});
|
||||
tp.print();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
||||
Reference in New Issue
Block a user