拓扑图
This commit is contained in:
@@ -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<void> Data_Feed_UDP_Server::handle_in_loop_coro() {
|
||||
co_await svr.tick_coro();
|
||||
co_return;
|
||||
|
||||
@@ -29,6 +29,7 @@ struct Type_Descriptor<Output_Format> {
|
||||
}
|
||||
class Data_Feed : public With_Loop_Coro {
|
||||
public:
|
||||
std::string source_key;
|
||||
BIN_Msg_Buffer msg_buffer{};
|
||||
adminive::Managed_Value<Output_Format> 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);
|
||||
};
|
||||
|
||||
@@ -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<std::string, std::string> 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<Cached_Source_Info> 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<std::mutex> lock(cdf_mtx);
|
||||
for (auto& item : next) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "adminive/adapters/nlohmann_json.hpp"
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -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<ecap::adminive_config::Data_Source_Row> {
|
||||
.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<ecap::adminive_config::Data_Feed_Row> {
|
||||
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<ecap::adminive_config::Data_Feed_Row> {
|
||||
static Table_View table() {
|
||||
using T = ecap::adminive_config::Data_Feed_Row;
|
||||
return table_view<T>(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<T>(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<ecap::adminive_config::Source_Feed_Relation_Row> {
|
||||
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<E::One_to_One_Relation>("一对一")
|
||||
.enum_label<E::Name_One_To_Many_Relation>("按名称一对多")
|
||||
.enum_label<E::First_Source_To_All_Feed_Relation>("默认:首个数据源到未匹配馈送");
|
||||
return object<T>("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<ecap::adminive_config::Source_Feed_Relation_Row> {
|
||||
static Table_View table() {
|
||||
using T = ecap::adminive_config::Source_Feed_Relation_Row;
|
||||
return table_view<T>(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<MLAT_Config_Data> {
|
||||
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_Config> 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 <class Function>
|
||||
adminive::Http_Response<Json> 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<double>();
|
||||
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<std::string, bool> 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<double>();
|
||||
const auto y = y_field->get<double>();
|
||||
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 <class Function>
|
||||
void complete_business_request(std::function<void(const drogon::HttpResponsePtr&)>& callback, Function&& function) noexcept {
|
||||
callback(adminive::make_drogon_response(business_response(std::forward<Function>(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<Data_Source_Type>(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<Source_Feed_Relation_Type>(value);
|
||||
if(!result)
|
||||
throw std::invalid_argument("未知关系类型: " + std::string(value));
|
||||
return *result;
|
||||
}
|
||||
Data_Source_Row source_row(const std::shared_ptr<Data_Source>& source) {
|
||||
Data_Source_Row row;
|
||||
row.key = source->key;
|
||||
@@ -548,6 +527,7 @@ Data_Feed_Row feed_row(const std::shared_ptr<Data_Feed>& 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<Data_Feed>& feed) {
|
||||
}
|
||||
void apply_feed_row(const std::shared_ptr<Data_Feed>& 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<Data_Feed>& feed, const Data_Feed_Row&
|
||||
});
|
||||
}
|
||||
}
|
||||
Source_Feed_Relation_Row relation_row(const std::shared_ptr<Source_Feed_Relation>& 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<Source_Feed_Relation>& 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 <class Row>
|
||||
adminive::Http_Response<Json> list_response(std::string path, std::vector<Row> rows, adminive::Collection_Query query) {
|
||||
adminive::Collection_Service<Row, Json> collection(std::move(path), std::move(rows));
|
||||
@@ -668,11 +629,43 @@ std::vector<Data_Feed_Row> feed_rows(Global* global) {
|
||||
rows.push_back(feed_row(feed));
|
||||
return rows;
|
||||
}
|
||||
std::vector<Source_Feed_Relation_Row> relation_rows(Global* global) {
|
||||
std::vector<Source_Feed_Relation_Row> 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<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<std::string, std::shared_ptr<Data_Source>> 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_Row> external_resource_rows(Global* global) {
|
||||
std::map<std::string, External_Resource_Status> status_by_name;
|
||||
@@ -705,11 +698,55 @@ std::vector<External_Resource_Row> external_resource_rows(Global* global) {
|
||||
Json source_amis_schema() {
|
||||
return adminive::to_amis_table_schema<Json, Data_Source_Row>("/api/adminive/config/data_sources", adminive::describe_table_view<Data_Source_Row>());
|
||||
}
|
||||
Json feed_amis_schema() {
|
||||
return adminive::to_amis_table_schema<Json, Data_Feed_Row>("/api/adminive/config/data_feeds", adminive::describe_table_view<Data_Feed_Row>());
|
||||
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<std::string>() == field_name) {
|
||||
value["type"] = "select";
|
||||
value["options"] = options;
|
||||
value["clearable"] = true;
|
||||
value["placeholder"] = "未连接";
|
||||
}
|
||||
for(auto& [key, child] : value.items()) {
|
||||
static_cast<void>(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<Json, Source_Feed_Relation_Row>("/api/adminive/config/source_feed_relations", adminive::describe_table_view<Source_Feed_Relation_Row>());
|
||||
Json feed_amis_schema(Global* global) {
|
||||
auto schema = adminive::to_amis_table_schema<Json, Data_Feed_Row>("/api/adminive/config/data_feeds", adminive::describe_table_view<Data_Feed_Row>());
|
||||
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<Json, External_Resource_Row>("/api/adminive/config/external_resources", adminive::describe_table_view<External_Resource_Row>());
|
||||
@@ -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<void(const drogon::HttpResponsePtr&)>&& 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<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
complete_business_request(callback, [this] {
|
||||
std::lock_guard lock(topology_mutex_);
|
||||
return adminive::make_http_success<Json>(data_topology_schema(global_));
|
||||
});
|
||||
}, get);
|
||||
app.registerHandler(data_topology_path, [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
complete_business_request(callback, [this] {
|
||||
std::lock_guard lock(topology_mutex_);
|
||||
return adminive::make_http_success<Json>(data_topology(global_));
|
||||
});
|
||||
}, get);
|
||||
app.registerHandler(data_topology_path + "/view", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
complete_business_request(callback, [this] {
|
||||
std::lock_guard lock(topology_mutex_);
|
||||
return adminive::make_http_success<Json>(data_topology_view(global_));
|
||||
});
|
||||
}, get);
|
||||
app.registerHandler(data_topology_path + "/view", [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& 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<Json>(data_topology_view(global_), "saved");
|
||||
});
|
||||
}, put);
|
||||
app.registerHandler(data_topology_path + "/status/data_source/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& 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<Json>(adminive::parse_json<Json>(source->get_state().to_json_string()));
|
||||
});
|
||||
}, get);
|
||||
app.registerHandler(data_topology_path + "/status/data_feed/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& 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<Json>(adminive::parse_json<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<void(const drogon::HttpResponsePtr&)>&& 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<Json>(adminive::to_frontend_json<Json>(source_row(source)), "created");
|
||||
});
|
||||
@@ -901,7 +983,7 @@ private:
|
||||
if(!result.success)
|
||||
return adminive::make_http_error<Json>(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<Json>(adminive::to_frontend_json<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<Json>(adminive::json_object<Json>(), "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<Json>(adminive::json_object<Json>(), "reordered");
|
||||
});
|
||||
@@ -943,7 +1025,7 @@ private:
|
||||
const auto remove = adminive::make_drogon_constraints(drogon::Delete, {});
|
||||
bind_collection_read_handlers<Data_Feed_Row>(app, data_feeds_path,
|
||||
[] { return adminive::to_descriptor_json<Json, Data_Feed_Row>(); },
|
||||
[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<void(const drogon::HttpResponsePtr&)>&& 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<Json>(422, result);
|
||||
if(global_->mode_acs.data_feed_config.map.get(row.key))
|
||||
return adminive::make_http_error<Json>(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<Json>(adminive::to_frontend_json<Json>(feed_row(feed)), "created");
|
||||
});
|
||||
@@ -973,8 +1057,10 @@ private:
|
||||
const auto result = adminive::apply_frontend_patch<Json>(row, input);
|
||||
if(!result.success)
|
||||
return adminive::make_http_error<Json>(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<Json>(adminive::to_frontend_json<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<Json>(adminive::json_object<Json>(), "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<Json>(adminive::json_object<Json>(), "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<Source_Feed_Relation_Row>(app, relations_path,
|
||||
[] { return adminive::to_descriptor_json<Json, Source_Feed_Relation_Row>(); },
|
||||
[this] { return relation_rows(global_); }, [] { return relation_amis_schema(); }, true);
|
||||
app.registerHandler(relations_path, [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& 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<Json>(row, input);
|
||||
if(!result.success)
|
||||
return adminive::make_http_error<Json>(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<Json>(adminive::to_frontend_json<Json>(relation_row(relation)), "created");
|
||||
});
|
||||
}, post);
|
||||
const auto update = [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& 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<Json>(row, input);
|
||||
if(!result.success)
|
||||
return adminive::make_http_error<Json>(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<Json>(adminive::to_frontend_json<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<void(const drogon::HttpResponsePtr&)>&& 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<Json>(adminive::json_object<Json>(), "deleted");
|
||||
});
|
||||
}, remove);
|
||||
app.registerHandler(relations_path + "/order", [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& 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<Json>(adminive::json_object<Json>(), "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<adminive::Composition_Slot_Contract> 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<std::string, Json> 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";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Config.h"
|
||||
#include "Config_Default.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
@@ -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<std::string, std::string> 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<double>("zoom"); value.has_value() && std::isfinite(value.value()) && value.value() > 0.0)
|
||||
zoom = value.value();
|
||||
if (const auto value = that_json->try_get_number<double>("pan_x"); value.has_value() && std::isfinite(value.value()))
|
||||
pan_x = value.value();
|
||||
if (const auto value = that_json->try_get_number<double>("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<double>("x");
|
||||
const auto y = item.try_get_number<double>("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());
|
||||
|
||||
@@ -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<std::string, Data_Topology_Node_Position> node_positions;
|
||||
void from_json(const Psc::JSON* that_json);
|
||||
[[nodiscard]] Psc::JSON to_json() const;
|
||||
};
|
||||
namespace adminive {
|
||||
template <>
|
||||
struct Type_Descriptor<Data_Topology_Node_Position> {
|
||||
static auto get() {
|
||||
using T = Data_Topology_Node_Position;
|
||||
return object<T>("data_topology_node_position", "拓扑节点位置",
|
||||
ADMINIVE_FIELD_LABEL(T, x, "X"),
|
||||
ADMINIVE_FIELD_LABEL(T, y, "Y"));
|
||||
}
|
||||
};
|
||||
template <>
|
||||
struct Type_Descriptor<Data_Topology_View_Config> {
|
||||
static auto get() {
|
||||
using T = Data_Topology_View_Config;
|
||||
return object<T>("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<Mode_ACS_Config_Data> {
|
||||
static auto get() {
|
||||
using T = Mode_ACS_Config_Data;
|
||||
@@ -121,31 +154,15 @@ struct Mode_ACS_Config {
|
||||
adminive::Managed_Value<Mode_ACS_Config_Data> 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_Config> 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 {
|
||||
|
||||
@@ -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<void>(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);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#ifndef SOURCE_FEED_RELATION_H
|
||||
#define SOURCE_FEED_RELATION_H
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#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<Source_Feed_Relation_Data> {
|
||||
static auto get() {
|
||||
using T = Source_Feed_Relation_Data;
|
||||
return object<T>("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<Source_Feed_Relation_Data> settings;
|
||||
void from_json(const Psc::JSON* that_json);
|
||||
[[nodiscard]] Psc::JSON to_json() const;
|
||||
};
|
||||
inline std::shared_ptr<Source_Feed_Relation> 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<Source_Feed_Relation>();
|
||||
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<std::string, std::shared_ptr<Source_Feed_Relation>> 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
|
||||
Reference in New Issue
Block a user