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