Files
ECAP_Server/module/Local_Server/server/Adminive_Config.cpp
T
2026-08-09 18:06:09 +08:00

1192 lines
74 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 <cstdint>
#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;
};
struct External_Resource_Row {
std::string name;
std::string description;
std::string download_url;
std::uint64_t update_interval_seconds{};
std::int64_t last_updated_at{};
std::size_t row_count{};
std::string state;
};
}
namespace adminive {
template <>
struct Type_Descriptor<ecap::adminive_config::External_Resource_Row> {
static auto get() {
using T = ecap::adminive_config::External_Resource_Row;
return object<T>("external_resource", "外部数据源管理",
ADMINIVE_FIELD_LABEL(T, name, "数据表"),
ADMINIVE_FIELD_LABEL(T, description, "说明"),
ADMINIVE_FIELD_LABEL(T, download_url, "在线来源"),
ADMINIVE_FIELD_LABEL(T, update_interval_seconds, "建议更新周期(秒)"),
ADMINIVE_FIELD_LABEL(T, last_updated_at, "上次更新时间"),
ADMINIVE_FIELD_LABEL(T, row_count, "行数"),
ADMINIVE_FIELD_LABEL(T, state, "状态"));
}
};
template <>
struct Type_View_Descriptor<ecap::adminive_config::External_Resource_Row> {
static Table_View table() {
using T = ecap::adminive_config::External_Resource_Row;
return table_view<T>(column<&T::name>("数据表").search(), column<&T::description>("说明"), column<&T::download_url>("在线来源"), column<&T::row_count>("行数").sort(), column<&T::last_updated_at>("上次更新时间").sort(), column<&T::update_interval_seconds>("建议更新周期(秒)"), column<&T::state>("状态")).titled("外部数据源管理");
}
};
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;
}
adminive::Resource_Transaction<Map_View_Config> map_view_transaction(Global* global) {
adminive::Resource_Transaction<Map_View_Config> transaction;
transaction.prepare = [global](const Map_View_Config& value, const adminive::Request_Context&) {
if(!Map_View_Config::is_scene_mode(value.scene_mode))
throw adminive::Field_Validation_Error("scene_mode", "场景模式必须为 2d 或 3d");
global->map_resources_config.read([&](const auto& resources) {
if(!resources.is_imagery_key(value.map2d.current_imagery_key))
throw adminive::Field_Validation_Error("map2d.current_imagery_key", "影像源不存在");
if(!resources.is_terrain_key(value.map2d.current_terrain_key))
throw adminive::Field_Validation_Error("map2d.current_terrain_key", "地形源不存在");
if(!resources.is_imagery_key(value.map3d.current_imagery_key))
throw adminive::Field_Validation_Error("map3d.current_imagery_key", "影像源不存在");
if(!resources.is_terrain_key(value.map3d.current_terrain_key))
throw adminive::Field_Validation_Error("map3d.current_terrain_key", "地形源不存在");
});
};
transaction.commit = [global](const Map_View_Config& value, const adminive::Request_Context&) {
global->map_resources_config.write([&](auto& resources) {
resources.current_imagery_key = value.scene_mode == "2d" ? value.map2d.current_imagery_key : value.map3d.current_imagery_key;
});
Config::save(Config_Section::map_view, value.to_base_json());
Config::save(Config_Section::map_resources);
};
transaction.rollback = [global](const Map_View_Config& value, const adminive::Request_Context&) {
global->map_resources_config.write([&](auto& resources) {
resources.current_imagery_key = value.scene_mode == "2d" ? value.map2d.current_imagery_key : value.map3d.current_imagery_key;
});
Config::save(Config_Section::map_view, value.to_base_json());
Config::save(Config_Section::map_resources);
};
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();
source->settings.read([&](const auto& value) {
row.config = value;
});
row.state = Psc::to_string(source->state);
if(const auto source_value = dynamic_cast<TCP_Client_Data_Source*>(source.get())) {
source_value->specific.read([&](const auto& value) {
row.ip = value.ip;
row.port = value.port;
});
} else if(const auto source_value = dynamic_cast<Serial_Data_Source*>(source.get())) {
source_value->specific.read([&](const auto& value) {
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())) {
source_value->specific.read([&](const auto& value) {
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())) {
source_value->specific.read([&](const auto& value) {
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())) {
source_value->specific.read([&](const auto& value) {
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();
feed->output_format.read([&](const auto& value) {
row.output_format = value;
row.format = value.type;
});
row.state = Psc::to_string(feed->state);
if(const auto feed_value = dynamic_cast<Data_Feed_TCP_Server*>(feed.get())) {
feed_value->specific.read([&](const auto& value) {
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())) {
feed_value->specific.read([&](const auto& value) {
row.port = value.port;
});
} else if(const auto feed_value = dynamic_cast<Data_Feed_TCP_Client*>(feed.get())) {
feed_value->specific.read([&](const auto& value) {
row.url = value.url;
row.port = value.port;
});
} else if(const auto feed_value = dynamic_cast<Data_Feed_UDP_Client*>(feed.get())) {
feed_value->specific.read([&](const auto& value) {
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;
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));
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;
}
std::vector<External_Resource_Row> external_resource_rows(Global* global) {
std::map<std::string, External_Resource_Status> status_by_name;
for(auto status : global->external_resources_manager.external_database_status())
status_by_name.emplace(status.name, std::move(status));
const auto config = global->external_resources_manager.to_base_json();
const auto* list = config.get("list");
std::vector<External_Resource_Row> rows;
if(!list)
return rows;
rows.reserve(list->children.size());
for(const auto& item : list->children) {
External_Resources_Data value;
value.from_base_json(&item, false);
External_Resource_Row row;
row.name = value.name;
row.description = value.description;
row.download_url = value.download_url;
row.update_interval_seconds = value.update_interval_seconds;
row.last_updated_at = static_cast<std::int64_t>(value.last_updated_at);
const auto status = status_by_name.find(value.name);
if(status != status_by_name.end()) {
row.row_count = status->second.row_count;
row.state = status->second.message;
}
rows.push_back(std::move(row));
}
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>());
}
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>());
auto& crud = schema["body"][0];
const std::string crud_id = "external_resource_table_crud";
crud["headerToolbar"] = Json::array({Json{{"type", "columns-toggler"}, {"draggable", true}}, "reload", Json{{"type", "button"}, {"label", "全部后端下载并导入"}, {"level", "primary"}, {"actionType", "ajax"}, {"api", Json{{"method", "post"}, {"url", "/api/refresh_external_databases"}}}, {"reload", crud_id}}});
auto& columns = crud["columns"];
if(!columns.empty())
columns.erase(columns.end() - 1);
const auto refresh_button = Json{{"type", "button"}, {"label", "后端下载并导入"}, {"level", "link"}, {"actionType", "ajax"}, {"api", Json{{"method", "post"}, {"url", "/api/refresh_external_database"}, {"data", Json{{"name", "${name}"}}}}}, {"reload", crud_id}};
const auto download_button = Json{{"type", "button"}, {"label", "前端下载"}, {"level", "link"}, {"actionType", "url"}, {"url", "${download_url}"}, {"blank", true}, {"disabledOn", "!this.download_url"}};
const auto upload_button = Json{{"type", "button"}, {"label", "离线导入"}, {"level", "link"}, {"actionType", "dialog"}, {"dialog", Json{{"title", "离线导入 ${name}"}, {"body", Json{{"type", "input-file"}, {"name", "file"}, {"label", "数据文件"}, {"accept", ".csv,.gz,.zip,.txt"}, {"receiver", Json{{"method", "post"}, {"url", "/api/upload_external_database"}, {"data", Json{{"name", "${name}"}}}}}}}}}};
const auto clear_button = Json{{"type", "button"}, {"label", "清空数据表"}, {"level", "link"}, {"className", "text-danger"}, {"actionType", "ajax"}, {"confirmText", "确定清空 ${name}"}, {"api", Json{{"method", "post"}, {"url", "/api/clear_external_database_table"}, {"data", Json{{"name", "${name}"}}}}}, {"reload", crud_id}};
columns.push_back(Json{{"type", "operation"}, {"label", "操作"}, {"buttons", Json::array({refresh_button, download_button, upload_button, clear_button})}});
return schema;
}
Json backend_config_schema() {
return Json{{"title", "后端完整配置(只读)"}, {"data_api", "/api/adminive/config/backend_config"}};
}
Json archive_operations_schema() {
const auto formats = Json::array({Json{{"label", "ZIP (.zip)"}, {"value", "zip"}, {"format", "zip"}, {"filter", ""}, {"extension", "zip"}}, Json{{"label", "TAR (.tar)"}, {"value", "tar"}, {"format", "pax"}, {"filter", ""}, {"extension", "tar"}}, Json{{"label", "TAR.GZ (.tar.gz)"}, {"value", "tar.gz"}, {"format", "pax"}, {"filter", "gzip"}, {"extension", "tar.gz"}}, Json{{"label", "TAR.XZ (.tar.xz)"}, {"value", "tar.xz"}, {"format", "pax"}, {"filter", "xz"}, {"extension", "tar.xz"}}, Json{{"label", "TAR.ZST (.tar.zst)"}, {"value", "tar.zst"}, {"format", "pax"}, {"filter", "zstd"}, {"extension", "tar.zst"}}, Json{{"label", "TAR.BZ2 (.tar.bz2)"}, {"value", "tar.bz2"}, {"format", "pax"}, {"filter", "bzip2"}, {"extension", "tar.bz2"}}, Json{{"label", "TAR.LZ4 (.tar.lz4)"}, {"value", "tar.lz4"}, {"format", "pax"}, {"filter", "lz4"}, {"extension", "tar.lz4"}}, Json{{"label", "7Z (.7z)"}, {"value", "7z"}, {"format", "7zip"}, {"filter", ""}, {"extension", "7z"}}});
return Json{{"title", "导入导出"}, {"formats", std::move(formats)}, {"exports", Json::array({Json{{"name", "版本"}, {"api", "/api/export_version"}, {"allow_version_selection", true}}, Json{{"name", "日志"}, {"api", "/api/export_logs"}, {"allow_version_selection", false}}})}, {"import_api", "/api/import_version"}, {"import_accept", ".tar.zstd,.tar.zst,.tar.gz,.tgz,.tar.xz,.txz,.tar.bz2,.tbz2,.tar.lz4,.zip,.7z,.rar,.tar,.cpio,.iso,.xar"}};
}
Json data_source_descriptor() {
auto descriptor = adminive::to_descriptor_json<Json, Data_Source_Row>();
const auto tile_view_descriptor = adminive::to_descriptor_json<Json, Map_Tile_View_Config>();
const auto map_view_descriptor = adminive::to_descriptor_json<Json, Map_View_Config>();
const auto graphics_descriptor = adminive::to_descriptor_json<Json, Cesium_Graphics_Config_Data>();
descriptor["sidebar"] = Json{
{"title", "数据源配置"},
{"common", Json{{"fields", Json::array({"update_form_gps", "base_station_has_valid_position", "keep_mode", "ignore_msg_time"})}}},
{"position", Json{{"fields", Json::array({"lat", "lon", "alt"})}}},
{"display", Json{
{"map2d", Json{
{"title", "2D显示配置"},
{"object_path", "map_display.map2d"},
{"fields", Json::array({"aircraft_show", "base_station_show", "show_icao", "show_call_sign", "show_fly_status", "aircraft_pixel_size", "base_station_pixel_size", "track_point_pixel_size", "color", "track_point_color", "base_station_color"})}
}},
{"map3d", Json{
{"title", "3D显示配置"},
{"object_path", "map_display.map3d"},
{"fields", Json::array({"aircraft_show", "base_station_show", "constant_screen_size", "show_icao", "show_call_sign", "show_fly_status", "aircraft_scale", "base_station_scale", "track_point_pixel_size", "color", "track_point_color", "base_station_color"})}
}}
}},
{"rules", Json::array({
Json{{"field", "base_station_has_valid_position"}, {"disabled_when", Json{{"field", "update_form_gps"}, {"equals", true}}}},
Json{{"field", "lat"}, {"disabled_when", Json{{"field", "update_form_gps"}, {"equals", true}}}},
Json{{"field", "lon"}, {"disabled_when", Json{{"field", "update_form_gps"}, {"equals", true}}}},
Json{{"field", "alt"}, {"disabled_when", Json{{"field", "update_form_gps"}, {"equals", true}}}}
})},
{"tiles", Json{
{"title", "瓦片显示"},
{"fields", Json::array({"tile_display_maximum_level"})},
{"descriptor_fields", tile_view_descriptor.at("fields")}
}},
{"view3d", Json{
{"graphics", Json{
{"title", "辅助显示"},
{"fields", Json::array({"surface_navigation_reference_visible", "view_axes_visible"})},
{"descriptor_fields", graphics_descriptor.at("fields")}
}},
{"map_view", Json{
{"title", "定位视图"},
{"fields", Json::array({"base_station_fly_to_height_offset_meters"})},
{"descriptor_fields", map_view_descriptor.at("fields")}
}}
}}
};
return descriptor;
}
Json cesium_models_schema(Global* global) {
Json aircraft_types = Json::array();
global->map_model_config.read([&](const auto& config) {
for(const auto& [key, value] : config.aircraft_models) {
static_cast<void>(value);
auto label = key;
for(std::uint8_t raw = 1; raw <= 21; ++raw) {
const auto type = static_cast<SSR::Vortex_Type>(raw);
if(SSR::Vortex_Type_to_key(type) == key) {
label = SSR::Vortex_Type_to_label(type);
break;
}
}
aircraft_types.push_back(Json{{"key", key}, {"label", label}});
}
});
const auto item_descriptor = adminive::to_descriptor_json<Json, Map_Model_Item_Config>();
return Json{
{"title", "Cesium 模型设置"},
{"data_api", "/map/models"},
{"upload_accept", ".glb"},
{"item_descriptor", item_descriptor},
{"groups", Json::array({
Json{{"title", "默认飞机模型"}, {"path", "aircraft_model"}, {"upload", Json{{"model_type", "aircraft"}, {"label", "上传飞机模型"}, {"success_message", "默认飞机模型已更新"}}}},
Json{{"title", "基站模型"}, {"path", "base_station_model"}, {"upload", Json{{"model_type", "base_station"}, {"label", "上传基站模型"}, {"success_message", "基站模型已更新"}}}},
Json{{"title", "设备模型"}, {"path", "device_model"}, {"upload", Json{{"model_type", "device"}, {"label", "上传设备模型"}, {"success_message", "设备模型已更新"}}}}
})},
{"collections", Json::array({
Json{{"title", "按涡流/目标类型选择飞机模型"}, {"path", "aircraft_models"}, {"upload_model_type", "aircraft_type"}, {"upload_label", "上传 GLB"}, {"upload_success_message", "分类飞机模型已更新"}, {"items", std::move(aircraft_types)}}
})}
};
}
Json system_actions_schema() {
return Json{{"type", "panel"}, {"title", "设备操作"}, {"body", Json::array({Json{{"type", "button"}, {"label", "重启 FPGA 设备"}, {"level", "primary"}, {"actionType", "ajax"}, {"confirmText", "确定重启 FPGA 设备?"}, {"api", Json{{"method", "post"}, {"url", "/api/restart_device"}}}}})}};
}
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()),
map_view_resource_(global->map_view_config, map_view_path, map_view_transaction(global)),
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);
map_view_resource_.bind(app);
mlat_resource_.bind(app);
cesium_graphics_resource_.bind(app);
bind_data_sources(app);
bind_data_feeds(app);
bind_relations(app);
bind_external_resources(app);
bind_backend_config(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_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) {
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(Json::parse(global_->toJson().to_json_string()))));
}, get);
}
void bind_external_resources(drogon::HttpAppFramework& app) {
const auto get = adminive::make_drogon_constraints(drogon::Get, {});
app.registerHandler(external_resources_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, External_Resource_Row>())));
}, get);
app.registerHandler(external_resources_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, External_Resource_Row>(adminive::describe_table_view<External_Resource_Row>()))));
}, get);
app.registerHandler(external_resources_path + "/amis", [](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(external_resource_amis_schema())));
}, get);
app.registerHandler(external_resources_path, [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
callback(adminive::make_drogon_response(list_response<External_Resource_Row>(external_resources_path, external_resource_rows(global_), read_collection_query(request))));
}, get);
}
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>(data_source_descriptor())));
}, 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("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"},
{"external_resources", "amis_schema", external_resources_path + "/descriptor", external_resources_path + "/view", external_resources_path, external_resources_path + "/amis"},
{"archive_operations", "ecap_archive_operations", {}, {}, {}, {}},
{"cesium_models", "ecap_cesium_models", {}, {}, "/map/models", {}},
{"backend_config", "ecap_backend_config", {}, {}, backend_config_path, {}},
{"system_actions", "amis_schema", {}, {}, {}, {}}
};
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()},
{"external_resources", external_resource_amis_schema()},
{"archive_operations", archive_operations_schema()},
{"cesium_models", cesium_models_schema(global_)},
{"backend_config", backend_config_schema()},
{"system_actions", system_actions_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 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";
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<Map_View_Config, Json> map_view_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();
}
}