- protocol.h: add K_LOCATOR, K_SOCKET, K_DEVICE_NAME natural key constants - models.h: add locator/socket_designation/device_name fields to structs - database.cpp: apply_*, serialize_*, save_nolock, load all updated; upsert_part gains natural key fallback for memory/cpu/disk - discovery.cpp: emit K_LOCATOR for memory sticks/slots; fix CPU filter to skip phantom iLO entries; emit K_SOCKET for cpu/cpu_slot; emit K_DEVICE_NAME for disks with serial collision fix; replace NIC blocklist with /sys/class/net/<if>/device symlink check + lspci enrichment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
989 lines
48 KiB
C++
989 lines
48 KiB
C++
#include "server/database.h"
|
|
#include "common/protocol.h"
|
|
#include <algorithm>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Escape / unescape: protect '|', '\n', '\' in pipe-delimited fields
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
std::string Database::escape(const std::string& s) {
|
|
std::string out;
|
|
out.reserve(s.size() + 4);
|
|
for (char c : s) {
|
|
if (c == '\\') out += "\\\\";
|
|
else if (c == '|') out += "\\|";
|
|
else if (c == '\n') out += "\\n";
|
|
else out += c;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::string Database::unescape(const std::string& s) {
|
|
std::string out;
|
|
out.reserve(s.size());
|
|
for (size_t i = 0; i < s.size(); ++i) {
|
|
if (s[i] == '\\' && i + 1 < s.size()) {
|
|
++i;
|
|
if (s[i] == '\\') out += '\\';
|
|
else if (s[i] == '|') out += '|';
|
|
else if (s[i] == 'n') out += '\n';
|
|
else { out += '\\'; out += s[i]; }
|
|
} else {
|
|
out += s[i];
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// split_pipe: split on '|' respecting backslash escapes
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
static std::vector<std::string> split_pipe(const std::string& line) {
|
|
std::vector<std::string> fields;
|
|
std::string cur;
|
|
for (size_t i = 0; i < line.size(); ++i) {
|
|
if (line[i] == '\\' && i + 1 < line.size()) {
|
|
cur += line[i];
|
|
cur += line[++i];
|
|
} else if (line[i] == '|') {
|
|
fields.push_back(cur);
|
|
cur.clear();
|
|
} else {
|
|
cur += line[i];
|
|
}
|
|
}
|
|
fields.push_back(cur);
|
|
return fields;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// List helpers (LS separator)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
static std::string ls_join_u64(const std::vector<uint64_t>& v) {
|
|
return join_u64(v, LS);
|
|
}
|
|
static std::string ls_join_u32(const std::vector<uint32_t>& v) {
|
|
return join_u32(v, LS);
|
|
}
|
|
static std::vector<std::string> ls_split(const std::string& s) {
|
|
if (s.empty()) return {};
|
|
return split(s, LS);
|
|
}
|
|
static std::vector<uint64_t> ls_split_u64(const std::string& s) {
|
|
std::vector<uint64_t> out;
|
|
for (const auto& p : split(s, LS))
|
|
if (!p.empty()) out.push_back(std::stoull(p));
|
|
return out;
|
|
}
|
|
static std::vector<uint32_t> ls_split_u32(const std::string& s) {
|
|
std::vector<uint32_t> out;
|
|
for (const auto& p : split(s, LS))
|
|
if (!p.empty()) out.push_back(static_cast<uint32_t>(std::stoul(p)));
|
|
return out;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// PartBase helpers
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
static std::string base_serial(const PartBase& p) {
|
|
return p.serial_number.has_value() ? p.serial_number.value() : "NULL";
|
|
}
|
|
|
|
static void apply_base(PartBase& p, const std::map<std::string,std::string>& kv) {
|
|
if (auto it = kv.find(K_SERIAL); it != kv.end()) {
|
|
if (it->second == "NULL") p.serial_number = std::nullopt;
|
|
else p.serial_number = it->second;
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Per-type apply helpers (static free functions)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
static void apply_memory_stick(MemoryStick& m, const std::map<std::string,std::string>& kv) {
|
|
apply_base(m, kv);
|
|
if (auto it = kv.find(K_SPEED_MHZ); it != kv.end()) m.speed_mhz = static_cast<uint32_t>(std::stoul(it->second));
|
|
if (auto it = kv.find(K_SIZE_MB); it != kv.end()) m.size_mb = std::stoull(it->second);
|
|
if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) m.manufacturer = it->second;
|
|
if (auto it = kv.find(K_CHANNEL_CONFIG); it != kv.end()) m.channel_config = it->second;
|
|
if (auto it = kv.find(K_OTHER_INFO); it != kv.end()) m.other_info = it->second;
|
|
if (auto it = kv.find(K_LOCATOR); it != kv.end()) m.locator = it->second;
|
|
}
|
|
|
|
static void apply_memory_slot(MemorySlot& m, const std::map<std::string,std::string>& kv) {
|
|
apply_base(m, kv);
|
|
if (auto it = kv.find(K_ALLOWED_SPEED); it != kv.end()) m.allowed_speed_mhz = static_cast<uint32_t>(std::stoul(it->second));
|
|
if (auto it = kv.find(K_ALLOWED_SIZE); it != kv.end()) m.allowed_size_mb = std::stoull(it->second);
|
|
if (auto it = kv.find(K_INSTALLED_STICK); it != kv.end()) {
|
|
if (it->second == "NULL") m.installed_stick_id = std::nullopt;
|
|
else m.installed_stick_id = static_cast<uint32_t>(std::stoul(it->second));
|
|
}
|
|
if (auto it = kv.find(K_LOCATOR); it != kv.end()) m.locator = it->second;
|
|
}
|
|
|
|
static void apply_cpu(CPU& c, const std::map<std::string,std::string>& kv) {
|
|
apply_base(c, kv);
|
|
if (auto it = kv.find(K_NAME); it != kv.end()) c.name = it->second;
|
|
if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) c.manufacturer = it->second;
|
|
if (auto it = kv.find(K_SPEED_GHZ); it != kv.end()) c.speed_ghz = std::stod(it->second);
|
|
if (auto it = kv.find(K_CORES); it != kv.end()) c.cores = static_cast<uint32_t>(std::stoul(it->second));
|
|
if (auto it = kv.find(K_THREADS); it != kv.end()) c.threads = static_cast<uint32_t>(std::stoul(it->second));
|
|
if (auto it = kv.find(K_SOCKET); it != kv.end()) c.socket_designation = it->second;
|
|
}
|
|
|
|
static void apply_cpu_slot(CPUSlot& c, const std::map<std::string,std::string>& kv) {
|
|
apply_base(c, kv);
|
|
if (auto it = kv.find(K_FORM_FACTOR); it != kv.end()) c.form_factor = it->second;
|
|
if (auto it = kv.find(K_INSTALLED_CPU); it != kv.end()) {
|
|
if (it->second == "NULL") c.installed_cpu_id = std::nullopt;
|
|
else c.installed_cpu_id = static_cast<uint32_t>(std::stoul(it->second));
|
|
}
|
|
if (auto it = kv.find(K_SOCKET); it != kv.end()) c.socket_designation = it->second;
|
|
}
|
|
|
|
static void apply_disk(Disk& d, const std::map<std::string,std::string>& kv) {
|
|
apply_base(d, kv);
|
|
if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) d.manufacturer = it->second;
|
|
if (auto it = kv.find(K_MODEL); it != kv.end()) d.model = it->second;
|
|
if (auto it = kv.find(K_GENERATION); it != kv.end()) d.generation = it->second;
|
|
if (auto it = kv.find(K_CONN_TYPE); it != kv.end()) d.connection_type = it->second;
|
|
if (auto it = kv.find(K_CONN_SPEED); it != kv.end()) d.connection_speed_mbps = std::stoull(it->second);
|
|
if (auto it = kv.find(K_DISK_SPEED); it != kv.end()) d.disk_speed_mbps = std::stoull(it->second);
|
|
if (auto it = kv.find(K_DISK_SIZE); it != kv.end()) d.disk_size_gb = std::stoull(it->second);
|
|
if (auto it = kv.find(K_DISK_TYPE); it != kv.end()) d.disk_type = it->second;
|
|
if (auto it = kv.find(K_AGE_YEARS); it != kv.end()) d.age_years = std::stoi(it->second);
|
|
if (auto it = kv.find(K_PARTITION_IDS); it != kv.end()) d.partition_ids = ls_split(it->second);
|
|
if (auto it = kv.find(K_PARTITION_SIZES); it != kv.end()) d.partition_sizes_gb = ls_split_u64(it->second);
|
|
if (auto it = kv.find(K_VM_HOSTNAMES); it != kv.end()) d.vm_hostnames = ls_split(it->second);
|
|
if (auto it = kv.find(K_VM_SERVER_IDS); it != kv.end()) d.vm_server_ids = ls_split_u32(it->second);
|
|
if (auto it = kv.find(K_DEVICE_NAME); it != kv.end()) d.device_name = it->second;
|
|
}
|
|
|
|
static void apply_nic(NetworkCard& n, const std::map<std::string,std::string>& kv) {
|
|
apply_base(n, kv);
|
|
if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) n.manufacturer = it->second;
|
|
if (auto it = kv.find(K_MODEL); it != kv.end()) n.model = it->second;
|
|
if (auto it = kv.find(K_AGE_YEARS); it != kv.end()) n.age_years = std::stoi(it->second);
|
|
if (auto it = kv.find(K_CONN_TYPE); it != kv.end()) n.connection_type = it->second;
|
|
if (auto it = kv.find(K_CONN_SPEED); it != kv.end()) n.connection_speed_mbps = std::stoull(it->second);
|
|
if (auto it = kv.find(K_MAC); it != kv.end()) n.mac_address = it->second;
|
|
if (auto it = kv.find(K_IPS); it != kv.end()) n.ip_addresses = ls_split(it->second);
|
|
if (auto it = kv.find(K_DHCP); it != kv.end()) n.dhcp = (it->second == "1" || it->second == "true");
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Wire-row serializers
|
|
// type_name<FS>part_id<FS>server_id<FS>serial_or_NULL<FS>last_updated<FS>k1<FS>v1...
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
static std::string base_prefix(const char* type_name, const PartBase& p) {
|
|
return std::string(type_name)
|
|
+ FS + std::to_string(p.part_id)
|
|
+ FS + std::to_string(p.server_id)
|
|
+ FS + base_serial(p)
|
|
+ FS + std::to_string(p.last_updated);
|
|
}
|
|
|
|
std::string Database::serialize_memory_stick(const MemoryStick& m) const {
|
|
return base_prefix(PTYPE_MEMORY_STICK, m)
|
|
+ FS + K_SPEED_MHZ + FS + std::to_string(m.speed_mhz)
|
|
+ FS + K_SIZE_MB + FS + std::to_string(m.size_mb)
|
|
+ FS + K_MANUFACTURER + FS + m.manufacturer
|
|
+ FS + K_CHANNEL_CONFIG + FS + m.channel_config
|
|
+ FS + K_OTHER_INFO + FS + m.other_info
|
|
+ FS + K_LOCATOR + FS + m.locator
|
|
+ "\n";
|
|
}
|
|
|
|
std::string Database::serialize_memory_slot(const MemorySlot& m) const {
|
|
std::string installed = m.installed_stick_id.has_value()
|
|
? std::to_string(m.installed_stick_id.value()) : "NULL";
|
|
return base_prefix(PTYPE_MEMORY_SLOT, m)
|
|
+ FS + K_ALLOWED_SPEED + FS + std::to_string(m.allowed_speed_mhz)
|
|
+ FS + K_ALLOWED_SIZE + FS + std::to_string(m.allowed_size_mb)
|
|
+ FS + K_INSTALLED_STICK + FS + installed
|
|
+ FS + K_LOCATOR + FS + m.locator
|
|
+ "\n";
|
|
}
|
|
|
|
std::string Database::serialize_cpu(const CPU& c) const {
|
|
return base_prefix(PTYPE_CPU, c)
|
|
+ FS + K_NAME + FS + c.name
|
|
+ FS + K_MANUFACTURER + FS + c.manufacturer
|
|
+ FS + K_SPEED_GHZ + FS + std::to_string(c.speed_ghz)
|
|
+ FS + K_CORES + FS + std::to_string(c.cores)
|
|
+ FS + K_THREADS + FS + std::to_string(c.threads)
|
|
+ FS + K_SOCKET + FS + c.socket_designation
|
|
+ "\n";
|
|
}
|
|
|
|
std::string Database::serialize_cpu_slot(const CPUSlot& c) const {
|
|
std::string installed = c.installed_cpu_id.has_value()
|
|
? std::to_string(c.installed_cpu_id.value()) : "NULL";
|
|
return base_prefix(PTYPE_CPU_SLOT, c)
|
|
+ FS + K_FORM_FACTOR + FS + c.form_factor
|
|
+ FS + K_INSTALLED_CPU + FS + installed
|
|
+ FS + K_SOCKET + FS + c.socket_designation
|
|
+ "\n";
|
|
}
|
|
|
|
std::string Database::serialize_disk(const Disk& d) const {
|
|
// Build per-list string: escape each element, join with LS
|
|
auto esc_join = [](const std::vector<std::string>& v) -> std::string {
|
|
std::string out;
|
|
for (size_t i = 0; i < v.size(); ++i) {
|
|
if (i) out += LS;
|
|
// escape element (pipe/backslash could appear in partition ids or hostnames)
|
|
for (char c : v[i]) {
|
|
if (c == '\\') out += "\\\\";
|
|
else if (c == '|') out += "\\|";
|
|
else out += c;
|
|
}
|
|
}
|
|
return out;
|
|
};
|
|
return base_prefix(PTYPE_DISK, d)
|
|
+ FS + K_MANUFACTURER + FS + d.manufacturer
|
|
+ FS + K_MODEL + FS + d.model
|
|
+ FS + K_GENERATION + FS + d.generation
|
|
+ FS + K_CONN_TYPE + FS + d.connection_type
|
|
+ FS + K_CONN_SPEED + FS + std::to_string(d.connection_speed_mbps)
|
|
+ FS + K_DISK_SPEED + FS + std::to_string(d.disk_speed_mbps)
|
|
+ FS + K_DISK_SIZE + FS + std::to_string(d.disk_size_gb)
|
|
+ FS + K_DISK_TYPE + FS + d.disk_type
|
|
+ FS + K_AGE_YEARS + FS + std::to_string(d.age_years)
|
|
+ FS + K_PARTITION_IDS + FS + esc_join(d.partition_ids)
|
|
+ FS + K_PARTITION_SIZES + FS + ls_join_u64(d.partition_sizes_gb)
|
|
+ FS + K_VM_HOSTNAMES + FS + esc_join(d.vm_hostnames)
|
|
+ FS + K_VM_SERVER_IDS + FS + ls_join_u32(d.vm_server_ids)
|
|
+ FS + K_DEVICE_NAME + FS + d.device_name
|
|
+ "\n";
|
|
}
|
|
|
|
std::string Database::serialize_nic(const NetworkCard& n) const {
|
|
auto esc_join = [](const std::vector<std::string>& v) -> std::string {
|
|
std::string out;
|
|
for (size_t i = 0; i < v.size(); ++i) {
|
|
if (i) out += LS;
|
|
for (char c : v[i]) {
|
|
if (c == '\\') out += "\\\\";
|
|
else if (c == '|') out += "\\|";
|
|
else out += c;
|
|
}
|
|
}
|
|
return out;
|
|
};
|
|
return base_prefix(PTYPE_NIC, n)
|
|
+ FS + K_MANUFACTURER + FS + n.manufacturer
|
|
+ FS + K_MODEL + FS + n.model
|
|
+ FS + K_AGE_YEARS + FS + std::to_string(n.age_years)
|
|
+ FS + K_CONN_TYPE + FS + n.connection_type
|
|
+ FS + K_CONN_SPEED + FS + std::to_string(n.connection_speed_mbps)
|
|
+ FS + K_MAC + FS + n.mac_address
|
|
+ FS + K_IPS + FS + esc_join(n.ip_addresses)
|
|
+ FS + K_DHCP + FS + (n.dhcp ? "1" : "0")
|
|
+ "\n";
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Constructor
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
Database::Database(std::string path) : path_(std::move(path)) {}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// alloc_part_id — caller must hold mu_
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
uint32_t Database::alloc_part_id() {
|
|
return inv_.next_part_id++;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// save_nolock / save
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
bool Database::save_nolock() {
|
|
std::ofstream f(path_, std::ios::trunc);
|
|
if (!f.is_open()) {
|
|
std::cerr << "[db] cannot open " << path_ << " for writing\n";
|
|
return false;
|
|
}
|
|
f << "# device-inventory database\n";
|
|
f << "META|" << inv_.next_server_id << "|"
|
|
<< inv_.next_part_type_id << "|"
|
|
<< inv_.next_part_id << "\n";
|
|
|
|
for (const auto& s : inv_.servers) {
|
|
f << "S|" << s.id
|
|
<< "|" << escape(s.name)
|
|
<< "|" << escape(s.hostname)
|
|
<< "|" << escape(s.location)
|
|
<< "|" << escape(s.description) << "\n";
|
|
}
|
|
for (const auto& pt : inv_.part_types) {
|
|
f << "PT|" << pt.id
|
|
<< "|" << escape(pt.name)
|
|
<< "|" << escape(pt.description) << "\n";
|
|
}
|
|
|
|
// Helper lambda: write LS-joined list of escaped strings as a single pipe field
|
|
auto write_str_list = [](std::ofstream& out, const std::vector<std::string>& v) {
|
|
for (size_t i = 0; i < v.size(); ++i) {
|
|
if (i) out << static_cast<char>(LS);
|
|
// escape individual elements (protect | and \ within each element)
|
|
for (char c : v[i]) {
|
|
if (c == '\\') out << "\\\\";
|
|
else if (c == '|') out << "\\|";
|
|
else out << c;
|
|
}
|
|
}
|
|
};
|
|
|
|
for (const auto& m : inv_.memory_sticks) {
|
|
f << "MS|" << m.part_id << "|" << m.server_id
|
|
<< "|" << escape(base_serial(m))
|
|
<< "|" << m.last_updated
|
|
<< "|" << m.speed_mhz
|
|
<< "|" << m.size_mb
|
|
<< "|" << escape(m.manufacturer)
|
|
<< "|" << escape(m.channel_config)
|
|
<< "|" << escape(m.other_info)
|
|
<< "|" << escape(m.locator) << "\n";
|
|
}
|
|
for (const auto& m : inv_.memory_slots) {
|
|
std::string inst = m.installed_stick_id.has_value()
|
|
? std::to_string(m.installed_stick_id.value()) : "NULL";
|
|
f << "MSLOT|" << m.part_id << "|" << m.server_id
|
|
<< "|" << escape(base_serial(m))
|
|
<< "|" << m.last_updated
|
|
<< "|" << m.allowed_speed_mhz
|
|
<< "|" << m.allowed_size_mb
|
|
<< "|" << inst
|
|
<< "|" << escape(m.locator) << "\n";
|
|
}
|
|
for (const auto& c : inv_.cpus) {
|
|
f << "CPU|" << c.part_id << "|" << c.server_id
|
|
<< "|" << escape(base_serial(c))
|
|
<< "|" << c.last_updated
|
|
<< "|" << escape(c.name)
|
|
<< "|" << escape(c.manufacturer)
|
|
<< "|" << c.speed_ghz
|
|
<< "|" << c.cores
|
|
<< "|" << c.threads
|
|
<< "|" << escape(c.socket_designation) << "\n";
|
|
}
|
|
for (const auto& c : inv_.cpu_slots) {
|
|
std::string inst = c.installed_cpu_id.has_value()
|
|
? std::to_string(c.installed_cpu_id.value()) : "NULL";
|
|
f << "CPUSLOT|" << c.part_id << "|" << c.server_id
|
|
<< "|" << escape(base_serial(c))
|
|
<< "|" << c.last_updated
|
|
<< "|" << escape(c.form_factor)
|
|
<< "|" << inst
|
|
<< "|" << escape(c.socket_designation) << "\n";
|
|
}
|
|
for (const auto& d : inv_.disks) {
|
|
f << "DISK|" << d.part_id << "|" << d.server_id
|
|
<< "|" << escape(base_serial(d))
|
|
<< "|" << d.last_updated
|
|
<< "|" << escape(d.manufacturer)
|
|
<< "|" << escape(d.model)
|
|
<< "|" << escape(d.generation)
|
|
<< "|" << escape(d.connection_type)
|
|
<< "|" << d.connection_speed_mbps
|
|
<< "|" << d.disk_speed_mbps
|
|
<< "|" << d.disk_size_gb
|
|
<< "|" << escape(d.disk_type)
|
|
<< "|" << d.age_years << "|";
|
|
write_str_list(f, d.partition_ids);
|
|
f << "|";
|
|
// partition_sizes_gb: numeric, no escape needed
|
|
for (size_t i = 0; i < d.partition_sizes_gb.size(); ++i) {
|
|
if (i) f << static_cast<char>(LS);
|
|
f << d.partition_sizes_gb[i];
|
|
}
|
|
f << "|";
|
|
write_str_list(f, d.vm_hostnames);
|
|
f << "|";
|
|
for (size_t i = 0; i < d.vm_server_ids.size(); ++i) {
|
|
if (i) f << static_cast<char>(LS);
|
|
f << d.vm_server_ids[i];
|
|
}
|
|
f << "|" << escape(d.device_name) << "\n";
|
|
}
|
|
for (const auto& n : inv_.network_cards) {
|
|
f << "NIC|" << n.part_id << "|" << n.server_id
|
|
<< "|" << escape(base_serial(n))
|
|
<< "|" << n.last_updated
|
|
<< "|" << escape(n.manufacturer)
|
|
<< "|" << escape(n.model)
|
|
<< "|" << n.age_years
|
|
<< "|" << escape(n.connection_type)
|
|
<< "|" << n.connection_speed_mbps
|
|
<< "|" << escape(n.mac_address) << "|";
|
|
write_str_list(f, n.ip_addresses);
|
|
f << "|" << (n.dhcp ? 1 : 0) << "\n";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Database::save() {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
return save_nolock();
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// load
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
bool Database::load() {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
std::ifstream f(path_);
|
|
if (!f.is_open()) {
|
|
inv_ = Inventory{};
|
|
return true;
|
|
}
|
|
|
|
Inventory fresh;
|
|
std::string line;
|
|
while (std::getline(f, line)) {
|
|
if (line.empty() || line[0] == '#') continue;
|
|
auto flds = split_pipe(line);
|
|
if (flds.empty()) continue;
|
|
const auto& rec = flds[0];
|
|
|
|
auto get = [&](size_t i) -> std::string {
|
|
return i < flds.size() ? unescape(flds[i]) : "";
|
|
};
|
|
auto getu32 = [&](size_t i) -> uint32_t {
|
|
return i < flds.size() ? static_cast<uint32_t>(std::stoul(flds[i])) : 0;
|
|
};
|
|
auto getu64 = [&](size_t i) -> uint64_t {
|
|
return i < flds.size() ? std::stoull(flds[i]) : 0ULL;
|
|
};
|
|
auto geti64 = [&](size_t i) -> int64_t {
|
|
return i < flds.size() ? std::stoll(flds[i]) : 0LL;
|
|
};
|
|
// serial field: raw flds[i] (not unescaped via get) but compare to "NULL"
|
|
auto get_serial = [&](size_t i) -> std::optional<std::string> {
|
|
if (i >= flds.size() || flds[i] == "NULL") return std::nullopt;
|
|
return unescape(flds[i]);
|
|
};
|
|
auto get_opt_u32 = [&](size_t i) -> std::optional<uint32_t> {
|
|
if (i >= flds.size() || flds[i] == "NULL") return std::nullopt;
|
|
return static_cast<uint32_t>(std::stoul(flds[i]));
|
|
};
|
|
|
|
if (rec == "META" && flds.size() >= 4) {
|
|
fresh.next_server_id = getu32(1);
|
|
fresh.next_part_type_id = getu32(2);
|
|
fresh.next_part_id = getu32(3);
|
|
} else if (rec == "S" && flds.size() >= 6) {
|
|
Server s;
|
|
s.id = getu32(1);
|
|
s.name = get(2);
|
|
s.hostname = get(3);
|
|
s.location = get(4);
|
|
s.description = get(5);
|
|
fresh.servers.push_back(std::move(s));
|
|
} else if (rec == "PT" && flds.size() >= 4) {
|
|
PartType pt;
|
|
pt.id = getu32(1);
|
|
pt.name = get(2);
|
|
pt.description = get(3);
|
|
fresh.part_types.push_back(std::move(pt));
|
|
} else if (rec == "MS" && flds.size() >= 10) {
|
|
MemoryStick m;
|
|
m.part_id = getu32(1);
|
|
m.server_id = getu32(2);
|
|
m.serial_number = get_serial(3);
|
|
m.last_updated = geti64(4);
|
|
m.speed_mhz = getu32(5);
|
|
m.size_mb = getu64(6);
|
|
m.manufacturer = get(7);
|
|
m.channel_config = get(8);
|
|
m.other_info = get(9);
|
|
m.locator = get(10);
|
|
fresh.memory_sticks.push_back(std::move(m));
|
|
} else if (rec == "MSLOT" && flds.size() >= 8) {
|
|
MemorySlot m;
|
|
m.part_id = getu32(1);
|
|
m.server_id = getu32(2);
|
|
m.serial_number = get_serial(3);
|
|
m.last_updated = geti64(4);
|
|
m.allowed_speed_mhz = getu32(5);
|
|
m.allowed_size_mb = getu64(6);
|
|
m.installed_stick_id = get_opt_u32(7);
|
|
m.locator = get(8);
|
|
fresh.memory_slots.push_back(std::move(m));
|
|
} else if (rec == "CPU" && flds.size() >= 10) {
|
|
CPU c;
|
|
c.part_id = getu32(1);
|
|
c.server_id = getu32(2);
|
|
c.serial_number = get_serial(3);
|
|
c.last_updated = geti64(4);
|
|
c.name = get(5);
|
|
c.manufacturer = get(6);
|
|
c.speed_ghz = flds.size() > 7 ? std::stod(flds[7]) : 0.0;
|
|
c.cores = getu32(8);
|
|
c.threads = getu32(9);
|
|
c.socket_designation = get(10);
|
|
fresh.cpus.push_back(std::move(c));
|
|
} else if (rec == "CPUSLOT" && flds.size() >= 7) {
|
|
CPUSlot c;
|
|
c.part_id = getu32(1);
|
|
c.server_id = getu32(2);
|
|
c.serial_number = get_serial(3);
|
|
c.last_updated = geti64(4);
|
|
c.form_factor = get(5);
|
|
c.installed_cpu_id = get_opt_u32(6);
|
|
c.socket_designation = get(7);
|
|
fresh.cpu_slots.push_back(std::move(c));
|
|
} else if (rec == "DISK" && flds.size() >= 18) {
|
|
Disk d;
|
|
d.part_id = getu32(1);
|
|
d.server_id = getu32(2);
|
|
d.serial_number = get_serial(3);
|
|
d.last_updated = geti64(4);
|
|
d.manufacturer = get(5);
|
|
d.model = get(6);
|
|
d.generation = get(7);
|
|
d.connection_type = get(8);
|
|
d.connection_speed_mbps = getu64(9);
|
|
d.disk_speed_mbps = getu64(10);
|
|
d.disk_size_gb = getu64(11);
|
|
d.disk_type = get(12);
|
|
d.age_years = flds.size() > 13 ? std::stoi(flds[13]) : -1;
|
|
// Note: flds[14..17] have NOT been through unescape() yet (we used raw flds)
|
|
// Re-split using the raw (escaped) field and unescape each element
|
|
auto uesc_split_raw = [](const std::string& raw) -> std::vector<std::string> {
|
|
if (raw.empty()) return {};
|
|
std::vector<std::string> out;
|
|
for (const auto& elem : split(raw, LS)) {
|
|
std::string u;
|
|
for (size_t i = 0; i < elem.size(); ++i) {
|
|
if (elem[i] == '\\' && i+1 < elem.size()) {
|
|
++i;
|
|
if (elem[i] == '\\') u += '\\';
|
|
else if (elem[i] == '|') u += '|';
|
|
else if (elem[i] == 'n') u += '\n';
|
|
else { u += '\\'; u += elem[i]; }
|
|
} else u += elem[i];
|
|
}
|
|
out.push_back(u);
|
|
}
|
|
return out;
|
|
};
|
|
d.partition_ids = uesc_split_raw(flds.size() > 14 ? flds[14] : "");
|
|
d.partition_sizes_gb = ls_split_u64(flds.size() > 15 ? flds[15] : "");
|
|
d.vm_hostnames = uesc_split_raw(flds.size() > 16 ? flds[16] : "");
|
|
d.vm_server_ids = ls_split_u32(flds.size() > 17 ? flds[17] : "");
|
|
d.device_name = get(18);
|
|
fresh.disks.push_back(std::move(d));
|
|
} else if (rec == "NIC" && flds.size() >= 13) {
|
|
NetworkCard n;
|
|
n.part_id = getu32(1);
|
|
n.server_id = getu32(2);
|
|
n.serial_number = get_serial(3);
|
|
n.last_updated = geti64(4);
|
|
n.manufacturer = get(5);
|
|
n.model = get(6);
|
|
n.age_years = flds.size() > 7 ? std::stoi(flds[7]) : -1;
|
|
n.connection_type = get(8);
|
|
n.connection_speed_mbps= getu64(9);
|
|
n.mac_address = get(10);
|
|
// ip_addresses: LS separated in flds[11]
|
|
{
|
|
auto& raw = flds[11];
|
|
for (auto& p : split(raw, LS))
|
|
n.ip_addresses.push_back(unescape(p));
|
|
}
|
|
n.dhcp = (flds.size() > 12 && flds[12] == "1");
|
|
fresh.network_cards.push_back(std::move(n));
|
|
}
|
|
}
|
|
inv_ = std::move(fresh);
|
|
return true;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Servers
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
Server Database::add_server(std::string name, std::string hostname,
|
|
std::string location, std::string description) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
Server s;
|
|
s.id = inv_.next_server_id++;
|
|
s.name = std::move(name);
|
|
s.hostname = std::move(hostname);
|
|
s.location = std::move(location);
|
|
s.description = std::move(description);
|
|
inv_.servers.push_back(s);
|
|
save_nolock();
|
|
return s;
|
|
}
|
|
|
|
std::vector<Server> Database::list_servers() const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
return inv_.servers;
|
|
}
|
|
|
|
std::optional<Server> Database::get_server(uint32_t id) const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
for (const auto& s : inv_.servers)
|
|
if (s.id == id) return s;
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool Database::edit_server(uint32_t id, const std::string& field, const std::string& value) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
for (auto& s : inv_.servers) {
|
|
if (s.id != id) continue;
|
|
if (field == "name") s.name = value;
|
|
else if (field == "hostname") s.hostname = value;
|
|
else if (field == "location") s.location = value;
|
|
else if (field == "description") s.description = value;
|
|
else return false;
|
|
save_nolock();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Database::remove_server(uint32_t id) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
auto& v = inv_.servers;
|
|
auto it = std::find_if(v.begin(), v.end(), [id](const Server& s){ return s.id == id; });
|
|
if (it == v.end()) return false;
|
|
v.erase(it);
|
|
save_nolock();
|
|
return true;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Part types
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
PartType Database::add_part_type(std::string name, std::string description) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
PartType pt;
|
|
pt.id = inv_.next_part_type_id++;
|
|
pt.name = std::move(name);
|
|
pt.description = std::move(description);
|
|
inv_.part_types.push_back(pt);
|
|
save_nolock();
|
|
return pt;
|
|
}
|
|
|
|
std::vector<PartType> Database::list_part_types() const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
return inv_.part_types;
|
|
}
|
|
|
|
std::optional<PartType> Database::get_part_type(uint32_t id) const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
for (const auto& pt : inv_.part_types)
|
|
if (pt.id == id) return pt;
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool Database::edit_part_type(uint32_t id, const std::string& field, const std::string& value) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
for (auto& pt : inv_.part_types) {
|
|
if (pt.id != id) continue;
|
|
if (field == "name") pt.name = value;
|
|
else if (field == "description") pt.description = value;
|
|
else return false;
|
|
save_nolock();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Database::remove_part_type(uint32_t id) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
auto& v = inv_.part_types;
|
|
auto it = std::find_if(v.begin(), v.end(), [id](const PartType& pt){ return pt.id == id; });
|
|
if (it == v.end()) return false;
|
|
v.erase(it);
|
|
save_nolock();
|
|
return true;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// add_part_nolock — caller must hold mu_
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
uint32_t Database::add_part_nolock(const std::string& type_name, uint32_t server_id,
|
|
const std::map<std::string,std::string>& kv) {
|
|
uint32_t pid = alloc_part_id();
|
|
int64_t now = static_cast<int64_t>(time(nullptr));
|
|
|
|
if (type_name == PTYPE_MEMORY_STICK) {
|
|
MemoryStick m; m.part_id = pid; m.server_id = server_id; m.last_updated = now;
|
|
apply_memory_stick(m, kv);
|
|
inv_.memory_sticks.push_back(std::move(m));
|
|
} else if (type_name == PTYPE_MEMORY_SLOT) {
|
|
MemorySlot m; m.part_id = pid; m.server_id = server_id; m.last_updated = now;
|
|
apply_memory_slot(m, kv);
|
|
inv_.memory_slots.push_back(std::move(m));
|
|
} else if (type_name == PTYPE_CPU) {
|
|
CPU c; c.part_id = pid; c.server_id = server_id; c.last_updated = now;
|
|
apply_cpu(c, kv);
|
|
inv_.cpus.push_back(std::move(c));
|
|
} else if (type_name == PTYPE_CPU_SLOT) {
|
|
CPUSlot c; c.part_id = pid; c.server_id = server_id; c.last_updated = now;
|
|
apply_cpu_slot(c, kv);
|
|
inv_.cpu_slots.push_back(std::move(c));
|
|
} else if (type_name == PTYPE_DISK) {
|
|
Disk d; d.part_id = pid; d.server_id = server_id; d.last_updated = now;
|
|
apply_disk(d, kv);
|
|
inv_.disks.push_back(std::move(d));
|
|
} else if (type_name == PTYPE_NIC) {
|
|
NetworkCard n; n.part_id = pid; n.server_id = server_id; n.last_updated = now;
|
|
apply_nic(n, kv);
|
|
inv_.network_cards.push_back(std::move(n));
|
|
} else {
|
|
// Unknown type — no-op, reclaim the id by decrementing
|
|
--inv_.next_part_id;
|
|
return 0;
|
|
}
|
|
save_nolock();
|
|
return pid;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// add_part (public, locks)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
uint32_t Database::add_part(const std::string& type_name, uint32_t server_id,
|
|
const std::map<std::string,std::string>& kv) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
return add_part_nolock(type_name, server_id, kv);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// upsert_part
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
uint32_t Database::upsert_part(const std::string& type_name, uint32_t server_id,
|
|
const std::map<std::string,std::string>& kv) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
int64_t now = static_cast<int64_t>(time(nullptr));
|
|
|
|
if (type_name == PTYPE_NIC) {
|
|
auto mac_it = kv.find(K_MAC);
|
|
if (mac_it != kv.end() && !mac_it->second.empty()) {
|
|
for (auto& n : inv_.network_cards) {
|
|
if (n.server_id == server_id && n.mac_address == mac_it->second) {
|
|
apply_nic(n, kv);
|
|
n.last_updated = now;
|
|
save_nolock();
|
|
return n.part_id;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
auto ser_it = kv.find(K_SERIAL);
|
|
if (ser_it != kv.end() && ser_it->second != "NULL" && !ser_it->second.empty()) {
|
|
const std::string& serial = ser_it->second;
|
|
// Search in the collection matching type_name
|
|
auto try_update = [&](auto& vec) -> uint32_t {
|
|
for (auto& p : vec) {
|
|
if (p.server_id == server_id && p.serial_number.has_value()
|
|
&& p.serial_number.value() == serial) {
|
|
if constexpr (std::is_same_v<std::decay_t<decltype(p)>, MemoryStick>)
|
|
apply_memory_stick(p, kv);
|
|
else if constexpr (std::is_same_v<std::decay_t<decltype(p)>, MemorySlot>)
|
|
apply_memory_slot(p, kv);
|
|
else if constexpr (std::is_same_v<std::decay_t<decltype(p)>, CPU>)
|
|
apply_cpu(p, kv);
|
|
else if constexpr (std::is_same_v<std::decay_t<decltype(p)>, CPUSlot>)
|
|
apply_cpu_slot(p, kv);
|
|
else if constexpr (std::is_same_v<std::decay_t<decltype(p)>, Disk>)
|
|
apply_disk(p, kv);
|
|
p.last_updated = now;
|
|
save_nolock();
|
|
return p.part_id;
|
|
}
|
|
}
|
|
return 0u;
|
|
};
|
|
|
|
uint32_t found = 0;
|
|
if (type_name == PTYPE_MEMORY_STICK) found = try_update(inv_.memory_sticks);
|
|
else if (type_name == PTYPE_MEMORY_SLOT) found = try_update(inv_.memory_slots);
|
|
else if (type_name == PTYPE_CPU) found = try_update(inv_.cpus);
|
|
else if (type_name == PTYPE_CPU_SLOT) found = try_update(inv_.cpu_slots);
|
|
else if (type_name == PTYPE_DISK) found = try_update(inv_.disks);
|
|
if (found) return found;
|
|
}
|
|
// Natural key fallback when no serial — prevents duplicate inserts on each run
|
|
if (type_name == PTYPE_MEMORY_STICK || type_name == PTYPE_MEMORY_SLOT) {
|
|
auto loc_it = kv.find(K_LOCATOR);
|
|
if (loc_it != kv.end() && !loc_it->second.empty()) {
|
|
const std::string& locator = loc_it->second;
|
|
auto try_locator = [&](auto& vec, auto apply_fn) -> uint32_t {
|
|
for (auto& p : vec) {
|
|
if (p.server_id == server_id && p.locator == locator) {
|
|
apply_fn(p, kv);
|
|
p.last_updated = now;
|
|
save_nolock();
|
|
return p.part_id;
|
|
}
|
|
}
|
|
return 0u;
|
|
};
|
|
uint32_t found = 0;
|
|
if (type_name == PTYPE_MEMORY_STICK) found = try_locator(inv_.memory_sticks, apply_memory_stick);
|
|
else if (type_name == PTYPE_MEMORY_SLOT) found = try_locator(inv_.memory_slots, apply_memory_slot);
|
|
if (found) return found;
|
|
}
|
|
} else if (type_name == PTYPE_CPU || type_name == PTYPE_CPU_SLOT) {
|
|
auto sock_it = kv.find(K_SOCKET);
|
|
if (sock_it != kv.end() && !sock_it->second.empty()) {
|
|
const std::string& socket = sock_it->second;
|
|
auto try_socket = [&](auto& vec, auto apply_fn) -> uint32_t {
|
|
for (auto& p : vec) {
|
|
if (p.server_id == server_id && p.socket_designation == socket) {
|
|
apply_fn(p, kv);
|
|
p.last_updated = now;
|
|
save_nolock();
|
|
return p.part_id;
|
|
}
|
|
}
|
|
return 0u;
|
|
};
|
|
uint32_t found = 0;
|
|
if (type_name == PTYPE_CPU) found = try_socket(inv_.cpus, apply_cpu);
|
|
else if (type_name == PTYPE_CPU_SLOT) found = try_socket(inv_.cpu_slots, apply_cpu_slot);
|
|
if (found) return found;
|
|
}
|
|
} else if (type_name == PTYPE_DISK) {
|
|
auto dev_it = kv.find(K_DEVICE_NAME);
|
|
if (dev_it != kv.end() && !dev_it->second.empty()) {
|
|
const std::string& dev_name = dev_it->second;
|
|
for (auto& d : inv_.disks) {
|
|
if (d.server_id == server_id && d.device_name == dev_name) {
|
|
apply_disk(d, kv);
|
|
d.last_updated = now;
|
|
save_nolock();
|
|
return d.part_id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Not found — insert
|
|
return add_part_nolock(type_name, server_id, kv);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// edit_part
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
bool Database::edit_part(uint32_t part_id, const std::map<std::string,std::string>& kv) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
int64_t now = static_cast<int64_t>(time(nullptr));
|
|
|
|
auto try_edit = [&](auto& vec, auto apply_fn) -> bool {
|
|
for (auto& p : vec) {
|
|
if (p.part_id != part_id) continue;
|
|
apply_fn(p, kv);
|
|
p.last_updated = now;
|
|
save_nolock();
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (try_edit(inv_.memory_sticks, apply_memory_stick)) return true;
|
|
if (try_edit(inv_.memory_slots, apply_memory_slot)) return true;
|
|
if (try_edit(inv_.cpus, apply_cpu)) return true;
|
|
if (try_edit(inv_.cpu_slots, apply_cpu_slot)) return true;
|
|
if (try_edit(inv_.disks, apply_disk)) return true;
|
|
if (try_edit(inv_.network_cards, apply_nic)) return true;
|
|
return false;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// remove_part
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
bool Database::remove_part(uint32_t part_id) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
|
|
auto try_remove = [&](auto& vec) -> bool {
|
|
auto it = std::find_if(vec.begin(), vec.end(),
|
|
[part_id](const auto& p){ return p.part_id == part_id; });
|
|
if (it == vec.end()) return false;
|
|
vec.erase(it);
|
|
return true;
|
|
};
|
|
|
|
bool removed = try_remove(inv_.memory_sticks)
|
|
|| try_remove(inv_.memory_slots)
|
|
|| try_remove(inv_.cpus)
|
|
|| try_remove(inv_.cpu_slots)
|
|
|| try_remove(inv_.disks)
|
|
|| try_remove(inv_.network_cards);
|
|
if (removed) save_nolock();
|
|
return removed;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// list_parts / get_part_row
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
std::vector<std::string> Database::list_parts(uint32_t server_id,
|
|
const std::string& type_filter) const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
std::vector<std::string> rows;
|
|
bool all = type_filter.empty();
|
|
|
|
if (all || type_filter == PTYPE_MEMORY_STICK)
|
|
for (const auto& m : inv_.memory_sticks)
|
|
if (m.server_id == server_id) rows.push_back(serialize_memory_stick(m));
|
|
|
|
if (all || type_filter == PTYPE_MEMORY_SLOT)
|
|
for (const auto& m : inv_.memory_slots)
|
|
if (m.server_id == server_id) rows.push_back(serialize_memory_slot(m));
|
|
|
|
if (all || type_filter == PTYPE_CPU)
|
|
for (const auto& c : inv_.cpus)
|
|
if (c.server_id == server_id) rows.push_back(serialize_cpu(c));
|
|
|
|
if (all || type_filter == PTYPE_CPU_SLOT)
|
|
for (const auto& c : inv_.cpu_slots)
|
|
if (c.server_id == server_id) rows.push_back(serialize_cpu_slot(c));
|
|
|
|
if (all || type_filter == PTYPE_DISK)
|
|
for (const auto& d : inv_.disks)
|
|
if (d.server_id == server_id) rows.push_back(serialize_disk(d));
|
|
|
|
if (all || type_filter == PTYPE_NIC)
|
|
for (const auto& n : inv_.network_cards)
|
|
if (n.server_id == server_id) rows.push_back(serialize_nic(n));
|
|
|
|
return rows;
|
|
}
|
|
|
|
std::string Database::get_part_row(uint32_t part_id) const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
|
|
for (const auto& m : inv_.memory_sticks)
|
|
if (m.part_id == part_id) return serialize_memory_stick(m);
|
|
for (const auto& m : inv_.memory_slots)
|
|
if (m.part_id == part_id) return serialize_memory_slot(m);
|
|
for (const auto& c : inv_.cpus)
|
|
if (c.part_id == part_id) return serialize_cpu(c);
|
|
for (const auto& c : inv_.cpu_slots)
|
|
if (c.part_id == part_id) return serialize_cpu_slot(c);
|
|
for (const auto& d : inv_.disks)
|
|
if (d.part_id == part_id) return serialize_disk(d);
|
|
for (const auto& n : inv_.network_cards)
|
|
if (n.part_id == part_id) return serialize_nic(n);
|
|
return "";
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// get_nics_for_server
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
std::vector<NetworkCard> Database::get_nics_for_server(uint32_t server_id) const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
std::vector<NetworkCard> result;
|
|
for (const auto& n : inv_.network_cards)
|
|
if (n.server_id == server_id) result.push_back(n);
|
|
return result;
|
|
}
|