941 lines
56 KiB
C++
941 lines
56 KiB
C++
#include "Adminive_Config.h"
|
|
#include "Global.h"
|
|
#include "adminive/adminive.hpp"
|
|
#include "adminive/adapters/drogon.hpp"
|
|
#include "adminive/adapters/magic_enum.hpp"
|
|
#include "adminive/adapters/nlohmann_json.hpp"
|
|
#include <algorithm>
|
|
#include <charconv>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
namespace ecap::adminive_config {
|
|
enum class Data_Source_Type {
|
|
Serial_Data_Source,
|
|
TCP_Client_Data_Source,
|
|
File_Data_Source,
|
|
Dll_Data_Source,
|
|
Shared_Memory_Data_Source
|
|
};
|
|
enum class Data_Feed_Type {
|
|
Data_Feed_TCP_Server,
|
|
Data_Feed_UDP_Server,
|
|
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;
|
|
bool enable{};
|
|
Data_Source_Data config;
|
|
std::string ip;
|
|
std::uint16_t port{};
|
|
std::string port_name;
|
|
Baud_Rate_Type baud_rate{};
|
|
std::string file_path;
|
|
File_Data_Type data_type = File_Data_Type::BIN_Blank_Text;
|
|
Play_Mode play_mode = Play_Mode::one;
|
|
std::string library_path;
|
|
std::string function_name;
|
|
std::size_t buffer_size{};
|
|
std::string shared_memory_name;
|
|
std::uint64_t shared_memory_size{};
|
|
std::string state;
|
|
};
|
|
struct Data_Feed_Row {
|
|
std::string key;
|
|
Data_Feed_Type type = Data_Feed_Type::Data_Feed_TCP_Server;
|
|
bool enable{};
|
|
Output_Format output_format;
|
|
Output_Data_Format format = Output_Data_Format::BIN;
|
|
std::uint16_t port{};
|
|
std::string url;
|
|
std::size_t connect_user_buffer_size = 409600;
|
|
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;
|
|
};
|
|
}
|
|
namespace adminive {
|
|
template <>
|
|
struct Type_Descriptor<ecap::adminive_config::Data_Source_Row> {
|
|
static auto get() {
|
|
using T = ecap::adminive_config::Data_Source_Row;
|
|
using E = ecap::adminive_config::Data_Source_Type;
|
|
auto type = ADMINIVE_FIELD_LABEL(T, type, "数据源类型").creatable().required().select_input();
|
|
type = type.enum_label<E::Serial_Data_Source>("串口数据源")
|
|
.enum_label<E::TCP_Client_Data_Source>("TCP 客户端数据源")
|
|
.enum_label<E::File_Data_Source>("文件数据源")
|
|
.enum_label<E::Dll_Data_Source>("DLL 数据源")
|
|
.enum_label<E::Shared_Memory_Data_Source>("共享内存数据源");
|
|
return object<T>("data_source", "数据源",
|
|
ADMINIVE_FIELD_LABEL(T, key, "名称").creatable().required().text_input(),
|
|
type,
|
|
ADMINIVE_FIELD_LABEL(T, enable, "启用").creatable().editable().boolean_input(),
|
|
ADMINIVE_FIELD_LABEL(T, config, "公共与地图配置").creatable().editable(),
|
|
ADMINIVE_FIELD_LABEL(T, ip, "IP 地址").creatable().editable().text_input().visible_on("${$self.type == 'TCP_Client_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, port, "端口").creatable().editable().number_input().visible_on("${$self.type == 'TCP_Client_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, port_name, "串口名称").creatable().editable().text_input().visible_on("${$self.type == 'Serial_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, baud_rate, "波特率").creatable().editable().select_input().visible_on("${$self.type == 'Serial_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, file_path, "文件路径").creatable().editable().text_input().visible_on("${$self.type == 'File_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, data_type, "数据格式").creatable().editable().select_input().visible_on("${$self.type == 'File_Data_Source' || $self.type == 'Dll_Data_Source' || $self.type == 'Shared_Memory_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, play_mode, "播放模式").creatable().editable().select_input().visible_on("${$self.type == 'File_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, library_path, "库路径").creatable().editable().text_input().visible_on("${$self.type == 'Dll_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, function_name, "函数名").creatable().editable().text_input().visible_on("${$self.type == 'Dll_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, buffer_size, "读取缓冲区大小").creatable().editable().number_input().visible_on("${$self.type == 'Dll_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, shared_memory_name, "共享内存名称").creatable().editable().text_input().visible_on("${$self.type == 'Shared_Memory_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, shared_memory_size, "共享内存大小").creatable().editable().number_input().visible_on("${$self.type == 'Shared_Memory_Data_Source'}"),
|
|
ADMINIVE_FIELD_LABEL(T, state, "运行状态").text_input());
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_View_Descriptor<ecap::adminive_config::Data_Source_Row> {
|
|
static Table_View table() {
|
|
using T = ecap::adminive_config::Data_Source_Row;
|
|
return table_view<T>(column<&T::key>("名称").search(), column<&T::type>("类型").filter(), column<&T::enable>("启用").filter(), column<&T::state>("状态"))
|
|
.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>()), 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>())));
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_Descriptor<ecap::adminive_config::Data_Feed_Row> {
|
|
static auto get() {
|
|
using T = ecap::adminive_config::Data_Feed_Row;
|
|
using E = ecap::adminive_config::Data_Feed_Type;
|
|
auto type = ADMINIVE_FIELD_LABEL(T, type, "数据馈送类型").creatable().required().select_input();
|
|
type = type.enum_label<E::Data_Feed_TCP_Server>("TCP 服务端")
|
|
.enum_label<E::Data_Feed_UDP_Server>("UDP 服务端")
|
|
.enum_label<E::Data_Feed_TCP_Client>("TCP 客户端")
|
|
.enum_label<E::Data_Feed_UDP_Client>("UDP 客户端");
|
|
return object<T>("data_feed", "数据馈送",
|
|
ADMINIVE_FIELD_LABEL(T, key, "名称").creatable().required().text_input(),
|
|
type,
|
|
ADMINIVE_FIELD_LABEL(T, enable, "启用").creatable().editable().boolean_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(),
|
|
ADMINIVE_FIELD_LABEL(T, url, "IP 地址").creatable().editable().text_input().visible_on("${$self.type == 'Data_Feed_TCP_Client' || $self.type == 'Data_Feed_UDP_Client'}"),
|
|
ADMINIVE_FIELD_LABEL(T, connect_user_buffer_size, "TCP 用户缓冲区").creatable().editable().number_input().visible_on("${$self.type == 'Data_Feed_TCP_Server'}"),
|
|
ADMINIVE_FIELD_LABEL(T, connect_system_buffer_size, "TCP 系统缓冲区").creatable().editable().number_input().visible_on("${$self.type == 'Data_Feed_TCP_Server'}"),
|
|
ADMINIVE_FIELD_LABEL(T, state, "运行状态").text_input());
|
|
}
|
|
};
|
|
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>("状态"))
|
|
.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>()), 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>())));
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_View_Descriptor<Mode_ACS_Config_Data> {
|
|
static Form_View edit() {
|
|
using T = Mode_ACS_Config_Data;
|
|
return edit_form<T>(vertical(
|
|
group("基础时序", grid(2, use<&T::timeout_seconds>(), use<&T::read_milliseconds>(), use<&T::air_pos_timeout>(), use<&T::surface_pos_timeout>())),
|
|
group("过滤参数", grid(2, use<&T::max_speed_m_s>(), use<&T::time_space_filter>(), use<&T::speed_filter>(), use<&T::aircraft_change_list_adsb_range_filter>(), use<&T::aircraft_change_list_adsb_range_factor>(), use<&T::adsb_theoretical_target_altitude_meters>())),
|
|
group("容量与上报", grid(2, use<&T::max_track_point_size>(), use<&T::mode_s_max_num>(), use<&T::mode_other_max_num>(), use<&T::report_data_feed_msg_mum>(), use<&T::default_min_aircraft_list_num>(), use<&T::aircraft_change_list_min_position_points>())),
|
|
group("运行开关", grid(2, use<&T::wait_process_msg>(), use<&T::ignore_df_11>(), use<&T::monitor_msg_live>())))).submit("保存 Mode ACS 配置");
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_View_Descriptor<Cesium_Graphics_Config_Data> {
|
|
static Form_View edit() {
|
|
using T = Cesium_Graphics_Config_Data;
|
|
return edit_form<T>(vertical(
|
|
group("渲染质量", grid(2, use<&T::debug_show_frames_per_second>(), use<&T::target_frame_rate>(), use<&T::use_browser_recommended_resolution>(), use<&T::resolution_scale>(), use<&T::msaa_samples>(), use<&T::fxaa>(), use<&T::maximum_screen_space_error>())),
|
|
group("光照与阴影", grid(2, use<&T::shadows>(), use<&T::enable_lighting>(), use<&T::solar_lighting>(), use<&T::solar_light_intensity>())),
|
|
group("地形", grid(2, use<&T::terrain_enabled_in_3d>(), use<&T::terrain_exaggeration>(), use<&T::terrain_limit_maximum_level>(), use<&T::terrain_maximum_level>(), use<&T::terrain_cache_tiles>(), use<&T::terrain_occlusion_enabled>(), use<&T::occlusion_sample_spacing_meters>(), use<&T::occlusion_clearance_margin_meters>())),
|
|
group("辅助显示", grid(2, use<&T::surface_navigation_reference_visible>(), use<&T::view_axes_visible>(), use<&T::performance_panel_x>(), use<&T::performance_panel_y>(), use<&T::view_axes_panel_x>(), use<&T::view_axes_panel_y>(), use<&T::view_axes_panel_size>())))).submit("保存 Cesium 图形配置");
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_View_Descriptor<Data_feed_Config_Data> {
|
|
static Form_View edit() {
|
|
using T = Data_feed_Config_Data;
|
|
return edit_form<T>(grid(2, use<&T::packet_byte_size>(), use<&T::empty_wait_milliseconds>(), use<&T::tcp_server_default_connect_user_buffer_size>(), use<&T::tcp_server_default_connect_system_buffer_size>())).submit("保存数据馈送全局配置");
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_View_Descriptor<MLAT_Config_Data> {
|
|
static Form_View edit() {
|
|
using T = MLAT_Config_Data;
|
|
return edit_form<T>(grid(2, use<&T::enable>(), use<&T::merge>(), use<&T::library_path>(), use<&T::function_name>())).submit("保存 MLAT 配置");
|
|
}
|
|
};
|
|
}
|
|
namespace ecap::adminive_config {
|
|
namespace {
|
|
using Json = nlohmann::json;
|
|
class Conflict_Error final : public std::runtime_error {
|
|
public:
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
template <class Model>
|
|
adminive::Resource_Transaction<Model> value_section_transaction(Config_Section section) {
|
|
adminive::Resource_Transaction<Model> transaction;
|
|
transaction.commit = [section](const Model& value, const adminive::Request_Context&) {
|
|
Config::save(section, value.to_base_json());
|
|
};
|
|
transaction.rollback = [section](const Model& value, const adminive::Request_Context&) {
|
|
Config::save(section, value.to_base_json());
|
|
};
|
|
return transaction;
|
|
}
|
|
adminive::Resource_Transaction<Mode_ACS_Config_Data> mode_acs_transaction() {
|
|
adminive::Resource_Transaction<Mode_ACS_Config_Data> transaction;
|
|
transaction.commit = [](const Mode_ACS_Config_Data& value, const adminive::Request_Context&) {
|
|
SSR::Monitor_Mode_ACS_Message_Num = value.monitor_msg_live;
|
|
Config::merge(Config_Section::mode_acs, value.to_base_json());
|
|
};
|
|
transaction.rollback = [](const Mode_ACS_Config_Data& value, const adminive::Request_Context&) {
|
|
SSR::Monitor_Mode_ACS_Message_Num = value.monitor_msg_live;
|
|
Config::merge(Config_Section::mode_acs, value.to_base_json());
|
|
};
|
|
return transaction;
|
|
}
|
|
adminive::Resource_Transaction<Data_feed_Config_Data> data_feed_settings_transaction() {
|
|
adminive::Resource_Transaction<Data_feed_Config_Data> transaction;
|
|
transaction.commit = [](const Data_feed_Config_Data& value, const adminive::Request_Context&) {
|
|
auto patch = Psc::JSON::object();
|
|
patch.append({"data_feed", value.to_base_json()});
|
|
Config::merge(Config_Section::mode_acs, patch);
|
|
};
|
|
transaction.rollback = [](const Data_feed_Config_Data& value, const adminive::Request_Context&) {
|
|
auto patch = Psc::JSON::object();
|
|
patch.append({"data_feed", value.to_base_json()});
|
|
Config::merge(Config_Section::mode_acs, patch);
|
|
};
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
template <class Function>
|
|
adminive::Http_Response<Json> business_response(Function&& function) noexcept {
|
|
try {
|
|
return std::forward<Function>(function)();
|
|
} catch(const Conflict_Error& error) {
|
|
return adminive::make_http_error<Json>(409, error.what());
|
|
} catch(const std::out_of_range& error) {
|
|
return adminive::make_http_error<Json>(404, error.what());
|
|
} catch(const std::invalid_argument& error) {
|
|
return adminive::make_http_error<Json>(422, error.what());
|
|
} catch(const std::exception& error) {
|
|
return adminive::make_http_error<Json>(500, error.what());
|
|
} catch(...) {
|
|
return adminive::make_http_error<Json>(500, "unknown server error");
|
|
}
|
|
}
|
|
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))));
|
|
}
|
|
adminive::Collection_Query read_collection_query(const drogon::HttpRequestPtr& request) {
|
|
adminive::Collection_Query query;
|
|
query.page = adminive::read_drogon_size_parameter(request, "page", 1);
|
|
query.per_page = adminive::read_drogon_size_parameter(request, "perPage", 20);
|
|
query.order_by = request->getParameter("orderBy");
|
|
query.order_dir = request->getParameter("orderDir");
|
|
for(const auto& [name, value] : request->getParameters()) {
|
|
if(name != "page" && name != "perPage" && name != "orderBy" && name != "orderDir")
|
|
query.fields.insert_or_assign(name, value);
|
|
}
|
|
return query;
|
|
}
|
|
std::vector<std::uint64_t> read_order_ids(std::string_view body) {
|
|
const Json input = adminive::parse_json<Json>(body);
|
|
if(!input.contains("ids"))
|
|
throw std::invalid_argument("missing ids");
|
|
const auto& value = input.at("ids");
|
|
std::vector<std::uint64_t> result;
|
|
if(value.is_array()) {
|
|
result.reserve(value.size());
|
|
for(const auto& item : value)
|
|
result.push_back(item.is_string() ? std::stoull(item.get<std::string>()) : item.get<std::uint64_t>());
|
|
return result;
|
|
}
|
|
const std::string text = value.get<std::string>();
|
|
std::size_t begin{};
|
|
while(begin < text.size()) {
|
|
const auto end = text.find(',', begin);
|
|
const auto token = std::string_view(text).substr(begin, end == std::string::npos ? text.size() - begin : end - begin);
|
|
std::uint64_t id{};
|
|
const auto [position, error] = std::from_chars(token.data(), token.data() + token.size(), id);
|
|
if(error != std::errc{} || position != token.data() + token.size())
|
|
throw std::invalid_argument("invalid order id");
|
|
result.push_back(id);
|
|
if(end == std::string::npos)
|
|
break;
|
|
begin = end + 1;
|
|
}
|
|
return result;
|
|
}
|
|
template <class Value>
|
|
std::shared_ptr<Value> item_by_id(const std::vector<std::shared_ptr<Value>>& list, std::uint64_t id) {
|
|
if(id == 0 || id > list.size())
|
|
throw std::out_of_range("record not found");
|
|
return list[id - 1];
|
|
}
|
|
template <class Value>
|
|
std::vector<std::string> ordered_keys(const std::vector<std::shared_ptr<Value>>& list, const std::vector<std::uint64_t>& ids) {
|
|
if(ids.size() != list.size())
|
|
throw std::invalid_argument("order must contain every record");
|
|
std::vector<bool> used(list.size());
|
|
std::vector<std::string> keys;
|
|
keys.reserve(ids.size());
|
|
for(const auto id : ids) {
|
|
if(id == 0 || id > list.size() || used[id - 1])
|
|
throw std::invalid_argument("invalid order id");
|
|
used[id - 1] = true;
|
|
keys.push_back(list[id - 1]->key);
|
|
}
|
|
return keys;
|
|
}
|
|
std::string source_type_name(Data_Source_Type value) {
|
|
return std::string(magic_enum::enum_name(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)
|
|
throw std::invalid_argument("未知数据源类型: " + std::string(value));
|
|
return *result;
|
|
}
|
|
Data_Feed_Type feed_type(std::string_view value) {
|
|
const auto result = magic_enum::enum_cast<Data_Feed_Type>(value);
|
|
if(!result)
|
|
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;
|
|
row.type = source_type(source->type);
|
|
row.enable = source->enabled();
|
|
row.config = source->settings.snapshot();
|
|
row.state = Psc::to_string(source->state);
|
|
if(const auto source_value = dynamic_cast<TCP_Client_Data_Source*>(source.get())) {
|
|
const auto value = source_value->specific.snapshot();
|
|
row.ip = value.ip;
|
|
row.port = value.port;
|
|
} else if(const auto source_value = dynamic_cast<Serial_Data_Source*>(source.get())) {
|
|
const auto value = source_value->specific.snapshot();
|
|
row.port_name = value.port_name;
|
|
row.baud_rate = value.baud_rate;
|
|
} else if(const auto source_value = dynamic_cast<File_Data_Source*>(source.get())) {
|
|
const auto value = source_value->specific.snapshot();
|
|
row.file_path = value.file_path;
|
|
row.data_type = value.data_type;
|
|
row.play_mode = value.play_mode;
|
|
} else if(const auto source_value = dynamic_cast<Dll_Data_Source*>(source.get())) {
|
|
const auto value = source_value->specific.snapshot();
|
|
row.library_path = value.library_path;
|
|
row.function_name = value.function_name;
|
|
row.data_type = value.data_type;
|
|
row.buffer_size = value.buffer_size;
|
|
} else if(const auto source_value = dynamic_cast<Shared_Memory_Data_Source*>(source.get())) {
|
|
const auto value = source_value->specific.snapshot();
|
|
row.shared_memory_name = value.shared_memory_name;
|
|
row.shared_memory_size = value.shared_memory_size;
|
|
row.data_type = value.data_type;
|
|
}
|
|
return row;
|
|
}
|
|
void apply_source_row(const std::shared_ptr<Data_Source>& source, const Data_Source_Row& row) {
|
|
source->set_enabled(row.enable);
|
|
source->settings.write([&](auto& value) {
|
|
value = row.config;
|
|
});
|
|
if(auto source_value = dynamic_cast<TCP_Client_Data_Source*>(source.get())) {
|
|
source_value->specific.write([&](auto& value) {
|
|
value.ip = row.ip;
|
|
value.port = row.port;
|
|
});
|
|
} else if(auto source_value = dynamic_cast<Serial_Data_Source*>(source.get())) {
|
|
source_value->specific.write([&](auto& value) {
|
|
value.port_name = row.port_name;
|
|
value.baud_rate = row.baud_rate;
|
|
});
|
|
} else if(auto source_value = dynamic_cast<File_Data_Source*>(source.get())) {
|
|
source_value->specific.write([&](auto& value) {
|
|
value.file_path = row.file_path;
|
|
value.data_type = row.data_type;
|
|
value.play_mode = row.play_mode;
|
|
});
|
|
} else if(auto source_value = dynamic_cast<Dll_Data_Source*>(source.get())) {
|
|
source_value->specific.write([&](auto& value) {
|
|
value.library_path = row.library_path;
|
|
value.function_name = row.function_name;
|
|
value.data_type = row.data_type;
|
|
value.buffer_size = row.buffer_size;
|
|
});
|
|
source_value->buffer.resize(row.buffer_size);
|
|
} else if(auto source_value = dynamic_cast<Shared_Memory_Data_Source*>(source.get())) {
|
|
source_value->specific.write([&](auto& value) {
|
|
value.shared_memory_name = row.shared_memory_name;
|
|
value.shared_memory_size = row.shared_memory_size;
|
|
value.data_type = row.data_type;
|
|
});
|
|
}
|
|
}
|
|
Data_Feed_Row feed_row(const std::shared_ptr<Data_Feed>& feed) {
|
|
Data_Feed_Row row;
|
|
row.key = feed->key;
|
|
row.type = feed_type(feed->type);
|
|
row.enable = feed->enabled();
|
|
row.output_format = feed->output_format.snapshot();
|
|
row.format = row.output_format.type;
|
|
row.state = Psc::to_string(feed->state);
|
|
if(const auto feed_value = dynamic_cast<Data_Feed_TCP_Server*>(feed.get())) {
|
|
const auto value = feed_value->specific.snapshot();
|
|
row.port = value.port;
|
|
row.connect_user_buffer_size = value.connect_user_buffer_size;
|
|
row.connect_system_buffer_size = value.connect_system_buffer_size;
|
|
} else if(const auto feed_value = dynamic_cast<Data_Feed_UDP_Server*>(feed.get())) {
|
|
row.port = feed_value->specific.member<&Data_Feed_UDP_Server_Data::port>().snapshot();
|
|
} else if(const auto feed_value = dynamic_cast<Data_Feed_TCP_Client*>(feed.get())) {
|
|
const auto value = feed_value->specific.snapshot();
|
|
row.url = value.url;
|
|
row.port = value.port;
|
|
} else if(const auto feed_value = dynamic_cast<Data_Feed_UDP_Client*>(feed.get())) {
|
|
const auto value = feed_value->specific.snapshot();
|
|
row.url = value.url;
|
|
row.port = value.port;
|
|
}
|
|
return row;
|
|
}
|
|
void apply_feed_row(const std::shared_ptr<Data_Feed>& feed, const Data_Feed_Row& row) {
|
|
feed->set_enabled(row.enable);
|
|
feed->output_format.write([&](auto& value) {
|
|
value = row.output_format;
|
|
});
|
|
if(auto feed_value = dynamic_cast<Data_Feed_TCP_Server*>(feed.get())) {
|
|
feed_value->specific.write([&](auto& value) {
|
|
value.port = row.port;
|
|
value.connect_user_buffer_size = row.connect_user_buffer_size;
|
|
value.connect_system_buffer_size = row.connect_system_buffer_size;
|
|
});
|
|
} else if(auto feed_value = dynamic_cast<Data_Feed_UDP_Server*>(feed.get())) {
|
|
feed_value->specific.member<&Data_Feed_UDP_Server_Data::port>().write([&](auto& value) {
|
|
value = row.port;
|
|
});
|
|
} else if(auto feed_value = dynamic_cast<Data_Feed_TCP_Client*>(feed.get())) {
|
|
feed_value->specific.write([&](auto& value) {
|
|
value.url = row.url;
|
|
value.port = row.port;
|
|
});
|
|
} else if(auto feed_value = dynamic_cast<Data_Feed_UDP_Client*>(feed.get())) {
|
|
feed_value->specific.write([&](auto& value) {
|
|
value.url = row.url;
|
|
value.port = row.port;
|
|
});
|
|
}
|
|
}
|
|
Source_Feed_Relation_Row relation_row(const std::shared_ptr<Source_Feed_Relation>& relation) {
|
|
Source_Feed_Relation_Row row;
|
|
const auto value = relation->settings.snapshot();
|
|
row.key = relation->key;
|
|
row.type = relation_type(relation->type);
|
|
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));
|
|
return collection.list_response(std::move(query));
|
|
}
|
|
template <class Row>
|
|
adminive::Http_Response<Json> item_response(std::string path, std::vector<Row> rows, std::uint64_t id) {
|
|
adminive::Collection_Service<Row, Json> collection(std::move(path), std::move(rows));
|
|
return collection.item_response(id);
|
|
}
|
|
std::vector<Data_Source_Row> source_rows(Global* global) {
|
|
std::vector<Data_Source_Row> rows;
|
|
for(const auto& source : global->mode_acs.data_source_config.map.list())
|
|
rows.push_back(source_row(source));
|
|
return rows;
|
|
}
|
|
std::vector<Data_Feed_Row> feed_rows(Global* global) {
|
|
std::vector<Data_Feed_Row> rows;
|
|
for(const auto& feed : global->mode_acs.data_feed_config.map.list())
|
|
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 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>());
|
|
}
|
|
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>());
|
|
}
|
|
class Adminive_Config_Resources {
|
|
public:
|
|
explicit Adminive_Config_Resources(Global* global) : global_(global),
|
|
mode_acs_resource_(global->mode_acs.settings, mode_acs_path, mode_acs_transaction()),
|
|
data_feed_settings_resource_(global->mode_acs.data_feed_config.settings, data_feed_settings_path, data_feed_settings_transaction()),
|
|
mlat_resource_(global->mlat.settings, mlat_path, value_section_transaction<MLAT_Config_Data>(Config_Section::mlat)),
|
|
cesium_graphics_resource_(global->cesium_graphics_config.settings, cesium_graphics_path, value_section_transaction<Cesium_Graphics_Config_Data>(Config_Section::cesium_graphics)) {
|
|
}
|
|
void bind() {
|
|
auto& app = drogon::app();
|
|
mode_acs_resource_.bind(app);
|
|
data_feed_settings_resource_.bind(app);
|
|
mlat_resource_.bind(app);
|
|
cesium_graphics_resource_.bind(app);
|
|
bind_data_sources(app);
|
|
bind_data_feeds(app);
|
|
bind_relations(app);
|
|
app.registerHandler(manifest_path, [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(manifest())));
|
|
}, {drogon::Get});
|
|
}
|
|
private:
|
|
void bind_data_sources(drogon::HttpAppFramework& app) {
|
|
const auto get = adminive::make_drogon_constraints(drogon::Get, {});
|
|
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, {});
|
|
app.registerHandler(data_sources_path + "/descriptor", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(adminive::to_descriptor_json<Json, Data_Source_Row>())));
|
|
}, get);
|
|
app.registerHandler(data_sources_path + "/view", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(adminive::to_view_json<Json, Data_Source_Row>(adminive::describe_table_view<Data_Source_Row>()))));
|
|
}, get);
|
|
app.registerHandler(data_sources_path + "/amis", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(source_amis_schema())));
|
|
}, get);
|
|
app.registerHandler(data_sources_path, [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(list_response<Data_Source_Row>(data_sources_path, source_rows(global_), read_collection_query(request))));
|
|
}, get);
|
|
app.registerHandler(data_sources_path + "/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
|
|
callback(adminive::make_drogon_response(item_response<Data_Source_Row>(data_sources_path, source_rows(global_), id)));
|
|
}, get);
|
|
app.registerHandler(data_sources_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_);
|
|
Json input;
|
|
try {
|
|
input = adminive::parse_json<Json>(body);
|
|
} catch(const std::exception& error) {
|
|
return adminive::make_http_error<Json>(400, error.what());
|
|
}
|
|
adminive::Json_Adapter<Json>::erase(input, "id");
|
|
Data_Source_Row row;
|
|
const auto result = adminive::apply_frontend_create<Json>(row, input);
|
|
if(!result.success)
|
|
return adminive::make_http_error<Json>(422, result);
|
|
if(global_->mode_acs.data_source_config.map.get(row.key))
|
|
return adminive::make_http_error<Json>(409, "数据源名称已存在: " + row.key);
|
|
auto source = create_data_source_from_type(source_type_name(row.type));
|
|
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();
|
|
save_data_sources(global_);
|
|
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(source_row(source)), "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.data_source_config.map.list();
|
|
auto source = item_by_id(list, id);
|
|
Json input;
|
|
try {
|
|
input = adminive::parse_json<Json>(body);
|
|
} catch(const std::exception& error) {
|
|
return adminive::make_http_error<Json>(400, error.what());
|
|
}
|
|
adminive::Json_Adapter<Json>::erase(input, "id");
|
|
auto row = source_row(source);
|
|
const auto result = adminive::apply_frontend_patch<Json>(row, input);
|
|
if(!result.success)
|
|
return adminive::make_http_error<Json>(422, result);
|
|
apply_source_row(source, row);
|
|
Source_Feed_Relation_Config::set_need_refresh();
|
|
save_data_sources(global_);
|
|
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(source_row(source)), result.message);
|
|
});
|
|
};
|
|
app.registerHandler(data_sources_path + "/{1:id}", update, put);
|
|
app.registerHandler(data_sources_path + "/{1:id}", update, patch);
|
|
app.registerHandler(data_sources_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.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);
|
|
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();
|
|
save_data_sources(global_);
|
|
return adminive::make_http_success<Json>(adminive::json_object<Json>(), "deleted");
|
|
});
|
|
}, remove);
|
|
app.registerHandler(data_sources_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.data_source_config.map.list();
|
|
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();
|
|
save_data_sources(global_);
|
|
return adminive::make_http_success<Json>(adminive::json_object<Json>(), "reordered");
|
|
});
|
|
}, post);
|
|
}
|
|
void bind_data_feeds(drogon::HttpAppFramework& app) {
|
|
const auto get = adminive::make_drogon_constraints(drogon::Get, {});
|
|
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, {});
|
|
app.registerHandler(data_feeds_path + "/descriptor", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(adminive::to_descriptor_json<Json, Data_Feed_Row>())));
|
|
}, get);
|
|
app.registerHandler(data_feeds_path + "/view", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(adminive::to_view_json<Json, Data_Feed_Row>(adminive::describe_table_view<Data_Feed_Row>()))));
|
|
}, get);
|
|
app.registerHandler(data_feeds_path + "/amis", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(feed_amis_schema())));
|
|
}, get);
|
|
app.registerHandler(data_feeds_path, [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(list_response<Data_Feed_Row>(data_feeds_path, feed_rows(global_), read_collection_query(request))));
|
|
}, get);
|
|
app.registerHandler(data_feeds_path + "/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
|
|
callback(adminive::make_drogon_response(item_response<Data_Feed_Row>(data_feeds_path, feed_rows(global_), id)));
|
|
}, get);
|
|
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_);
|
|
Json input;
|
|
try {
|
|
input = adminive::parse_json<Json>(body);
|
|
} catch(const std::exception& error) {
|
|
return adminive::make_http_error<Json>(400, error.what());
|
|
}
|
|
adminive::Json_Adapter<Json>::erase(input, "id");
|
|
Data_Feed_Row row;
|
|
const auto result = adminive::apply_frontend_create<Json>(row, input);
|
|
if(!result.success)
|
|
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);
|
|
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();
|
|
save_data_feeds(global_);
|
|
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(feed_row(feed)), "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.data_feed_config.map.list();
|
|
auto feed = item_by_id(list, id);
|
|
Json input;
|
|
try {
|
|
input = adminive::parse_json<Json>(body);
|
|
} catch(const std::exception& error) {
|
|
return adminive::make_http_error<Json>(400, error.what());
|
|
}
|
|
adminive::Json_Adapter<Json>::erase(input, "id");
|
|
auto row = feed_row(feed);
|
|
const auto result = adminive::apply_frontend_patch<Json>(row, input);
|
|
if(!result.success)
|
|
return adminive::make_http_error<Json>(422, result);
|
|
apply_feed_row(feed, row);
|
|
Source_Feed_Relation_Config::set_need_refresh();
|
|
save_data_feeds(global_);
|
|
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(feed_row(feed)), result.message);
|
|
});
|
|
};
|
|
app.registerHandler(data_feeds_path + "/{1:id}", update, put);
|
|
app.registerHandler(data_feeds_path + "/{1:id}", update, patch);
|
|
app.registerHandler(data_feeds_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.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();
|
|
save_data_feeds(global_);
|
|
return adminive::make_http_success<Json>(adminive::json_object<Json>(), "deleted");
|
|
});
|
|
}, remove);
|
|
app.registerHandler(data_feeds_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.data_feed_config.map.list();
|
|
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();
|
|
save_data_feeds(global_);
|
|
return adminive::make_http_success<Json>(adminive::json_object<Json>(), "reordered");
|
|
});
|
|
}, post);
|
|
}
|
|
void bind_relations(drogon::HttpAppFramework& app) {
|
|
const auto get = adminive::make_drogon_constraints(drogon::Get, {});
|
|
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, {});
|
|
app.registerHandler(relations_path + "/descriptor", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(adminive::to_descriptor_json<Json, Source_Feed_Relation_Row>())));
|
|
}, get);
|
|
app.registerHandler(relations_path + "/view", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(adminive::to_view_json<Json, Source_Feed_Relation_Row>(adminive::describe_table_view<Source_Feed_Relation_Row>()))));
|
|
}, get);
|
|
app.registerHandler(relations_path + "/amis", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(relation_amis_schema())));
|
|
}, get);
|
|
app.registerHandler(relations_path, [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
callback(adminive::make_drogon_response(list_response<Source_Feed_Relation_Row>(relations_path, relation_rows(global_), read_collection_query(request))));
|
|
}, get);
|
|
app.registerHandler(relations_path + "/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
|
|
callback(adminive::make_drogon_response(item_response<Source_Feed_Relation_Row>(relations_path, relation_rows(global_), id)));
|
|
}, get);
|
|
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_);
|
|
Json input;
|
|
try {
|
|
input = adminive::parse_json<Json>(body);
|
|
} catch(const std::exception& error) {
|
|
return adminive::make_http_error<Json>(400, error.what());
|
|
}
|
|
adminive::Json_Adapter<Json>::erase(input, "id");
|
|
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);
|
|
Json input;
|
|
try {
|
|
input = adminive::parse_json<Json>(body);
|
|
} catch(const std::exception& error) {
|
|
return adminive::make_http_error<Json>(400, error.what());
|
|
}
|
|
adminive::Json_Adapter<Json>::erase(input, "id");
|
|
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("cesium_graphics"))).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"},
|
|
{"cesium_graphics", "amis_schema", cesium_graphics_path + "/descriptor", cesium_graphics_path + "/view", cesium_graphics_path + "/data", cesium_graphics_path + "/amis"}
|
|
};
|
|
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()},
|
|
{"cesium_graphics", cesium_graphics_resource_.amis_schema()}
|
|
};
|
|
return adminive::to_composition_manifest_json<Json>(view, contracts, schemas);
|
|
}
|
|
static inline const std::string mode_acs_path = "/api/adminive/config/mode_acs";
|
|
static inline const std::string data_feed_settings_path = "/api/adminive/config/data_feed_settings";
|
|
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 mlat_path = "/api/adminive/config/mlat";
|
|
static inline const std::string cesium_graphics_path = "/api/adminive/config/cesium_graphics";
|
|
static inline const std::string manifest_path = "/api/adminive/config/manifest";
|
|
Global* global_;
|
|
std::mutex topology_mutex_;
|
|
adminive::Drogon_Resource<Mode_ACS_Config_Data, Json> mode_acs_resource_;
|
|
adminive::Drogon_Resource<Data_feed_Config_Data, Json> data_feed_settings_resource_;
|
|
adminive::Drogon_Resource<MLAT_Config_Data, Json> mlat_resource_;
|
|
adminive::Drogon_Resource<Cesium_Graphics_Config_Data, Json> cesium_graphics_resource_;
|
|
};
|
|
}
|
|
void register_adminive_config(Global* global) {
|
|
static auto resources = std::make_unique<Adminive_Config_Resources>(global);
|
|
resources->bind();
|
|
}
|
|
}
|