diff --git a/config/alarms.json b/config/alarms.json index 1c603b0..51b40c0 100644 --- a/config/alarms.json +++ b/config/alarms.json @@ -1,10 +1,22 @@ [ + { + "rule_id": "TeST", + "device_id": "rtu_temp_sensor_lab", + "data_point_name": "temperature", + "compare_type": "EQ", + "threshold": 5, + "level": "CRITICAL", + "debounce_seconds": 0, + "message_template": "测试用", + "clear_message_template": "测试解除", + "alarm_mqtt_topic": "alarms/rtu_test" + }, { "rule_id": "RTU_TEMP_HIGH_CRITICAL", "device_id": "rtu_temp_sensor_lab", "data_point_name": "temperature", "compare_type": "GT", - "threshold": 30.0, + "threshold": 30, "level": "CRITICAL", "debounce_seconds": 60, "alarm_mqtt_topic": "alarms/rtu_lab", @@ -16,43 +28,22 @@ "device_id": "rtu_temp_sensor_lab", "data_point_name": "temperature", "compare_type": "GT", - "threshold": 10.0, + "threshold": 10, "level": "CRITICAL", "debounce_seconds": 0, - "alarm_mqtt_topic": "alarms/rtu_lab", + "alarm_mqtt_topic": "alarms/rtu_temp_sensor_lab", "message_template": "测试告警 设备 '{device_id}' 温度 ({value}°C) 高于测试阈值 {threshold}°C!", "clear_message_template": "【测试告警解除】设备 '{device_id}' 温度 ({value}°C) 已恢复正常。" }, - { - "rule_id": "ANY_TEMP_CRITICAL", - "device_id": "*", - "data_point_name": "temperature", - "compare_type": "GT", - "threshold": 40.0, - "level": "CRITICAL", - "alarm_mqtt_topic": "alarms/critical", - "message_template": "【全局报警】任意设备 '{device_id}' 的温度 ({value}°C) 达到或超过 {threshold}°C。", - "clear_message_template": "【全局报警解除】设备 '{device_id}' 的温度 ({value}°C) 已恢复。" - }, - { - "rule_id": "SYSTEM_CPU_HIGH", - "device_id": "proxy_system", - "data_point_name": "cpu_usage", - "compare_type": "GT", - "threshold": 90.0, - "level": "WARNING", - "debounce_seconds": 30, - "message_template": "代理系统 CPU 利用率 ({value}%) 超过 {threshold}%,请检查!", - "clear_message_template": "代理系统 CPU 利用率已恢复 ({value}%)。" - }, { "rule_id": "SYSTEM_MEM_HIGH", "device_id": "proxy_system", "data_point_name": "mem_usage_percentage", "compare_type": "GT", - "threshold": 80.0, + "threshold": 80, "level": "WARNING", "debounce_seconds": 30, + "alarm_mqtt_topic": "alarms/proxy", "message_template": "代理系统内存使用率 ({value}%) 超过 {threshold}%,请检查!", "clear_message_template": "代理系统内存使用率已恢复 ({value}%)。" } diff --git a/src/alarm/alarm_service.cc b/src/alarm/alarm_service.cc index dc3a811..762e5e6 100644 --- a/src/alarm/alarm_service.cc +++ b/src/alarm/alarm_service.cc @@ -3,15 +3,14 @@ #include #include +#include #include "dataStorage/data_storage.h" -AlarmService::AlarmService(boost::asio::io_context& io, - PiperTTSInterface& tts_service, - MqttClient& mqtt_client) - : m_io_context(io), - m_tts_service(tts_service), - m_mqtt_client(mqtt_client), +AlarmService::AlarmService(boost::asio::io_context &io, + PiperTTSInterface &tts_service, + MqttClient &mqtt_client) + : m_io_context(io), m_tts_service(tts_service), m_mqtt_client(mqtt_client), m_tts_running(true) { m_tts_thread = std::thread(&AlarmService::tts_worker, this); spdlog::info("AlarmService created and TTS worker thread started."); @@ -32,10 +31,10 @@ void AlarmService::stop() { std::lock_guard lock(m_tts_queue_mutex); m_tts_running = false; } - m_tts_cv.notify_one(); // 唤醒工作线程使其退出 + m_tts_cv.notify_one(); // 唤醒工作线程使其退出 } -bool AlarmService::load_rules(const std::string& config_path) { +bool AlarmService::load_rules(const std::string &config_path) { m_rules_config_path = config_path; std::vector new_rules; @@ -88,14 +87,15 @@ bool AlarmService::reload_rules() { return true; } -void AlarmService::process_device_data(const std::string& device_id, - const std::string& data_json) { +void AlarmService::process_device_data(const std::string &device_id, + const std::string &data_json) { std::lock_guard lock(m_rules_mutex); try { json j_data = json::parse(data_json); - if (!j_data.is_object()) return; + if (!j_data.is_object()) + return; - for (auto& rule : m_rules) { + for (auto &rule : m_rules) { if (rule.device_id == device_id || rule.device_id == "*") { if (j_data.contains(rule.data_point_name)) { if (j_data[rule.data_point_name].is_number()) { @@ -105,20 +105,21 @@ void AlarmService::process_device_data(const std::string& device_id, } } } - } catch (const json::exception& e) { + } catch (const json::exception &e) { spdlog::error("Failed to parse device data for alarm: {}", e.what()); } } -void AlarmService::process_system_data(const std::string& system_data_json) { +void AlarmService::process_system_data(const std::string &system_data_json) { std::lock_guard lock(m_rules_mutex); try { json j_data = json::parse(system_data_json); - if (!j_data.is_object()) return; + if (!j_data.is_object()) + return; - const std::string device_id = "proxy_system"; // 系统数据的固定ID + const std::string device_id = "proxy_system"; // 系统数据的固定ID - for (auto& rule : m_rules) { + for (auto &rule : m_rules) { if (rule.device_id == device_id) { if (j_data.contains(rule.data_point_name)) { if (j_data[rule.data_point_name].is_number()) { @@ -128,26 +129,26 @@ void AlarmService::process_system_data(const std::string& system_data_json) { } } } - } catch (const json::exception& e) { + } catch (const json::exception &e) { spdlog::error("Failed to parse system data for alarm: {}", e.what()); } } void AlarmService::check_rule_against_value( - AlarmRule& rule, double value, const std::string& actual_device_id) { + AlarmRule &rule, double value, const std::string &actual_device_id) { bool condition_met = false; switch (rule.compare_type) { - case CompareType::GT: - condition_met = (value > rule.threshold); - break; - case CompareType::LT: - condition_met = (value < rule.threshold); - break; - case CompareType::EQ: - condition_met = (std::abs(value - rule.threshold) < 1e-9); - break; - default: - break; + case CompareType::GT: + condition_met = (value > rule.threshold); + break; + case CompareType::LT: + condition_met = (value < rule.threshold); + break; + case CompareType::EQ: + condition_met = (std::abs(value - rule.threshold) < 1e-9); + break; + default: + break; } auto it = m_alarm_states.find(rule.rule_id); @@ -163,7 +164,7 @@ void AlarmService::check_rule_against_value( it = result.first; } - AlarmState& state = it->second; + AlarmState &state = it->second; if (condition_met) { if (state.current_state == AlarmStateType::NORMAL) { @@ -181,8 +182,8 @@ void AlarmService::check_rule_against_value( state.debounce_timer.async_wait([this, rule_id, value, actual_device_id]( - const boost::system::error_code& - ec) { + const boost::system::error_code + &ec) { std::lock_guard lock(m_rules_mutex); if (ec == boost::asio::error::operation_aborted) { @@ -196,10 +197,10 @@ void AlarmService::check_rule_against_value( rule_id); return; } - AlarmState& current_state = it->second; + AlarmState ¤t_state = it->second; - AlarmRule* current_rule = nullptr; - for (auto& r : m_rules) { + AlarmRule *current_rule = nullptr; + for (auto &r : m_rules) { if (r.rule_id == rule_id) { current_rule = &r; break; @@ -226,10 +227,10 @@ void AlarmService::check_rule_against_value( } } -std::string AlarmService::format_message(const AlarmRule& rule, - const std::string& template_str, +std::string AlarmService::format_message(const AlarmRule &rule, + const std::string &template_str, double value, - const std::string& actual_device_id) { + const std::string &actual_device_id) { std::string msg = template_str; size_t pos = msg.find("{device_id}"); @@ -253,8 +254,8 @@ std::string AlarmService::format_message(const AlarmRule& rule, return msg; } -void AlarmService::trigger_alarm_action(AlarmRule& rule, double value, - const std::string& actual_device_id) { +void AlarmService::trigger_alarm_action(AlarmRule &rule, double value, + const std::string &actual_device_id) { std::string message = format_message(rule, rule.message_template, value, actual_device_id); spdlog::warn("[ALARM ACTIVE] (Rule: {}) {}", rule.rule_id, message); @@ -285,13 +286,13 @@ void AlarmService::trigger_alarm_action(AlarmRule& rule, double value, } } -void AlarmService::clear_alarm(AlarmRule& rule, double value, - const std::string& actual_device_id) { +void AlarmService::clear_alarm(AlarmRule &rule, double value, + const std::string &actual_device_id) { spdlog::info("[ALARM CLEARED] (Rule: {})", rule.rule_id); // 检查是否有解除消息模板 if (rule.clear_message_template.empty()) { - return; // 没有模板,安静地解除 + return; // 没有模板,安静地解除 } std::string message = format_message(rule, rule.clear_message_template, value, @@ -335,14 +336,14 @@ void AlarmService::tts_worker() { [this] { return !m_tts_queue.empty() || !m_tts_running; }); if (!m_tts_running && m_tts_queue.empty()) { - break; // 退出 + break; // 退出 } if (!m_tts_queue.empty()) { message_to_play = m_tts_queue.front(); m_tts_queue.pop(); } - } // 释放锁 + } // 释放锁 if (!message_to_play.empty()) { spdlog::debug("TTS worker playing: {}", message_to_play); @@ -353,8 +354,9 @@ void AlarmService::tts_worker() { spdlog::info("TTS worker thread stopped."); } -void AlarmService::schedule_tts(const std::string& text) { - if (text.empty() || !m_tts_running) return; +void AlarmService::schedule_tts(const std::string &text) { + if (text.empty() || !m_tts_running) + return; { std::lock_guard lock(m_tts_queue_mutex); m_tts_queue.push(text); @@ -365,7 +367,7 @@ void AlarmService::schedule_tts(const std::string& text) { nlohmann::json AlarmService::getActiveAlarmsJson() { auto alarms = DataStorage::getInstance().getActiveAlarms(); json j_alarms = json::array(); - for (const auto& alarm : alarms) { + for (const auto &alarm : alarms) { j_alarms.push_back(AlarmEvent::toJson(alarm)); } return j_alarms; @@ -374,29 +376,94 @@ nlohmann::json AlarmService::getActiveAlarmsJson() { nlohmann::json AlarmService::getAlarmHistoryJson(int limit) { auto alarms = DataStorage::getInstance().getAlarmHistory(limit); json j_alarms = json::array(); - for (const auto& alarm : alarms) { + for (const auto &alarm : alarms) { j_alarms.push_back(AlarmEvent::toJson(alarm)); } return j_alarms; } -bool AlarmService::parse_rules_from_file( - const std::string& config_path, std::vector& out_rules, - std::map& out_states) { - std::ifstream ifs(config_path); - if (!ifs.is_open()) { - spdlog::error("Failed to open alarm rules file: {}", config_path); +bool AlarmService::manually_clear_alarm(const std::string &rule_id, + const std::string &device_id) { + auto it = m_alarm_states.find(rule_id); + if (it == m_alarm_states.end()) { + spdlog::warn( + "Cannot manually clear alarm: Rule '{}' not found in state map.", + rule_id); return false; } - try { - json j_rules = json::parse(ifs); + AlarmState &state = it->second; + state.current_state = AlarmStateType::NORMAL; + state.debounce_timer.cancel(); + spdlog::info("[ALARM MANUALLY CLEARED] (Rule: {}) for Device: {}", rule_id, + device_id); + + // 2. 写入数据库日志 + AlarmEvent event; + event.rule_id = rule_id; + event.device_id = device_id; + event.status = AlarmEventStatus::CLEARED; + event.message = "Alarm manually cleared by user."; + event.trigger_value = 0.0; + event.timestamp_ms = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + for (const auto &rule : m_rules) { + if (rule.rule_id == rule_id) { + event.level = AlarmRule::levelToString(rule.level); + break; + } + } + if (event.level.empty()) { + event.level = "INFO"; + } + + if (!DataStorage::getInstance().storeAlarmEvent(event)) { + spdlog::error("Failed to store MANUALLY CLEARED alarm event for rule: {}", + rule_id); + return false; + } + + return true; +} + +std::string AlarmService::get_rules_as_json_string() { + std::lock_guard lock(m_rules_mutex); + + if (m_rules_config_path.empty()) { + spdlog::error("Cannot get rules: config path not set."); + return "[]"; + } + + std::ifstream ifs(m_rules_config_path); + if (!ifs.is_open()) { + spdlog::error("Failed to open alarm rules file for reading: {}", + m_rules_config_path); + return "[]"; + } + + std::stringstream buffer; + buffer << ifs.rdbuf(); + return buffer.str(); +} + +bool AlarmService::parse_rules_from_content( + const std::string &json_content, std::vector &out_rules, + std::map &out_states) { + + try { + json j_rules = json::parse(json_content); + + if (!j_rules.is_array()) { + spdlog::error("Failed to parse rules: JSON content is not an array."); + return false; + } - // 清空临时容器,准备接收新规则 out_rules.clear(); out_states.clear(); - for (const auto& j_rule : j_rules) { + for (const auto &j_rule : j_rules) { AlarmRule rule; rule.rule_id = j_rule.at("rule_id"); rule.device_id = j_rule.at("device_id"); @@ -417,64 +484,65 @@ bool AlarmService::parse_rules_from_file( out_rules.push_back(std::move(rule)); - // 为每条规则准备一个状态机 out_states.emplace(std::piecewise_construct, std::forward_as_tuple(rule.rule_id), - std::forward_as_tuple( - m_io_context) // m_io_context 是类成员,可以访问 - ); + std::forward_as_tuple(m_io_context)); } - } catch (const json::exception& e) { - spdlog::error("Failed to parse alarm rules file '{}': {}", config_path, - e.what()); + } catch (const json::exception &e) { + spdlog::error("Failed to parse alarm rules content: {}", e.what()); return false; } return true; } -bool AlarmService::manually_clear_alarm(const std::string& rule_id, - const std::string& device_id) { - auto it = m_alarm_states.find(rule_id); - if (it == m_alarm_states.end()) { - spdlog::warn( - "Cannot manually clear alarm: Rule '{}' not found in state map.", - rule_id); +bool AlarmService::parse_rules_from_file( + const std::string &config_path, std::vector &out_rules, + std::map &out_states) { + + std::ifstream ifs(config_path); + if (!ifs.is_open()) { + spdlog::error("Failed to open alarm rules file: {}", config_path); return false; } - AlarmState& state = it->second; - state.current_state = AlarmStateType::NORMAL; - state.debounce_timer.cancel(); // 确保计时器被取消 - spdlog::info("[ALARM MANUALLY CLEARED] (Rule: {}) for Device: {}", rule_id, - device_id); + std::stringstream buffer; + buffer << ifs.rdbuf(); + std::string file_content = buffer.str(); - // 2. 写入数据库日志 - AlarmEvent event; - event.rule_id = rule_id; - event.device_id = device_id; - event.status = AlarmEventStatus::CLEARED; // 标记为 CLEARED - event.message = "Alarm manually cleared by user."; - event.trigger_value = 0.0; - event.timestamp_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + return parse_rules_from_content(file_content, out_rules, out_states); +} - for (const auto& rule : m_rules) { - if (rule.rule_id == rule_id) { - event.level = AlarmRule::levelToString(rule.level); - break; - } - } - if (event.level.empty()) { - event.level = "INFO"; - } +bool AlarmService::save_rules_from_json_string( + const std::string &json_content) { - if (!DataStorage::getInstance().storeAlarmEvent(event)) { - spdlog::error("Failed to store MANUALLY CLEARED alarm event for rule: {}", - rule_id); + std::vector dummy_rules; + std::map dummy_states; + + if (!parse_rules_from_content(json_content, dummy_rules, dummy_states)) { + spdlog::warn("Failed to save rules: Invalid content or schema."); return false; } + std::lock_guard lock(m_rules_mutex); + + if (m_rules_config_path.empty()) { + spdlog::error("Cannot save rules: config path not set."); + return false; + } + + std::ofstream ofs(m_rules_config_path, std::ios::trunc); + if (!ofs.is_open()) { + spdlog::error("Failed to open alarm rules file for writing: {}", + m_rules_config_path); + return false; + } + + ofs << json_content; + ofs.close(); + + spdlog::info( + "Successfully saved new alarm rules to {}. (Reload is required to apply)", + m_rules_config_path); return true; } \ No newline at end of file diff --git a/src/alarm/alarm_service.h b/src/alarm/alarm_service.h index 45709d1..838950b 100644 --- a/src/alarm/alarm_service.h +++ b/src/alarm/alarm_service.h @@ -20,53 +20,70 @@ using json = nlohmann::json; class AlarmService { - public: - AlarmService(boost::asio::io_context& io, PiperTTSInterface& tts_service, - MqttClient& mqtt_client); +public: + AlarmService(boost::asio::io_context &io, PiperTTSInterface &tts_service, + MqttClient &mqtt_client); ~AlarmService(); void stop(); - bool load_rules(const std::string& config_path); + bool load_rules(const std::string &config_path); bool reload_rules(); - void process_device_data(const std::string& device_id, - const std::string& data_json); + void process_device_data(const std::string &device_id, + const std::string &data_json); - void process_system_data(const std::string& system_data_json); + void process_system_data(const std::string &system_data_json); nlohmann::json getActiveAlarmsJson(); nlohmann::json getAlarmHistoryJson(int limit); /** * @brief [新增] 手动清除一个当前激活的告警 - * @param rule_id 要清除的规则ID + * @param rule_id 要清除的规则IDparse_rules_from_fil * @param device_id 触发该规则的设备ID * @return 成功则返回 true */ - bool manually_clear_alarm(const std::string& rule_id, - const std::string& device_id); + bool manually_clear_alarm(const std::string &rule_id, + const std::string &device_id); - private: - void check_rule_against_value(AlarmRule& rule, double value, - const std::string& actual_device_id); - std::string format_message(const AlarmRule& rule, - const std::string& template_str, double value, - const std::string& actual_device_id); - void trigger_alarm_action(AlarmRule& rule, double value, - const std::string& actual_device_id); - void clear_alarm(AlarmRule& rule, double value, - const std::string& actual_device_id); + std::string get_rules_as_json_string(); + + /** + * @brief [新增] 从 JSON 字符串内容中解析和校验规则 + * 这是所有解析器的核心 + */ + bool parse_rules_from_content(const std::string &json_content, + std::vector &out_rules, + std::map &out_states); + + /** + * @brief [新增] 验证并保存告警规则到文件 + * @param json_content 包含新规则的完整JSON字符串 + * @return 成功 (JSON有效且写入成功) 则返回 true + */ + bool save_rules_from_json_string(const std::string &json_content); + +private: + void check_rule_against_value(AlarmRule &rule, double value, + const std::string &actual_device_id); + std::string format_message(const AlarmRule &rule, + const std::string &template_str, double value, + const std::string &actual_device_id); + void trigger_alarm_action(AlarmRule &rule, double value, + const std::string &actual_device_id); + void clear_alarm(AlarmRule &rule, double value, + const std::string &actual_device_id); void tts_worker(); - void schedule_tts(const std::string& text); - bool parse_rules_from_file(const std::string& config_path, - std::vector& out_rules, - std::map& out_states); + void schedule_tts(const std::string &text); + bool parse_rules_from_file(const std::string &config_path, + std::vector &out_rules, + std::map &out_states); - boost::asio::io_context& m_io_context; - PiperTTSInterface& m_tts_service; - MqttClient& m_mqtt_client; + boost::asio::io_context &m_io_context; + PiperTTSInterface &m_tts_service; + MqttClient &m_mqtt_client; std::vector m_rules; std::map m_alarm_states; diff --git a/src/web/web_server.cc b/src/web/web_server.cc index cd19586..8d5bff2 100644 --- a/src/web/web_server.cc +++ b/src/web/web_server.cc @@ -7,16 +7,13 @@ #include "config/config_manager.h" #include "spdlog/spdlog.h" -WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, - DeviceManager& deviceManager, LiveDataCache& liveDataCache, - AlarmService& alarm_service, uint16_t port) - : crow::Crow(), - m_monitor(monitor), - m_device_manager(deviceManager), - m_live_data_cache(liveDataCache), - m_alarm_service(alarm_service), - m_port(port) { - auto& cors = this->get_middleware(); +WebServer::WebServer(SystemMonitor::SystemMonitor &monitor, + DeviceManager &deviceManager, LiveDataCache &liveDataCache, + AlarmService &alarm_service, uint16_t port) + : crow::Crow(), m_monitor(monitor), + m_device_manager(deviceManager), m_live_data_cache(liveDataCache), + m_alarm_service(alarm_service), m_port(port) { + auto &cors = this->get_middleware(); cors.global() .origin("*") .headers("Content-Type", "Authorization") @@ -77,7 +74,7 @@ void WebServer::setup_routes() { auto devices_info = m_device_manager.get_all_device_info(); std::vector devices_json; - for (const auto& info : devices_info) { + for (const auto &info : devices_info) { crow::json::wvalue device_obj; device_obj["id"] = info.id; @@ -85,7 +82,7 @@ void WebServer::setup_routes() { device_obj["is_running"] = info.is_running; crow::json::wvalue details_obj; - for (const auto& pair : info.connection_details) { + for (const auto &pair : info.connection_details) { details_obj[pair.first] = pair.second; } device_obj["connection_details"] = std::move(details_obj); @@ -100,7 +97,7 @@ void WebServer::setup_routes() { auto latest_data_map = m_live_data_cache.get_all_data(); crow::json::wvalue response; - for (const auto& pair : latest_data_map) { + for (const auto &pair : latest_data_map) { response[pair.first] = crow::json::load(pair.second); } @@ -115,7 +112,7 @@ void WebServer::setup_routes() { res.set_header("Content-Type", "application/json"); return res; - } catch (const std::exception& e) { + } catch (const std::exception &e) { spdlog::error("Error processing /api/alarms/active: {}", e.what()); crow::json::wvalue error_resp; error_resp["error"] = "Failed to retrieve active alarms."; @@ -127,15 +124,16 @@ void WebServer::setup_routes() { }); CROW_ROUTE((*this), "/api/alarms/history") - .methods("GET"_method)([this](const crow::request& req) { + .methods("GET"_method)([this](const crow::request &req) { int limit = 100; if (req.url_params.get("limit")) { try { limit = std::stoi(req.url_params.get("limit")); - } catch (const std::exception&) { /* ignore invalid */ + } catch (const std::exception &) { /* ignore invalid */ } } - if (limit <= 0) limit = 100; + if (limit <= 0) + limit = 100; try { auto json_string = m_alarm_service.getAlarmHistoryJson(limit).dump(); @@ -144,7 +142,7 @@ void WebServer::setup_routes() { res.set_header("Content-Type", "application/json"); return res; - } catch (const std::exception& e) { + } catch (const std::exception &e) { spdlog::error("Error processing /api/alarms/history: {}", e.what()); crow::json::wvalue error_resp; error_resp["error"] = "Failed to retrieve alarm history."; @@ -181,11 +179,11 @@ void WebServer::setup_routes() { }); CROW_ROUTE((*this), "/api/alarms/clear") - .methods("POST"_method)([this](const crow::request& req) { + .methods("POST"_method)([this](const crow::request &req) { crow::json::rvalue j_body; try { j_body = crow::json::load(req.body); - } catch (const std::exception& e) { + } catch (const std::exception &e) { spdlog::warn("Failed to parse request body for /api/alarms/clear: {}", e.what()); auto res = crow::response(400, "{\"error\":\"Invalid JSON body.\"}"); @@ -211,10 +209,45 @@ void WebServer::setup_routes() { res.set_header("Content-Type", "application/json"); return res; } else { - auto res = - crow::response(404, - "{\"status\":\"error\", \"message\":\"Alarm not " - "found or not active.\"}"); + auto res = crow::response( + 404, "{\"status\":\"error\", \"message\":\"Alarm not " + "found or not active.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + CROW_ROUTE((*this), "/api/alarms/config").methods("GET"_method)([this]() { + std::string rules_json_string = m_alarm_service.get_rules_as_json_string(); + auto res = crow::response(200, rules_json_string); + res.set_header("Content-Type", "application/json"); + return res; + }); + + CROW_ROUTE((*this), "/api/alarms/config") + .methods("POST"_method)([this](const crow::request &req) { + const std::string &new_rules_content = req.body; + + bool success = + m_alarm_service.save_rules_from_json_string(new_rules_content); + + if (success) { + crow::json::wvalue response_json; + response_json["status"] = "success"; + response_json["message"] = + "Rules saved successfully. A reload is required to apply."; + + auto res = crow::response(200, response_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + + } else { + crow::json::wvalue error_json; + error_json["status"] = "error"; + error_json["message"] = + "Failed to save rules. Invalid JSON format or server error."; + + auto res = crow::response(400, error_json.dump()); res.set_header("Content-Type", "application/json"); return res; }