From 39966da9203d5facbc5e1466b8a76541d7284d87 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Mon, 10 Aug 2026 00:08:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=93=E6=89=91=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.json | 19 +- config/linux/config.json | 19 +- module/Local_Server/Data_Feed/Data_Feed.cpp | 11 + module/Local_Server/Data_Feed/Data_Feed.h | 5 + .../Data_Source/Data_Source_Handler.cpp | 54 +-- .../Local_Server/server/Adminive_Config.cpp | 400 +++++++++--------- module/Local_Server/server/Config.cpp | 107 +++++ module/Local_Server/server/Config.h | 57 ++- .../server/Source_Feed_Relation.cpp | 99 ----- .../server/Source_Feed_Relation.h | 52 --- 10 files changed, 387 insertions(+), 436 deletions(-) delete mode 100644 module/Local_Server/server/Source_Feed_Relation.cpp delete mode 100644 module/Local_Server/server/Source_Feed_Relation.h diff --git a/config/config.json b/config/config.json index 39f76e5..a5839d9 100644 --- a/config/config.json +++ b/config/config.json @@ -451,6 +451,7 @@ "type": "Data_Feed_TCP_Server", "key": "Port_10003", "enable": true, + "source_key": "lzy_dll", "output_format": { "type": "BIN", "use_status": true, @@ -466,6 +467,7 @@ "type": "Data_Feed_UDP_Server", "key": "22333", "enable": false, + "source_key": "lzy_dll", "output_format": { "type": "BIN", "use_status": false, @@ -479,6 +481,7 @@ "type": "Data_Feed_UDP_Client", "key": "Port_10004", "enable": false, + "source_key": "lzy_dll", "output_format": { "type": "AVR", "use_status": false, @@ -493,6 +496,7 @@ "type": "Data_Feed_TCP_Client", "key": "Port_10005", "enable": false, + "source_key": "lzy_dll", "output_format": { "type": "BIN", "use_status": true, @@ -507,6 +511,7 @@ "type": "Data_Feed_TCP_Server", "key": "Port_30003", "enable": false, + "source_key": "lzy_dll", "output_format": { "type": "SBS", "use_status": false, @@ -623,14 +628,12 @@ } ] }, - "source_feed_relation": { - "list": [ - { - "key": "key_10003", - "enable": true, - "type": "First_Source_To_All_Feed_Relation" - } - ] + "data_topology_view": { + "initialized": false, + "zoom": 1.0, + "pan_x": 0.0, + "pan_y": 0.0, + "node_positions": {} } }, "log": { diff --git a/config/linux/config.json b/config/linux/config.json index 995ce05..1b5e401 100644 --- a/config/linux/config.json +++ b/config/linux/config.json @@ -451,6 +451,7 @@ "type": "Data_Feed_TCP_Server", "key": "Port_10003", "enable": true, + "source_key": "lzy_dll", "output_format": { "type": "BIN", "use_status": true, @@ -466,6 +467,7 @@ "type": "Data_Feed_UDP_Server", "key": "22333", "enable": false, + "source_key": "lzy_dll", "output_format": { "type": "BIN", "use_status": false, @@ -479,6 +481,7 @@ "type": "Data_Feed_UDP_Client", "key": "Port_10004", "enable": false, + "source_key": "lzy_dll", "output_format": { "type": "AVR", "use_status": false, @@ -493,6 +496,7 @@ "type": "Data_Feed_TCP_Client", "key": "Port_10005", "enable": false, + "source_key": "lzy_dll", "output_format": { "type": "BIN", "use_status": true, @@ -507,6 +511,7 @@ "type": "Data_Feed_TCP_Server", "key": "Port_30003", "enable": false, + "source_key": "lzy_dll", "output_format": { "type": "SBS", "use_status": false, @@ -574,14 +579,12 @@ } ] }, - "source_feed_relation": { - "list": [ - { - "key": "key_10003", - "enable": true, - "type": "First_Source_To_All_Feed_Relation" - } - ] + "data_topology_view": { + "initialized": false, + "zoom": 1.0, + "pan_x": 0.0, + "pan_y": 0.0, + "node_positions": {} } }, "log": { diff --git a/module/Local_Server/Data_Feed/Data_Feed.cpp b/module/Local_Server/Data_Feed/Data_Feed.cpp index 94f973e..d526a7c 100644 --- a/module/Local_Server/Data_Feed/Data_Feed.cpp +++ b/module/Local_Server/Data_Feed/Data_Feed.cpp @@ -10,6 +10,17 @@ bool Data_Feed::registered() { } return false; } +void Data_feed_Config::set_connections_need_refresh() { + for (const auto& source : Global::instance()->mode_acs.data_source_config.map.list()) + source->need_refresh_data_feed_key_list = true; +} +bool Data_feed_Config::source_has_connection(std::string_view source_key) const { + for (const auto& feed : map.list()) { + if (feed->source_key == source_key) + return true; + } + return false; +} asio::awaitable Data_Feed_UDP_Server::handle_in_loop_coro() { co_await svr.tick_coro(); co_return; diff --git a/module/Local_Server/Data_Feed/Data_Feed.h b/module/Local_Server/Data_Feed/Data_Feed.h index 08ba6e7..8ad0e43 100644 --- a/module/Local_Server/Data_Feed/Data_Feed.h +++ b/module/Local_Server/Data_Feed/Data_Feed.h @@ -29,6 +29,7 @@ struct Type_Descriptor { } class Data_Feed : public With_Loop_Coro { public: + std::string source_key; BIN_Msg_Buffer msg_buffer{}; adminive::Managed_Value output_format; Frequency_Limit_Multi sbs_flm{}; @@ -44,11 +45,13 @@ public: } virtual Psc::JSON to_json() { Psc::JSON ret = With_Loop_Coro::to_base_json(); + ret.append(Psc::JSON("source_key", source_key)); ret.append(Psc::JSON("output_format", output_format.read([](const auto& value) { return value.to_base_json(); }))); return ret; } virtual void from_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { With_Loop_Coro::from_base_json(that_json, not_exist_use_default_value); + source_key = that_json->get("source_key") == nullptr ? std::string{} : that_json->get_string("source_key"); output_format.write([&](auto& value) { value.from_base_json(that_json->get("output_format"), not_exist_use_default_value); }); @@ -392,5 +395,7 @@ public: ret.append({"list", list()}); return ret; } + static void set_connections_need_refresh(); + [[nodiscard]] bool source_has_connection(std::string_view source_key) const; void server(Global* g); }; diff --git a/module/Local_Server/Data_Source/Data_Source_Handler.cpp b/module/Local_Server/Data_Source/Data_Source_Handler.cpp index 2ced602..1b89563 100644 --- a/module/Local_Server/Data_Source/Data_Source_Handler.cpp +++ b/module/Local_Server/Data_Source/Data_Source_Handler.cpp @@ -269,58 +269,10 @@ void Data_Source_Handler::handle_HULC(std::string_view packet) { } } void Data_Source_Handler::refresh_data_feed_key_list() { - auto* global = Global::instance(); - std::map assignment; - const auto sources = global->mode_acs.data_source_config.map.list(); - const auto feeds = global->mode_acs.data_feed_config.map.list(); - const auto relations = global->mode_acs.source_feed_relation_config.map.list(); - for (const auto& relation : relations) { - relation->settings.read([&](const auto& config) { - if (!config.enable || relation->type != "One_to_One_Relation") - return; - const auto source = global->mode_acs.data_source_config.map.get(config.source_key); - const auto feed = global->mode_acs.data_feed_config.map.get(config.feed_key); - if (source.has_value() && feed.has_value() && source.value()->enabled() && feed.value()->enabled() && !assignment.contains(config.feed_key)) - assignment.emplace(config.feed_key, config.source_key); - }); - } - for (const auto& relation : relations) { - relation->settings.read([&](const auto& config) { - if (!config.enable || relation->type != "Name_One_To_Many_Relation") - return; - const auto source = global->mode_acs.data_source_config.map.get(config.source_key); - if (!source.has_value() || !source.value()->enabled()) - return; - const std::string& prefix = config.feed_name.empty() ? config.source_key : config.feed_name; - for (const auto& feed : feeds) { - if (!feed->enabled() || assignment.contains(feed->key) || !feed->key.starts_with(prefix)) - continue; - assignment.emplace(feed->key, config.source_key); - } - }); - } - bool use_default = false; - for (const auto& relation : relations) { - if (relation->type == "First_Source_To_All_Feed_Relation" && relation->settings.member<&Source_Feed_Relation_Data::enable>().read([](const auto& value) { return value; })) { - use_default = true; - break; - } - } - if (use_default) { - auto first = std::find_if(sources.begin(), sources.end(), [](const auto& source) { - return source->enabled(); - }); - if (first != sources.end()) { - for (const auto& feed : feeds) { - if (feed->enabled() && !assignment.contains(feed->key)) - assignment.emplace(feed->key, (*first)->key); - } - } - } std::vector next; - for (const auto& [feed_key, source_key] : assignment) { - if (source_key == key) - next.push_back({feed_key}); + for (const auto& feed : Global::instance()->mode_acs.data_feed_config.map.list()) { + if (feed->enabled() && feed->source_key == key) + next.push_back({feed->key}); } std::lock_guard lock(cdf_mtx); for (auto& item : next) { diff --git a/module/Local_Server/server/Adminive_Config.cpp b/module/Local_Server/server/Adminive_Config.cpp index b3b7790..355201e 100644 --- a/module/Local_Server/server/Adminive_Config.cpp +++ b/module/Local_Server/server/Adminive_Config.cpp @@ -6,6 +6,7 @@ #include "adminive/adapters/nlohmann_json.hpp" #include #include +#include #include #include #include @@ -28,11 +29,6 @@ enum class Data_Feed_Type { Data_Feed_TCP_Client, Data_Feed_UDP_Client }; -enum class Source_Feed_Relation_Type { - One_to_One_Relation, - Name_One_To_Many_Relation, - First_Source_To_All_Feed_Relation -}; struct Data_Source_Row { std::string key; Data_Source_Type type = Data_Source_Type::TCP_Client_Data_Source; @@ -56,6 +52,7 @@ struct Data_Feed_Row { std::string key; Data_Feed_Type type = Data_Feed_Type::Data_Feed_TCP_Server; bool enable{}; + std::string source_key; Output_Format output_format; Output_Data_Format format = Output_Data_Format::BIN; std::uint16_t port{}; @@ -64,14 +61,6 @@ struct Data_Feed_Row { std::size_t connect_system_buffer_size = 409600; std::string state; }; -struct Source_Feed_Relation_Row { - std::string key; - Source_Feed_Relation_Type type = Source_Feed_Relation_Type::One_to_One_Relation; - bool enable{}; - std::string source_key; - std::string feed_key; - std::string feed_name; -}; struct External_Resource_Row { std::string name; std::string description; @@ -145,7 +134,7 @@ struct Type_View_Descriptor { .create_title("添加数据源") .edit_title("编辑") .delete_title("删除") - .delete_confirmation("确定删除该数据源?存在关系依赖时后端会拒绝删除。") + .delete_confirmation("确定删除该数据源?仍有数据馈送连接时后端会拒绝删除。") .reorderable() .create_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>()), use<&T::config>(), grid(2, use<&T::ip>(), use<&T::port>(), use<&T::port_name>(), use<&T::baud_rate>(), use<&T::file_path>(), use<&T::data_type>(), use<&T::play_mode>(), use<&T::library_path>(), use<&T::function_name>(), use<&T::buffer_size>(), use<&T::shared_memory_name>(), use<&T::shared_memory_size>()))) .edit_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>(), use<&T::state>()), use<&T::config>(), grid(2, use<&T::ip>(), use<&T::port>(), use<&T::port_name>(), use<&T::baud_rate>(), use<&T::file_path>(), use<&T::data_type>(), use<&T::play_mode>(), use<&T::library_path>(), use<&T::function_name>(), use<&T::buffer_size>(), use<&T::shared_memory_name>(), use<&T::shared_memory_size>()))); @@ -165,6 +154,7 @@ struct Type_Descriptor { ADMINIVE_FIELD_LABEL(T, key, "名称").creatable().required().text_input(), type, ADMINIVE_FIELD_LABEL(T, enable, "启用").creatable().editable().boolean_input(), + ADMINIVE_FIELD_LABEL(T, source_key, "连接的数据源").creatable().editable().select_input(), ADMINIVE_FIELD_LABEL(T, output_format, "输出格式").creatable().editable(), ADMINIVE_FIELD_LABEL(T, format, "数据格式").select_input(), ADMINIVE_FIELD_LABEL(T, port, "端口").creatable().editable().number_input(), @@ -178,50 +168,16 @@ template <> struct Type_View_Descriptor { static Table_View table() { using T = ecap::adminive_config::Data_Feed_Row; - return table_view(column<&T::key>("名称").search(), column<&T::type>("类型").filter(), column<&T::format>("输出格式").filter(), column<&T::port>("端口").sort(), column<&T::enable>("启用").filter(), column<&T::state>("状态")) + return table_view(column<&T::key>("名称").search(), column<&T::type>("类型").filter(), column<&T::source_key>("连接的数据源").filter(), column<&T::format>("输出格式").filter(), column<&T::port>("端口").sort(), column<&T::enable>("启用").filter(), column<&T::state>("状态")) .titled("数据馈送") .id_title("序号") .create_title("添加数据馈送") .edit_title("编辑") .delete_title("删除") - .delete_confirmation("确定删除该数据馈送?存在关系依赖时后端会拒绝删除。") + .delete_confirmation("确定删除该数据馈送?对应拓扑连接会一并删除。") .reorderable() - .create_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>()), use<&T::output_format>(), grid(2, use<&T::url>(), use<&T::port>(), use<&T::connect_user_buffer_size>(), use<&T::connect_system_buffer_size>()))) - .edit_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>(), use<&T::state>()), use<&T::output_format>(), grid(2, use<&T::url>(), use<&T::port>(), use<&T::connect_user_buffer_size>(), use<&T::connect_system_buffer_size>()))); - } -}; -template <> -struct Type_Descriptor { - static auto get() { - using T = ecap::adminive_config::Source_Feed_Relation_Row; - using E = ecap::adminive_config::Source_Feed_Relation_Type; - auto type = ADMINIVE_FIELD_LABEL(T, type, "关系类型").creatable().required().select_input(); - type = type.enum_label("一对一") - .enum_label("按名称一对多") - .enum_label("默认:首个数据源到未匹配馈送"); - return object("source_feed_relation", "数据源-数据馈送关系", - ADMINIVE_FIELD_LABEL(T, key, "关系名称").creatable().required().text_input(), - type, - ADMINIVE_FIELD_LABEL(T, enable, "启用").creatable().editable().boolean_input(), - ADMINIVE_FIELD_LABEL(T, source_key, "数据源名称").creatable().editable().text_input().visible_on("${$self.type == 'One_to_One_Relation' || $self.type == 'Name_One_To_Many_Relation'}"), - ADMINIVE_FIELD_LABEL(T, feed_key, "数据馈送名称").creatable().editable().text_input().visible_on("${$self.type == 'One_to_One_Relation'}"), - ADMINIVE_FIELD_LABEL(T, feed_name, "数据馈送名称前缀").description("按名称扫描全部数据馈送;为空时使用数据源名称作为前缀。默认关系只接管前面规则没有匹配到的数据馈送。").creatable().editable().text_input().visible_on("${$self.type == 'Name_One_To_Many_Relation'}")); - } -}; -template <> -struct Type_View_Descriptor { - static Table_View table() { - using T = ecap::adminive_config::Source_Feed_Relation_Row; - return table_view(column<&T::key>("关系名称").search(), column<&T::type>("类型").filter(), column<&T::source_key>("数据源"), column<&T::feed_key>("数据馈送"), column<&T::feed_name>("名称前缀"), column<&T::enable>("启用").filter()) - .titled("数据源-数据馈送关系") - .id_title("序号") - .create_title("添加关系") - .edit_title("编辑") - .delete_title("删除") - .delete_confirmation("确定删除该关系?") - .reorderable() - .create_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>()), grid(2, use<&T::source_key>(), use<&T::feed_key>(), use<&T::feed_name>()))) - .edit_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>()), grid(2, use<&T::source_key>(), use<&T::feed_key>(), use<&T::feed_name>()))); + .create_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>(), use<&T::source_key>()), use<&T::output_format>(), grid(2, use<&T::url>(), use<&T::port>(), use<&T::connect_user_buffer_size>(), use<&T::connect_system_buffer_size>()))) + .edit_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>(), use<&T::source_key>(), use<&T::state>()), use<&T::output_format>(), grid(2, use<&T::url>(), use<&T::port>(), use<&T::connect_user_buffer_size>(), use<&T::connect_system_buffer_size>()))); } }; template <> @@ -264,6 +220,7 @@ struct Type_View_Descriptor { namespace ecap::adminive_config { namespace { using Json = nlohmann::json; +const std::string data_topology_path = "/api/adminive/config/data_topology"; class Conflict_Error final : public std::runtime_error { public: using std::runtime_error::runtime_error; @@ -330,21 +287,10 @@ adminive::Resource_Transaction map_view_transaction(Global* glo return transaction; } void save_data_sources(Global* global) { - auto patch = Psc::JSON::object(); - patch.append({"data_source", global->mode_acs.data_source_config.to_json()}); - Config::merge(Config_Section::mode_acs, patch); + Config::save(Config_Section::mode_acs, global->mode_acs.to_base_json()); } void save_data_feeds(Global* global) { - auto feed = Psc::JSON::object(); - feed.append({"list", global->mode_acs.data_feed_config.list()}); - auto patch = Psc::JSON::object(); - patch.append({"data_feed", feed}); - Config::merge(Config_Section::mode_acs, patch); -} -void save_source_feed_relations(Global* global) { - auto patch = Psc::JSON::object(); - patch.append({"source_feed_relation", global->mode_acs.source_feed_relation_config.to_json()}); - Config::merge(Config_Section::mode_acs, patch); + Config::save(Config_Section::mode_acs, global->mode_acs.to_base_json()); } template adminive::Http_Response business_response(Function&& function) noexcept { @@ -373,6 +319,48 @@ Json read_request_body(std::string_view body) { throw Bad_Request_Error(error.what()); } } +Data_Topology_View_Config read_topology_view(Json input, Global* global) { + if(input.value("protocol", std::string{}) != "ecap.data-topology-view" || input.value("protocol_version", 0) != 1) + throw std::invalid_argument("数据拓扑视图协议或版本不受支持"); + const auto finite_number = [&input](std::string_view name) { + const auto field = input.find(std::string(name)); + if(field == input.end() || !field->is_number()) + throw std::invalid_argument("数据拓扑视图缺少数值字段: " + std::string(name)); + const auto value = field->get(); + if(!std::isfinite(value)) + throw std::invalid_argument("数据拓扑视图字段不是有限数值: " + std::string(name)); + return value; + }; + Data_Topology_View_Config result; + result.initialized = true; + result.zoom = finite_number("zoom"); + result.pan_x = finite_number("pan_x"); + result.pan_y = finite_number("pan_y"); + if(result.zoom <= 0.0 || result.zoom > 100.0) + throw std::invalid_argument("数据拓扑视图参数无效"); + std::map valid_nodes; + for(const auto& source : global->mode_acs.data_source_config.map.list()) + valid_nodes.emplace("data_source:" + source->key, true); + for(const auto& feed : global->mode_acs.data_feed_config.map.list()) + valid_nodes.emplace("data_feed:" + feed->key, true); + const auto positions = input.find("node_positions"); + if(positions == input.end() || !positions->is_object()) + throw std::invalid_argument("节点位置必须是对象"); + for(const auto& [id, position] : positions->items()) { + if(!valid_nodes.contains(id) || !position.is_object()) + continue; + const auto x_field = position.find("x"); + const auto y_field = position.find("y"); + if(x_field == position.end() || y_field == position.end() || !x_field->is_number() || !y_field->is_number()) + throw std::invalid_argument("节点位置缺少数值坐标: " + id); + const auto x = x_field->get(); + const auto y = y_field->get(); + if(!std::isfinite(x) || !std::isfinite(y)) + throw std::invalid_argument("节点位置不是有限数值: " + id); + result.node_positions.emplace(id, Data_Topology_Node_Position{x, y}); + } + return result; +} template void complete_business_request(std::function& callback, Function&& function) noexcept { callback(adminive::make_drogon_response(business_response(std::forward(function)))); @@ -444,9 +432,6 @@ std::string source_type_name(Data_Source_Type value) { std::string feed_type_name(Data_Feed_Type value) { return std::string(magic_enum::enum_name(value)); } -std::string relation_type_name(Source_Feed_Relation_Type value) { - return std::string(magic_enum::enum_name(value)); -} Data_Source_Type source_type(std::string_view value) { const auto result = magic_enum::enum_cast(value); if(!result) @@ -459,12 +444,6 @@ Data_Feed_Type feed_type(std::string_view value) { throw std::invalid_argument("未知数据馈送类型: " + std::string(value)); return *result; } -Source_Feed_Relation_Type relation_type(std::string_view value) { - const auto result = magic_enum::enum_cast(value); - if(!result) - throw std::invalid_argument("未知关系类型: " + std::string(value)); - return *result; -} Data_Source_Row source_row(const std::shared_ptr& source) { Data_Source_Row row; row.key = source->key; @@ -548,6 +527,7 @@ Data_Feed_Row feed_row(const std::shared_ptr& feed) { row.key = feed->key; row.type = feed_type(feed->type); row.enable = feed->enabled(); + row.source_key = feed->source_key; feed->output_format.read([&](const auto& value) { row.output_format = value; row.format = value.type; @@ -578,6 +558,7 @@ Data_Feed_Row feed_row(const std::shared_ptr& feed) { } void apply_feed_row(const std::shared_ptr& feed, const Data_Feed_Row& row) { feed->set_enabled(row.enable); + feed->source_key = row.source_key; feed->output_format.write([&](auto& value) { value = row.output_format; }); @@ -603,26 +584,6 @@ void apply_feed_row(const std::shared_ptr& feed, const Data_Feed_Row& }); } } -Source_Feed_Relation_Row relation_row(const std::shared_ptr& relation) { - Source_Feed_Relation_Row row; - row.key = relation->key; - row.type = relation_type(relation->type); - relation->settings.read([&](const auto& value) { - row.enable = value.enable; - row.source_key = value.source_key; - row.feed_key = value.feed_key; - row.feed_name = value.feed_name; - }); - return row; -} -void apply_relation_row(const std::shared_ptr& relation, const Source_Feed_Relation_Row& row) { - relation->settings.write([&](auto& value) { - value.enable = row.enable; - value.source_key = row.source_key; - value.feed_key = row.feed_key; - value.feed_name = row.feed_name; - }); -} template adminive::Http_Response list_response(std::string path, std::vector rows, adminive::Collection_Query query) { adminive::Collection_Service collection(std::move(path), std::move(rows)); @@ -668,11 +629,43 @@ std::vector feed_rows(Global* global) { rows.push_back(feed_row(feed)); return rows; } -std::vector relation_rows(Global* global) { - std::vector rows; - for(const auto& relation : global->mode_acs.source_feed_relation_config.map.list()) - rows.push_back(relation_row(relation)); - return rows; +Json data_topology_view(Global* global) { + auto result = global->mode_acs.data_topology_view.read([](const auto& value) { + return adminive::parse_json(value.to_json().to_json_string()); + }); + result["protocol"] = "ecap.data-topology-view"; + result["protocol_version"] = 1; + return result; +} +Json data_topology(Global* global) { + const auto sources = global->mode_acs.data_source_config.map.list(); + const auto feeds = global->mode_acs.data_feed_config.map.list(); + std::map> source_by_key; + Json nodes = Json::array(); + Json edges = Json::array(); + for(std::size_t index = 0; index < sources.size(); ++index) { + const auto& source = sources[index]; + const auto id = "data_source:" + source->key; + source_by_key.emplace(source->key, source); + nodes.push_back(Json{{"id", id}, {"kind", "data_source"}, {"entity_id", index + 1}, + {"key", source->key}, {"label", source->key}, {"type", source->type}, + {"enabled", source->enabled()}, {"status_api", data_topology_path + "/status/data_source/" + std::to_string(index + 1)}}); + } + for(std::size_t index = 0; index < feeds.size(); ++index) { + const auto& feed = feeds[index]; + const auto id = "data_feed:" + feed->key; + nodes.push_back(Json{{"id", id}, {"kind", "data_feed"}, {"entity_id", index + 1}, + {"key", feed->key}, {"label", feed->key}, {"type", feed->type}, + {"enabled", feed->enabled()}, {"status_api", data_topology_path + "/status/data_feed/" + std::to_string(index + 1)}}); + const auto source = source_by_key.find(feed->source_key); + if(source != source_by_key.end()) { + edges.push_back(Json{{"id", "source-feed:" + feed->key}, + {"source", "data_source:" + feed->source_key}, {"target", id}, + {"active", source->second->enabled() && feed->enabled()}}); + } + } + return Json{{"protocol", "ecap.data-topology"}, {"protocol_version", 1}, + {"nodes", std::move(nodes)}, {"edges", std::move(edges)}, {"view", data_topology_view(global)}}; } std::vector external_resource_rows(Global* global) { std::map status_by_name; @@ -705,11 +698,55 @@ std::vector external_resource_rows(Global* global) { Json source_amis_schema() { return adminive::to_amis_table_schema("/api/adminive/config/data_sources", adminive::describe_table_view()); } -Json feed_amis_schema() { - return adminive::to_amis_table_schema("/api/adminive/config/data_feeds", adminive::describe_table_view()); +void set_select_options(Json& value, std::string_view field_name, const Json& options) { + if(value.is_object()) { + const auto name = value.find("name"); + if(name != value.end() && name->is_string() && name->get() == field_name) { + value["type"] = "select"; + value["options"] = options; + value["clearable"] = true; + value["placeholder"] = "未连接"; + } + for(auto& [key, child] : value.items()) { + static_cast(key); + set_select_options(child, field_name, options); + } + } else if(value.is_array()) { + for(auto& child : value) + set_select_options(child, field_name, options); + } } -Json relation_amis_schema() { - return adminive::to_amis_table_schema("/api/adminive/config/source_feed_relations", adminive::describe_table_view()); +Json feed_amis_schema(Global* global) { + auto schema = adminive::to_amis_table_schema("/api/adminive/config/data_feeds", adminive::describe_table_view()); + Json options = Json::array(); + for(const auto& source : global->mode_acs.data_source_config.map.list()) + options.push_back(Json{{"label", source->key}, {"value", source->key}}); + set_select_options(schema, "source_key", options); + return schema; +} +Json data_topology_schema(Global* global) { + return Json{ + {"title", "数据源与数据馈送拓扑"}, + {"data_api", data_topology_path}, + {"view_api", data_topology_path + "/view"}, + {"graph", Json{ + {"height", 620}, + {"layout", Json{{"type", "dagre"}, {"rankdir", "LR"}, {"align", "UL"}, {"nodesep", 34}, {"ranksep", 100}}}, + {"behaviors", Json::array({"drag-canvas", "zoom-canvas", "drag-element"})}, + {"active_edge_color", "#52c41a"}, + {"inactive_edge_color", "#bfbfbf"} + }}, + {"node_kinds", Json::array({ + Json{{"kind", "data_source"}, {"title", "数据源"}, {"fill", "#e6f4ff"}, {"stroke", "#1677ff"}}, + Json{{"kind", "data_feed"}, {"title", "数据馈送"}, {"fill", "#f6ffed"}, {"stroke", "#52c41a"}} + })}, + {"status", Json{{"title", "运行状态"}, {"poll_interval_ms", 1000}, + {"collapsed_hint", "展开后才向后台轮询,收起立即停止"}}}, + {"editors", Json::array({ + Json{{"kind", "data_source"}, {"title", "数据源管理"}, {"button_label", "管理数据源"}, {"schema", source_amis_schema()}}, + Json{{"kind", "data_feed"}, {"title", "数据馈送管理"}, {"button_label", "管理数据馈送"}, {"schema", feed_amis_schema(global)}} + })} + }; } Json external_resource_amis_schema() { auto schema = adminive::to_amis_table_schema("/api/adminive/config/external_resources", adminive::describe_table_view()); @@ -843,7 +880,7 @@ public: cesium_graphics_resource_.bind(app); bind_data_sources(app); bind_data_feeds(app); - bind_relations(app); + bind_data_topology(app); bind_external_resources(app); bind_backend_config(app); app.registerHandler(manifest_path, [this](const drogon::HttpRequestPtr&, std::function&& callback) { @@ -851,6 +888,51 @@ public: }, {drogon::Get}); } private: + void bind_data_topology(drogon::HttpAppFramework& app) { + const auto get = adminive::make_drogon_constraints(drogon::Get, {}); + const auto put = adminive::make_drogon_constraints(drogon::Put, {}); + app.registerHandler(data_topology_path + "/schema", [this](const drogon::HttpRequestPtr&, std::function&& callback) { + complete_business_request(callback, [this] { + std::lock_guard lock(topology_mutex_); + return adminive::make_http_success(data_topology_schema(global_)); + }); + }, get); + app.registerHandler(data_topology_path, [this](const drogon::HttpRequestPtr&, std::function&& callback) { + complete_business_request(callback, [this] { + std::lock_guard lock(topology_mutex_); + return adminive::make_http_success(data_topology(global_)); + }); + }, get); + app.registerHandler(data_topology_path + "/view", [this](const drogon::HttpRequestPtr&, std::function&& callback) { + complete_business_request(callback, [this] { + std::lock_guard lock(topology_mutex_); + return adminive::make_http_success(data_topology_view(global_)); + }); + }, get); + app.registerHandler(data_topology_path + "/view", [this](const drogon::HttpRequestPtr& request, std::function&& callback) { + complete_business_request(callback, [this, body = std::string(request->getBody())] { + std::lock_guard lock(topology_mutex_); + auto value = read_topology_view(read_request_body(body), global_); + global_->mode_acs.data_topology_view.write([&](auto& current) { current = std::move(value); }); + Config::save(Config_Section::mode_acs, global_->mode_acs.to_base_json()); + return adminive::make_http_success(data_topology_view(global_), "saved"); + }); + }, put); + app.registerHandler(data_topology_path + "/status/data_source/{1:id}", [this](const drogon::HttpRequestPtr&, std::function&& callback, std::uint64_t id) { + complete_business_request(callback, [this, id] { + std::lock_guard lock(topology_mutex_); + const auto source = item_by_id(global_->mode_acs.data_source_config.map.list(), id); + return adminive::make_http_success(adminive::parse_json(source->get_state().to_json_string())); + }); + }, get); + app.registerHandler(data_topology_path + "/status/data_feed/{1:id}", [this](const drogon::HttpRequestPtr&, std::function&& callback, std::uint64_t id) { + complete_business_request(callback, [this, id] { + std::lock_guard lock(topology_mutex_); + const auto feed = item_by_id(global_->mode_acs.data_feed_config.map.list(), id); + return adminive::make_http_success(adminive::parse_json(feed->get_state_json().to_json_string())); + }); + }, get); + } void bind_backend_config(drogon::HttpAppFramework& app) { const auto get = adminive::make_drogon_constraints(drogon::Get, {}); app.registerHandler(backend_config_path, [this](const drogon::HttpRequestPtr&, std::function&& callback) { @@ -885,7 +967,7 @@ private: source->key = row.key; apply_source_row(source, row); global_->mode_acs.data_source_config.map.push_back(source); - Source_Feed_Relation_Config::set_need_refresh(); + Data_feed_Config::set_connections_need_refresh(); save_data_sources(global_); return adminive::make_http_success(adminive::to_frontend_json(source_row(source)), "created"); }); @@ -901,7 +983,7 @@ private: if(!result.success) return adminive::make_http_error(422, result); apply_source_row(source, row); - Source_Feed_Relation_Config::set_need_refresh(); + Data_feed_Config::set_connections_need_refresh(); save_data_sources(global_); return adminive::make_http_success(adminive::to_frontend_json(source_row(source)), result.message); }); @@ -913,12 +995,12 @@ private: std::lock_guard lock(topology_mutex_); const auto list = global_->mode_acs.data_source_config.map.list(); auto source = item_by_id(list, id); - if(global_->mode_acs.source_feed_relation_config.source_has_dependency(source->key)) - throw Conflict_Error("数据源存在数据馈送关系依赖,不能删除: " + source->key); + if(global_->mode_acs.data_feed_config.source_has_connection(source->key)) + throw Conflict_Error("数据源仍被数据馈送连接,不能删除: " + source->key); source->async_stop(); if(!global_->mode_acs.data_source_config.map.remove(id - 1)) throw std::out_of_range("record not found"); - Source_Feed_Relation_Config::set_need_refresh(); + Data_feed_Config::set_connections_need_refresh(); save_data_sources(global_); return adminive::make_http_success(adminive::json_object(), "deleted"); }); @@ -930,7 +1012,7 @@ private: const auto keys = ordered_keys(list, read_order_ids(body)); if(!global_->mode_acs.data_source_config.map.set_order(keys)) throw std::invalid_argument("invalid order"); - Source_Feed_Relation_Config::set_need_refresh(); + Data_feed_Config::set_connections_need_refresh(); save_data_sources(global_); return adminive::make_http_success(adminive::json_object(), "reordered"); }); @@ -943,7 +1025,7 @@ private: const auto remove = adminive::make_drogon_constraints(drogon::Delete, {}); bind_collection_read_handlers(app, data_feeds_path, [] { return adminive::to_descriptor_json(); }, - [this] { return feed_rows(global_); }, [] { return feed_amis_schema(); }, true); + [this] { return feed_rows(global_); }, [this] { return feed_amis_schema(global_); }, true); app.registerHandler(data_feeds_path, [this](const drogon::HttpRequestPtr& request, std::function&& callback) { complete_business_request(callback, [this, body = std::string(request->getBody())] { std::lock_guard lock(topology_mutex_); @@ -954,11 +1036,13 @@ private: return adminive::make_http_error(422, result); if(global_->mode_acs.data_feed_config.map.get(row.key)) return adminive::make_http_error(409, "数据馈送名称已存在: " + row.key); + if(!row.source_key.empty() && !global_->mode_acs.data_source_config.map.get(row.source_key)) + throw std::invalid_argument("连接的数据源不存在: " + row.source_key); auto feed = create_data_feed_from_type(feed_type_name(row.type)); feed->key = row.key; apply_feed_row(feed, row); global_->mode_acs.data_feed_config.map.push_back(feed); - Source_Feed_Relation_Config::set_need_refresh(); + Data_feed_Config::set_connections_need_refresh(); save_data_feeds(global_); return adminive::make_http_success(adminive::to_frontend_json(feed_row(feed)), "created"); }); @@ -973,8 +1057,10 @@ private: const auto result = adminive::apply_frontend_patch(row, input); if(!result.success) return adminive::make_http_error(422, result); + if(!row.source_key.empty() && !global_->mode_acs.data_source_config.map.get(row.source_key)) + throw std::invalid_argument("连接的数据源不存在: " + row.source_key); apply_feed_row(feed, row); - Source_Feed_Relation_Config::set_need_refresh(); + Data_feed_Config::set_connections_need_refresh(); save_data_feeds(global_); return adminive::make_http_success(adminive::to_frontend_json(feed_row(feed)), result.message); }); @@ -986,12 +1072,10 @@ private: std::lock_guard lock(topology_mutex_); const auto list = global_->mode_acs.data_feed_config.map.list(); auto feed = item_by_id(list, id); - if(global_->mode_acs.source_feed_relation_config.feed_has_dependency(feed->key)) - throw Conflict_Error("数据馈送存在数据源关系依赖,不能删除: " + feed->key); feed->async_stop(); if(!global_->mode_acs.data_feed_config.map.remove(id - 1)) throw std::out_of_range("record not found"); - Source_Feed_Relation_Config::set_need_refresh(); + Data_feed_Config::set_connections_need_refresh(); save_data_feeds(global_); return adminive::make_http_success(adminive::json_object(), "deleted"); }); @@ -1003,93 +1087,17 @@ private: const auto keys = ordered_keys(list, read_order_ids(body)); if(!global_->mode_acs.data_feed_config.map.set_order(keys)) throw std::invalid_argument("invalid order"); - Source_Feed_Relation_Config::set_need_refresh(); + Data_feed_Config::set_connections_need_refresh(); save_data_feeds(global_); return adminive::make_http_success(adminive::json_object(), "reordered"); }); }, post); } - void bind_relations(drogon::HttpAppFramework& app) { - const auto post = adminive::make_drogon_constraints(drogon::Post, {}); - const auto put = adminive::make_drogon_constraints(drogon::Put, {}); - const auto patch = adminive::make_drogon_constraints(drogon::Patch, {}); - const auto remove = adminive::make_drogon_constraints(drogon::Delete, {}); - bind_collection_read_handlers(app, relations_path, - [] { return adminive::to_descriptor_json(); }, - [this] { return relation_rows(global_); }, [] { return relation_amis_schema(); }, true); - app.registerHandler(relations_path, [this](const drogon::HttpRequestPtr& request, std::function&& callback) { - complete_business_request(callback, [this, body = std::string(request->getBody())] { - std::lock_guard lock(topology_mutex_); - auto input = read_request_body(body); - Source_Feed_Relation_Row row; - const auto result = adminive::apply_frontend_create(row, input); - if(!result.success) - return adminive::make_http_error(422, result); - auto relation = create_source_feed_relation_from_type(relation_type_name(row.type)); - relation->key = row.key; - apply_relation_row(relation, row); - global_->mode_acs.source_feed_relation_config.validate(*relation); - global_->mode_acs.source_feed_relation_config.map.push_back(relation); - Source_Feed_Relation_Config::set_need_refresh(); - save_source_feed_relations(global_); - return adminive::make_http_success(adminive::to_frontend_json(relation_row(relation)), "created"); - }); - }, post); - const auto update = [this](const drogon::HttpRequestPtr& request, std::function&& callback, std::uint64_t id) { - complete_business_request(callback, [this, id, body = std::string(request->getBody())] { - std::lock_guard lock(topology_mutex_); - const auto list = global_->mode_acs.source_feed_relation_config.map.list(); - auto relation = item_by_id(list, id); - auto input = read_request_body(body); - auto row = relation_row(relation); - const auto result = adminive::apply_frontend_patch(row, input); - if(!result.success) - return adminive::make_http_error(422, result); - auto candidate = create_source_feed_relation_from_type(relation->type); - candidate->key = relation->key; - apply_relation_row(candidate, row); - global_->mode_acs.source_feed_relation_config.validate(*candidate, relation->key); - apply_relation_row(relation, row); - Source_Feed_Relation_Config::set_need_refresh(); - save_source_feed_relations(global_); - return adminive::make_http_success(adminive::to_frontend_json(relation_row(relation)), result.message); - }); - }; - app.registerHandler(relations_path + "/{1:id}", update, put); - app.registerHandler(relations_path + "/{1:id}", update, patch); - app.registerHandler(relations_path + "/{1:id}", [this](const drogon::HttpRequestPtr&, std::function&& callback, std::uint64_t id) { - complete_business_request(callback, [this, id] { - std::lock_guard lock(topology_mutex_); - const auto list = global_->mode_acs.source_feed_relation_config.map.list(); - item_by_id(list, id); - if(!global_->mode_acs.source_feed_relation_config.map.remove(id - 1)) - throw std::out_of_range("record not found"); - Source_Feed_Relation_Config::set_need_refresh(); - save_source_feed_relations(global_); - return adminive::make_http_success(adminive::json_object(), "deleted"); - }); - }, remove); - app.registerHandler(relations_path + "/order", [this](const drogon::HttpRequestPtr& request, std::function&& callback) { - complete_business_request(callback, [this, body = std::string(request->getBody())] { - std::lock_guard lock(topology_mutex_); - const auto list = global_->mode_acs.source_feed_relation_config.map.list(); - const auto keys = ordered_keys(list, read_order_ids(body)); - if(!global_->mode_acs.source_feed_relation_config.map.set_order(keys)) - throw std::invalid_argument("invalid order"); - Source_Feed_Relation_Config::set_need_refresh(); - save_source_feed_relations(global_); - return adminive::make_http_success(adminive::json_object(), "reordered"); - }); - }, post); - } Json manifest() const { - const auto view = adminive::composition_view("ecap_config", adminive::compose::frontend(adminive::compose::slot("mode_acs"), adminive::compose::slot("data_feed_settings"), adminive::compose::slot("data_sources"), adminive::compose::slot("data_feeds"), adminive::compose::slot("source_feed_relations"), adminive::compose::slot("mlat"), adminive::compose::slot("map_view"), adminive::compose::slot("cesium_graphics"), adminive::compose::slot("external_resources"), adminive::compose::slot("archive_operations"), adminive::compose::slot("cesium_models"), adminive::compose::slot("backend_config"), adminive::compose::slot("system_actions"))).titled("ECAP 配置"); + const auto view = adminive::composition_view("ecap_config", adminive::compose::frontend(adminive::compose::slot("mode_acs"), adminive::compose::slot("data_feed_settings"), adminive::compose::slot("mlat"), adminive::compose::slot("map_view"), adminive::compose::slot("cesium_graphics"), adminive::compose::slot("external_resources"), adminive::compose::slot("archive_operations"), adminive::compose::slot("cesium_models"), adminive::compose::slot("backend_config"), adminive::compose::slot("system_actions"))).titled("ECAP 配置"); const std::vector contracts = { {"mode_acs", "amis_schema", mode_acs_path + "/descriptor", mode_acs_path + "/view", mode_acs_path + "/data", mode_acs_path + "/amis"}, {"data_feed_settings", "amis_schema", data_feed_settings_path + "/descriptor", data_feed_settings_path + "/view", data_feed_settings_path + "/data", data_feed_settings_path + "/amis"}, - {"data_sources", "amis_schema", data_sources_path + "/descriptor", data_sources_path + "/view", data_sources_path, data_sources_path + "/amis"}, - {"data_feeds", "amis_schema", data_feeds_path + "/descriptor", data_feeds_path + "/view", data_feeds_path, data_feeds_path + "/amis"}, - {"source_feed_relations", "amis_schema", relations_path + "/descriptor", relations_path + "/view", relations_path, relations_path + "/amis"}, {"mlat", "amis_schema", mlat_path + "/descriptor", mlat_path + "/view", mlat_path + "/data", mlat_path + "/amis"}, {"map_view", "amis_schema", map_view_path + "/descriptor", map_view_path + "/view", map_view_path + "/data", map_view_path + "/amis"}, {"cesium_graphics", "amis_schema", cesium_graphics_path + "/descriptor", cesium_graphics_path + "/view", cesium_graphics_path + "/data", cesium_graphics_path + "/amis"}, @@ -1102,9 +1110,6 @@ private: const std::map schemas = { {"mode_acs", mode_acs_resource_.amis_schema()}, {"data_feed_settings", data_feed_settings_resource_.amis_schema()}, - {"data_sources", source_amis_schema()}, - {"data_feeds", feed_amis_schema()}, - {"source_feed_relations", relation_amis_schema()}, {"mlat", mlat_resource_.amis_schema()}, {"map_view", map_view_resource_.amis_schema()}, {"cesium_graphics", cesium_graphics_resource_.amis_schema()}, @@ -1121,7 +1126,6 @@ private: static inline const std::string map_view_path = "/api/adminive/config/map_view"; static inline const std::string data_sources_path = "/api/adminive/config/data_sources"; static inline const std::string data_feeds_path = "/api/adminive/config/data_feeds"; - static inline const std::string relations_path = "/api/adminive/config/source_feed_relations"; static inline const std::string external_resources_path = "/api/adminive/config/external_resources"; static inline const std::string backend_config_path = "/api/adminive/config/backend_config"; static inline const std::string mlat_path = "/api/adminive/config/mlat"; diff --git a/module/Local_Server/server/Config.cpp b/module/Local_Server/server/Config.cpp index ee5b657..41b6c30 100644 --- a/module/Local_Server/server/Config.cpp +++ b/module/Local_Server/server/Config.cpp @@ -1,6 +1,7 @@ #include "Config.h" #include "Config_Default.h" #include +#include #include #include #include @@ -118,6 +119,112 @@ Psc::JSON config_section_value(Global& global, Config_Section section) { } std::terminate(); } +std::string optional_string(const Psc::JSON& value, std::string_view key) { + const auto* field = value.get(key); + return field != nullptr && field->valueType == Psc::String ? field->val : std::string{}; +} +bool optional_enabled(const Psc::JSON& value) { + const auto* field = value.get("enable"); + return field != nullptr && field->valueType == Psc::Bool && field->bool_val(); +} +void migrate_legacy_source_feed_relations(const Psc::JSON* mode_acs, Data_Source_Config& sources, + Data_feed_Config& feeds) { + const auto* legacy = mode_acs == nullptr ? nullptr : mode_acs->get("source_feed_relation"); + const auto* list = legacy == nullptr ? nullptr : legacy->get("list"); + if (list == nullptr || list->valueType != Psc::Array) + return; + std::map assignments; + const auto feed_list = feeds.map.list(); + for (const auto& relation : list->children) { + if (!optional_enabled(relation) || optional_string(relation, "type") != "One_to_One_Relation") + continue; + const auto source_key = optional_string(relation, "source_key"); + const auto feed_key = optional_string(relation, "feed_key"); + if (sources.map.get(source_key) && feeds.map.get(feed_key) && !assignments.contains(feed_key)) + assignments.emplace(feed_key, source_key); + } + for (const auto& relation : list->children) { + if (!optional_enabled(relation) || optional_string(relation, "type") != "Name_One_To_Many_Relation") + continue; + const auto source_key = optional_string(relation, "source_key"); + if (!sources.map.get(source_key)) + continue; + const auto configured_prefix = optional_string(relation, "feed_name"); + const auto& prefix = configured_prefix.empty() ? source_key : configured_prefix; + for (const auto& feed : feed_list) { + if (!assignments.contains(feed->key) && feed->key.starts_with(prefix)) + assignments.emplace(feed->key, source_key); + } + } + const auto default_relation = std::find_if(list->children.begin(), list->children.end(), [](const auto& relation) { + return optional_enabled(relation) && optional_string(relation, "type") == "First_Source_To_All_Feed_Relation"; + }); + if (default_relation != list->children.end()) { + const auto source_list = sources.map.list(); + const auto first_source = std::find_if(source_list.begin(), source_list.end(), [](const auto& source) { + return source->enabled(); + }); + if (first_source != source_list.end()) { + for (const auto& feed : feed_list) { + if (!assignments.contains(feed->key)) + assignments.emplace(feed->key, (*first_source)->key); + } + } + } + for (const auto& feed : feed_list) { + const auto assignment = assignments.find(feed->key); + if (feed->source_key.empty() && assignment != assignments.end()) + feed->source_key = assignment->second; + } +} +} +void Data_Topology_View_Config::from_json(const Psc::JSON* that_json) { + if (that_json == nullptr || that_json->valueType != Psc::Object) + return; + if (const auto* value = that_json->get("initialized"); value != nullptr && value->valueType == Psc::Bool) + initialized = value->bool_val(); + if (const auto value = that_json->try_get_number("zoom"); value.has_value() && std::isfinite(value.value()) && value.value() > 0.0) + zoom = value.value(); + if (const auto value = that_json->try_get_number("pan_x"); value.has_value() && std::isfinite(value.value())) + pan_x = value.value(); + if (const auto value = that_json->try_get_number("pan_y"); value.has_value() && std::isfinite(value.value())) + pan_y = value.value(); + const auto* positions = that_json->get("node_positions"); + if (positions == nullptr || positions->valueType != Psc::Object) + return; + node_positions.clear(); + for (const auto& item : positions->children) { + if (item.valueType != Psc::Object) + continue; + const auto x = item.try_get_number("x"); + const auto y = item.try_get_number("y"); + if (x.has_value() && y.has_value() && std::isfinite(x.value()) && std::isfinite(y.value())) + node_positions.emplace(item.key, Data_Topology_Node_Position{x.value(), y.value()}); + } +} +Psc::JSON Data_Topology_View_Config::to_json() const { + auto positions = Psc::JSON::object(); + for (const auto& [id, position] : node_positions) + positions.append({id, Psc::JSON::object({{"x", position.x}, {"y", position.y}})}); + return Psc::JSON::object({{"initialized", initialized}, {"zoom", zoom}, {"pan_x", pan_x}, {"pan_y", pan_y}, + {"node_positions", positions}}); +} +Psc::JSON Mode_ACS_Config::to_base_json() const { + auto result = settings.read([](const auto& value) { return value.to_base_json(); }); + result.append(Psc::JSON("data_feed", data_feed_config.to_json())); + result.append(Psc::JSON("data_source", data_source_config.to_json())); + result.append(Psc::JSON("data_topology_view", data_topology_view.read([](const auto& value) { return value.to_json(); }))); + return result; +} +void Mode_ACS_Config::from_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { + settings.write([&](auto& value) { + value.from_base_json(that_json, not_exist_use_default_value); + SSR::Monitor_Mode_ACS_Message_Num = value.monitor_msg_live; + }); + data_source_config.init(that_json->get("data_source"), not_exist_use_default_value); + data_feed_config.init(that_json->get("data_feed"), not_exist_use_default_value); + migrate_legacy_source_feed_relations(that_json, data_source_config, data_feed_config); + data_topology_view.write([&](auto& value) { value.from_json(that_json->get("data_topology_view")); }); } Psc::JSON Config::load() { std::filesystem::create_directories(config_directory()); diff --git a/module/Local_Server/server/Config.h b/module/Local_Server/server/Config.h index f41a61a..f3276fe 100644 --- a/module/Local_Server/server/Config.h +++ b/module/Local_Server/server/Config.h @@ -9,7 +9,6 @@ #include "../Data_Source/export.h" #include "MLAT.h" #include "Psc_Cpp_Core/spdlog/export.h" -#include "Source_Feed_Relation.h" #include "adminive/json.hpp" #include "adminive/managed.hpp" inline bool delete_log = false; @@ -91,8 +90,42 @@ struct Mode_ACS_Config_Data { bool monitor_msg_live{}; PSC_USE_JSON }; +struct Data_Topology_Node_Position { + double x{}; + double y{}; +}; +struct Data_Topology_View_Config { + bool initialized{}; + double zoom = 1.0; + double pan_x{}; + double pan_y{}; + std::map node_positions; + void from_json(const Psc::JSON* that_json); + [[nodiscard]] Psc::JSON to_json() const; +}; namespace adminive { template <> +struct Type_Descriptor { + static auto get() { + using T = Data_Topology_Node_Position; + return object("data_topology_node_position", "拓扑节点位置", + ADMINIVE_FIELD_LABEL(T, x, "X"), + ADMINIVE_FIELD_LABEL(T, y, "Y")); + } +}; +template <> +struct Type_Descriptor { + static auto get() { + using T = Data_Topology_View_Config; + return object("data_topology_view", "数据拓扑视图", + ADMINIVE_FIELD_LABEL(T, initialized, "已初始化"), + ADMINIVE_FIELD_LABEL(T, zoom, "缩放"), + ADMINIVE_FIELD_LABEL(T, pan_x, "平移 X"), + ADMINIVE_FIELD_LABEL(T, pan_y, "平移 Y"), + ADMINIVE_FIELD_LABEL(T, node_positions, "节点位置")); + } +}; +template <> struct Type_Descriptor { static auto get() { using T = Mode_ACS_Config_Data; @@ -121,31 +154,15 @@ struct Mode_ACS_Config { adminive::Managed_Value settings; Data_feed_Config data_feed_config; Data_Source_Config data_source_config; - Source_Feed_Relation_Config source_feed_relation_config; + adminive::Managed_Value data_topology_view; std::size_t Report_Data_Feed_Message_Num{}; Mode_ACS_Config() = default; ~Mode_ACS_Config() { if (delete_log) std::cout << " ~Mode_S_Config()" << std::endl; } - Psc::JSON to_base_json() const { - auto ret = settings.read([](const auto& value) { - return value.to_base_json(); - }); - ret.append(Psc::JSON("data_feed", data_feed_config.to_json())); - ret.append(Psc::JSON("data_source", data_source_config.to_json())); - ret.append(Psc::JSON("source_feed_relation", source_feed_relation_config.to_json())); - return ret; - } - void from_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { - settings.write([&](auto& value) { - value.from_base_json(that_json, not_exist_use_default_value); - SSR::Monitor_Mode_ACS_Message_Num = value.monitor_msg_live; - }); - data_feed_config.init(that_json->get("data_feed"), not_exist_use_default_value); - data_source_config.init(that_json->get("data_source"), not_exist_use_default_value); - source_feed_relation_config.from_json(that_json->get("source_feed_relation"), not_exist_use_default_value); - } + [[nodiscard]] Psc::JSON to_base_json() const; + void from_json(const Psc::JSON* that_json, bool not_exist_use_default_value); void server(Global* g); }; struct Web_Server_Config { diff --git a/module/Local_Server/server/Source_Feed_Relation.cpp b/module/Local_Server/server/Source_Feed_Relation.cpp deleted file mode 100644 index 9baa2dd..0000000 --- a/module/Local_Server/server/Source_Feed_Relation.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "Source_Feed_Relation.h" -#include "Config.h" -#include "Global.h" -void Source_Feed_Relation::from_json(const Psc::JSON* that_json) { - Get_J(key) Get_J(type) - settings.write([&](auto& value) { - value.from_base_json(that_json, true); - }); -} -Psc::JSON Source_Feed_Relation::to_json() const { - auto ret = settings.read([](const auto& value) { return value.to_base_json(); }); - ret.append({"key", key}); - ret.append({"type", type}); - return ret; -} -void Source_Feed_Relation_Config::from_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { - static_cast(not_exist_use_default_value); - auto j_list = that_json->get("list"); - for (const Psc::JSON& item : j_list->children) { - auto relation = create_source_feed_relation_from_type(item.get_string("type")); - relation->from_json(&item); - validate(*relation); - map.push_back(relation); - } -} -Psc::JSON Source_Feed_Relation_Config::to_json() const { - Psc::JSON ret = Psc::JSON::object(); - Psc::JSON list = Psc::JSON::array(); - for (const auto& relation : map.list()) - list.append(relation->to_json()); - ret.append(Psc::JSON("list", list)); - return ret; -} -void Source_Feed_Relation_Config::set_need_refresh() { - for (const auto& source : Global::instance()->mode_acs.data_source_config.map.list()) - source->need_refresh_data_feed_key_list = true; -} -bool Source_Feed_Relation_Config::source_exists(std::string_view key) const { - return Global::instance()->mode_acs.data_source_config.map.get(std::string(key)).has_value(); -} -bool Source_Feed_Relation_Config::feed_exists(std::string_view key) const { - return Global::instance()->mode_acs.data_feed_config.map.get(std::string(key)).has_value(); -} -bool Source_Feed_Relation_Config::source_has_dependency(std::string_view key) const { - for (const auto& relation : map.list()) { - if (relation->type == "First_Source_To_All_Feed_Relation") - continue; - if (relation->settings.member<&Source_Feed_Relation_Data::source_key>().read([](const auto& value) { return value; }) == key) - return true; - } - return false; -} -bool Source_Feed_Relation_Config::feed_has_dependency(std::string_view key) const { - for (const auto& relation : map.list()) { - const auto value = relation->settings.read([](const auto& value) { return value; }); - if (relation->type == "One_to_One_Relation" && value.feed_key == key) - return true; - if (relation->type == "Name_One_To_Many_Relation") { - const auto& prefix = value.feed_name.empty() ? value.source_key : value.feed_name; - if (key.starts_with(prefix)) - return true; - } - } - return false; -} -void Source_Feed_Relation_Config::validate(const Source_Feed_Relation& relation, std::string_view current_key) const { - if (relation.key.empty()) - throw std::invalid_argument("关系名称不能为空"); - auto existing = map.get(relation.key); - if (existing.has_value() && relation.key != current_key) - throw std::invalid_argument("关系名称已存在: " + relation.key); - const auto value = relation.settings.read([](const auto& value) { return value; }); - if (relation.type == "One_to_One_Relation") { - if (!source_exists(value.source_key)) - throw std::invalid_argument("数据源不存在: " + value.source_key); - if (!feed_exists(value.feed_key)) - throw std::invalid_argument("数据馈送不存在: " + value.feed_key); - for (const auto& item : map.list()) { - if (item->key == current_key || item->type != "One_to_One_Relation") - continue; - if (item->settings.member<&Source_Feed_Relation_Data::feed_key>().read([](const auto& value) { return value; }) == value.feed_key) - throw std::invalid_argument("数据馈送已存在一对一关系: " + value.feed_key); - } - return; - } - if (relation.type == "Name_One_To_Many_Relation") { - if (!source_exists(value.source_key)) - throw std::invalid_argument("数据源不存在: " + value.source_key); - return; - } - if (relation.type == "First_Source_To_All_Feed_Relation") { - for (const auto& item : map.list()) { - if (item->key != current_key && item->type == "First_Source_To_All_Feed_Relation") - throw std::invalid_argument("默认关系只能存在一个"); - } - return; - } - throw std::invalid_argument("未知关系类型: " + relation.type); -} diff --git a/module/Local_Server/server/Source_Feed_Relation.h b/module/Local_Server/server/Source_Feed_Relation.h deleted file mode 100644 index 7d7f445..0000000 --- a/module/Local_Server/server/Source_Feed_Relation.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef SOURCE_FEED_RELATION_H -#define SOURCE_FEED_RELATION_H -#include -#include -#include "../global_include.h" -#include "adminive/managed.hpp" -struct Source_Feed_Relation_Data { - bool enable{}; - std::string source_key; - std::string feed_key; - std::string feed_name; - PSC_USE_JSON -}; -namespace adminive { -template <> -struct Type_Descriptor { - static auto get() { - using T = Source_Feed_Relation_Data; - return object("source_feed_relation_data", "数据源-数据馈送关系", - ADMINIVE_FIELD_LABEL(T, enable, "启用").editable().boolean_input(), - ADMINIVE_FIELD_LABEL(T, source_key, "数据源名称").editable().text_input(), - ADMINIVE_FIELD_LABEL(T, feed_key, "数据馈送名称").editable().text_input(), - ADMINIVE_FIELD_LABEL(T, feed_name, "数据馈送名称前缀").editable().text_input()); - } -}; -} -struct Source_Feed_Relation { - std::string key; - std::string type; - adminive::Managed_Value settings; - void from_json(const Psc::JSON* that_json); - [[nodiscard]] Psc::JSON to_json() const; -}; -inline std::shared_ptr create_source_feed_relation_from_type(std::string_view type) { - if (type != "One_to_One_Relation" && type != "Name_One_To_Many_Relation" && type != "First_Source_To_All_Feed_Relation") - throw std::invalid_argument("unknown Source_Feed_Relation type: " + std::string(type)); - auto result = std::make_shared(); - result->type = type; - return result; -} -struct Source_Feed_Relation_Config { - void from_json(const Psc::JSON* that_json, bool not_exist_use_default_value); - [[nodiscard]] Psc::JSON to_json() const; - Ordered_Map> map; - static void set_need_refresh(); - [[nodiscard]] bool source_exists(std::string_view key) const; - [[nodiscard]] bool feed_exists(std::string_view key) const; - [[nodiscard]] bool source_has_dependency(std::string_view key) const; - [[nodiscard]] bool feed_has_dependency(std::string_view key) const; - void validate(const Source_Feed_Relation& relation, std::string_view current_key = {}) const; -}; -#endif