Files
ECAP_Server/module/Local_Server/server/Adminive_Config.cpp
T
2026-08-10 00:08:47 +08:00

1148 lines
69 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 <cmath>
#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
};
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{};
std::string source_key;
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 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, source_key, "连接的数据源").creatable().editable().select_input(),
ADMINIVE_FIELD_LABEL(T, output_format, "输出格式").creatable().editable(),
ADMINIVE_FIELD_LABEL(T, format, "数据格式").select_input(),
ADMINIVE_FIELD_LABEL(T, port, "端口").creatable().editable().number_input(),
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::source_key>("连接的数据源").filter(), column<&T::format>("输出格式").filter(), column<&T::port>("端口").sort(), column<&T::enable>("启用").filter(), column<&T::state>("状态"))
.titled("数据馈送")
.id_title("序号")
.create_title("添加数据馈送")
.edit_title("编辑")
.delete_title("删除")
.delete_confirmation("确定删除该数据馈送?对应拓扑连接会一并删除。")
.reorderable()
.create_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>(), use<&T::source_key>()), use<&T::output_format>(), grid(2, use<&T::url>(), use<&T::port>(), use<&T::connect_user_buffer_size>(), use<&T::connect_system_buffer_size>())))
.edit_layout(vertical(grid(2, use<&T::key>(), use<&T::type>(), use<&T::enable>(), use<&T::source_key>(), use<&T::state>()), use<&T::output_format>(), grid(2, use<&T::url>(), use<&T::port>(), use<&T::connect_user_buffer_size>(), use<&T::connect_system_buffer_size>())));
}
};
template <>
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::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::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>())).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::merge>())).submit("保存 MLAT 配置");
}
};
}
namespace ecap::adminive_config {
namespace {
using Json = nlohmann::json;
const std::string data_topology_path = "/api/adminive/config/data_topology";
class Conflict_Error final : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class Bad_Request_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 = transaction.commit;
return transaction;
}
adminive::Resource_Transaction<Mode_ACS_Config_Data> mode_acs_transaction() {
adminive::Resource_Transaction<Mode_ACS_Config_Data> transaction;
const auto persist = [](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.commit = persist;
transaction.rollback = persist;
return transaction;
}
adminive::Resource_Transaction<Data_feed_Config_Data> data_feed_settings_transaction() {
adminive::Resource_Transaction<Data_feed_Config_Data> transaction;
const auto persist = [](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.commit = persist;
transaction.rollback = persist;
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", "地形源不存在");
});
};
const auto persist = [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.commit = persist;
transaction.rollback = persist;
return transaction;
}
void save_data_sources(Global* global) {
Config::save(Config_Section::mode_acs, global->mode_acs.to_base_json());
}
void save_data_feeds(Global* global) {
Config::save(Config_Section::mode_acs, global->mode_acs.to_base_json());
}
template <class Function>
adminive::Http_Response<Json> business_response(Function&& function) noexcept {
try {
return std::forward<Function>(function)();
} catch(const Bad_Request_Error& error) {
return adminive::make_http_error<Json>(400, error.what());
} 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");
}
}
Json read_request_body(std::string_view body) {
try {
auto input = adminive::parse_json<Json>(body);
adminive::Json_Adapter<Json>::erase(input, "id");
return input;
} catch(const std::exception& error) {
throw Bad_Request_Error(error.what());
}
}
Data_Topology_View_Config read_topology_view(Json input, Global* global) {
if(input.value("protocol", std::string{}) != "ecap.data-topology-view" || input.value("protocol_version", 0) != 1)
throw std::invalid_argument("数据拓扑视图协议或版本不受支持");
const auto finite_number = [&input](std::string_view name) {
const auto field = input.find(std::string(name));
if(field == input.end() || !field->is_number())
throw std::invalid_argument("数据拓扑视图缺少数值字段: " + std::string(name));
const auto value = field->get<double>();
if(!std::isfinite(value))
throw std::invalid_argument("数据拓扑视图字段不是有限数值: " + std::string(name));
return value;
};
Data_Topology_View_Config result;
result.initialized = true;
result.zoom = finite_number("zoom");
result.pan_x = finite_number("pan_x");
result.pan_y = finite_number("pan_y");
if(result.zoom <= 0.0 || result.zoom > 100.0)
throw std::invalid_argument("数据拓扑视图参数无效");
std::map<std::string, bool> valid_nodes;
for(const auto& source : global->mode_acs.data_source_config.map.list())
valid_nodes.emplace("data_source:" + source->key, true);
for(const auto& feed : global->mode_acs.data_feed_config.map.list())
valid_nodes.emplace("data_feed:" + feed->key, true);
const auto positions = input.find("node_positions");
if(positions == input.end() || !positions->is_object())
throw std::invalid_argument("节点位置必须是对象");
for(const auto& [id, position] : positions->items()) {
if(!valid_nodes.contains(id) || !position.is_object())
continue;
const auto x_field = position.find("x");
const auto y_field = position.find("y");
if(x_field == position.end() || y_field == position.end() || !x_field->is_number() || !y_field->is_number())
throw std::invalid_argument("节点位置缺少数值坐标: " + id);
const auto x = x_field->get<double>();
const auto y = y_field->get<double>();
if(!std::isfinite(x) || !std::isfinite(y))
throw std::invalid_argument("节点位置不是有限数值: " + id);
result.node_positions.emplace(id, Data_Topology_Node_Position{x, y});
}
return result;
}
template <class Function>
void complete_business_request(std::function<void(const drogon::HttpResponsePtr&)>& callback, Function&& function) noexcept {
callback(adminive::make_drogon_response(business_response(std::forward<Function>(function))));
}
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));
}
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;
}
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();
row.source_key = feed->source_key;
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->source_key = row.source_key;
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;
});
}
}
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);
}
template <class Row, class Descriptor_Factory, class Rows_Factory, class Schema_Factory>
void bind_collection_read_handlers(drogon::HttpAppFramework& app, std::string path,
Descriptor_Factory descriptor_factory, Rows_Factory rows_factory,
Schema_Factory schema_factory, bool bind_item) {
const auto get = adminive::make_drogon_constraints(drogon::Get, {});
app.registerHandler(path + "/descriptor", [descriptor_factory](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(descriptor_factory())));
}, get);
app.registerHandler(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, Row>(adminive::describe_table_view<Row>()))));
}, get);
app.registerHandler(path + "/amis", [schema_factory](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(schema_factory())));
}, get);
app.registerHandler(path, [path, rows_factory](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
callback(adminive::make_drogon_response(list_response<Row>(path, rows_factory(), read_collection_query(request))));
}, get);
if(bind_item) {
app.registerHandler(path + "/{1:id}", [path, rows_factory](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
callback(adminive::make_drogon_response(item_response<Row>(path, rows_factory(), id)));
}, get);
}
}
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;
}
Json data_topology_view(Global* global) {
auto result = global->mode_acs.data_topology_view.read([](const auto& value) {
return adminive::parse_json<Json>(value.to_json().to_json_string());
});
result["protocol"] = "ecap.data-topology-view";
result["protocol_version"] = 1;
return result;
}
Json data_topology(Global* global) {
const auto sources = global->mode_acs.data_source_config.map.list();
const auto feeds = global->mode_acs.data_feed_config.map.list();
std::map<std::string, std::shared_ptr<Data_Source>> source_by_key;
Json nodes = Json::array();
Json edges = Json::array();
for(std::size_t index = 0; index < sources.size(); ++index) {
const auto& source = sources[index];
const auto id = "data_source:" + source->key;
source_by_key.emplace(source->key, source);
nodes.push_back(Json{{"id", id}, {"kind", "data_source"}, {"entity_id", index + 1},
{"key", source->key}, {"label", source->key}, {"type", source->type},
{"enabled", source->enabled()}, {"status_api", data_topology_path + "/status/data_source/" + std::to_string(index + 1)}});
}
for(std::size_t index = 0; index < feeds.size(); ++index) {
const auto& feed = feeds[index];
const auto id = "data_feed:" + feed->key;
nodes.push_back(Json{{"id", id}, {"kind", "data_feed"}, {"entity_id", index + 1},
{"key", feed->key}, {"label", feed->key}, {"type", feed->type},
{"enabled", feed->enabled()}, {"status_api", data_topology_path + "/status/data_feed/" + std::to_string(index + 1)}});
const auto source = source_by_key.find(feed->source_key);
if(source != source_by_key.end()) {
edges.push_back(Json{{"id", "source-feed:" + feed->key},
{"source", "data_source:" + feed->source_key}, {"target", id},
{"active", source->second->enabled() && feed->enabled()}});
}
}
return Json{{"protocol", "ecap.data-topology"}, {"protocol_version", 1},
{"nodes", std::move(nodes)}, {"edges", std::move(edges)}, {"view", data_topology_view(global)}};
}
std::vector<External_Resource_Row> external_resource_rows(Global* global) {
std::map<std::string, External_Resource_Status> status_by_name;
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>());
}
void set_select_options(Json& value, std::string_view field_name, const Json& options) {
if(value.is_object()) {
const auto name = value.find("name");
if(name != value.end() && name->is_string() && name->get<std::string>() == field_name) {
value["type"] = "select";
value["options"] = options;
value["clearable"] = true;
value["placeholder"] = "未连接";
}
for(auto& [key, child] : value.items()) {
static_cast<void>(key);
set_select_options(child, field_name, options);
}
} else if(value.is_array()) {
for(auto& child : value)
set_select_options(child, field_name, options);
}
}
Json feed_amis_schema(Global* global) {
auto schema = adminive::to_amis_table_schema<Json, Data_Feed_Row>("/api/adminive/config/data_feeds", adminive::describe_table_view<Data_Feed_Row>());
Json options = Json::array();
for(const auto& source : global->mode_acs.data_source_config.map.list())
options.push_back(Json{{"label", source->key}, {"value", source->key}});
set_select_options(schema, "source_key", options);
return schema;
}
Json data_topology_schema(Global* global) {
return Json{
{"title", "数据源与数据馈送拓扑"},
{"data_api", data_topology_path},
{"view_api", data_topology_path + "/view"},
{"graph", Json{
{"height", 620},
{"layout", Json{{"type", "dagre"}, {"rankdir", "LR"}, {"align", "UL"}, {"nodesep", 34}, {"ranksep", 100}}},
{"behaviors", Json::array({"drag-canvas", "zoom-canvas", "drag-element"})},
{"active_edge_color", "#52c41a"},
{"inactive_edge_color", "#bfbfbf"}
}},
{"node_kinds", Json::array({
Json{{"kind", "data_source"}, {"title", "数据源"}, {"fill", "#e6f4ff"}, {"stroke", "#1677ff"}},
Json{{"kind", "data_feed"}, {"title", "数据馈送"}, {"fill", "#f6ffed"}, {"stroke", "#52c41a"}}
})},
{"status", Json{{"title", "运行状态"}, {"poll_interval_ms", 1000},
{"collapsed_hint", "展开后才向后台轮询,收起立即停止"}}},
{"editors", Json::array({
Json{{"kind", "data_source"}, {"title", "数据源管理"}, {"button_label", "管理数据源"}, {"schema", source_amis_schema()}},
Json{{"kind", "data_feed"}, {"title", "数据馈送管理"}, {"button_label", "管理数据馈送"}, {"schema", feed_amis_schema(global)}}
})}
};
}
Json external_resource_amis_schema() {
auto schema = adminive::to_amis_table_schema<Json, External_Resource_Row>("/api/adminive/config/external_resources", adminive::describe_table_view<External_Resource_Row>());
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{
{"source", Json{
{"title", "数据源配置"},
{"sections", Json::array({
Json{{"layout", Json::array({Json{{"kind", "cards"}, {"fields", Json::array({"update_form_gps", "base_station_has_valid_position", "keep_mode", "ignore_msg_time"})}}})}},
Json{{"layout", Json::array({Json{{"kind", "vertical"}, {"fields", Json::array({"lat", "lon", "alt"})}}})}},
Json{
{"title", "2D显示配置"},
{"modes", Json::array({"map2d"})},
{"object_path", "map_display.map2d"},
{"layout", Json::array({
Json{{"kind", "cards"}, {"fields", Json::array({"aircraft_show", "base_station_show", "show_icao", "show_call_sign", "show_fly_status"})}},
Json{{"kind", "vertical"}, {"fields", Json::array({"aircraft_pixel_size", "base_station_pixel_size", "track_point_pixel_size", "color", "track_point_color", "base_station_color"})}}
})}
},
Json{
{"title", "3D显示配置"},
{"modes", Json::array({"map3d"})},
{"object_path", "map_display.map3d"},
{"layout", Json::array({
Json{{"kind", "cards"}, {"fields", Json::array({"aircraft_show", "base_station_show", "constant_screen_size", "show_icao", "show_call_sign", "show_fly_status"})}},
Json{{"kind", "vertical"}, {"fields", Json::array({"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", "瓦片显示"},
{"layout", Json::array({Json{{"kind", "vertical"}, {"fields", Json::array({"tile_display_maximum_level"})}}})},
{"descriptor_fields", tile_view_descriptor.at("fields")}
}},
{"view3d", Json{
{"graphics", Json{
{"title", "辅助显示"},
{"layout", Json::array({Json{{"kind", "cards"}, {"fields", Json::array({"surface_navigation_reference_visible", "view_axes_visible"})}}})},
{"descriptor_fields", graphics_descriptor.at("fields")}
}},
{"map_view", Json{
{"title", "定位视图"},
{"layout", Json::array({Json{{"kind", "vertical"}, {"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_data_topology(app);
bind_external_resources(app);
bind_backend_config(app);
app.registerHandler(manifest_path, [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
callback(adminive::make_drogon_response(adminive::make_http_success<Json>(manifest())));
}, {drogon::Get});
}
private:
void bind_data_topology(drogon::HttpAppFramework& app) {
const auto get = adminive::make_drogon_constraints(drogon::Get, {});
const auto put = adminive::make_drogon_constraints(drogon::Put, {});
app.registerHandler(data_topology_path + "/schema", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_business_request(callback, [this] {
std::lock_guard lock(topology_mutex_);
return adminive::make_http_success<Json>(data_topology_schema(global_));
});
}, get);
app.registerHandler(data_topology_path, [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_business_request(callback, [this] {
std::lock_guard lock(topology_mutex_);
return adminive::make_http_success<Json>(data_topology(global_));
});
}, get);
app.registerHandler(data_topology_path + "/view", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_business_request(callback, [this] {
std::lock_guard lock(topology_mutex_);
return adminive::make_http_success<Json>(data_topology_view(global_));
});
}, get);
app.registerHandler(data_topology_path + "/view", [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_business_request(callback, [this, body = std::string(request->getBody())] {
std::lock_guard lock(topology_mutex_);
auto value = read_topology_view(read_request_body(body), global_);
global_->mode_acs.data_topology_view.write([&](auto& current) { current = std::move(value); });
Config::save(Config_Section::mode_acs, global_->mode_acs.to_base_json());
return adminive::make_http_success<Json>(data_topology_view(global_), "saved");
});
}, put);
app.registerHandler(data_topology_path + "/status/data_source/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id] {
std::lock_guard lock(topology_mutex_);
const auto source = item_by_id(global_->mode_acs.data_source_config.map.list(), id);
return adminive::make_http_success<Json>(adminive::parse_json<Json>(source->get_state().to_json_string()));
});
}, get);
app.registerHandler(data_topology_path + "/status/data_feed/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id] {
std::lock_guard lock(topology_mutex_);
const auto feed = item_by_id(global_->mode_acs.data_feed_config.map.list(), id);
return adminive::make_http_success<Json>(adminive::parse_json<Json>(feed->get_state_json().to_json_string()));
});
}, get);
}
void bind_backend_config(drogon::HttpAppFramework& app) {
const auto get = adminive::make_drogon_constraints(drogon::Get, {});
app.registerHandler(backend_config_path, [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
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) {
bind_collection_read_handlers<External_Resource_Row>(app, external_resources_path,
[] { return adminive::to_descriptor_json<Json, External_Resource_Row>(); },
[this] { return external_resource_rows(global_); },
[] { return external_resource_amis_schema(); }, false);
}
void bind_data_sources(drogon::HttpAppFramework& app) {
const auto post = adminive::make_drogon_constraints(drogon::Post, {});
const auto put = adminive::make_drogon_constraints(drogon::Put, {});
const auto patch = adminive::make_drogon_constraints(drogon::Patch, {});
const auto remove = adminive::make_drogon_constraints(drogon::Delete, {});
bind_collection_read_handlers<Data_Source_Row>(app, data_sources_path,
[] { return data_source_descriptor(); }, [this] { return source_rows(global_); },
[] { return source_amis_schema(); }, true);
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_);
auto input = read_request_body(body);
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);
Data_feed_Config::set_connections_need_refresh();
save_data_sources(global_);
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(source_row(source)), "created");
});
}, 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);
auto input = read_request_body(body);
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);
Data_feed_Config::set_connections_need_refresh();
save_data_sources(global_);
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(source_row(source)), result.message);
});
};
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.data_feed_config.source_has_connection(source->key))
throw Conflict_Error("数据源仍被数据馈送连接,不能删除: " + source->key);
source->async_stop();
if(!global_->mode_acs.data_source_config.map.remove(id - 1))
throw std::out_of_range("record not found");
Data_feed_Config::set_connections_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");
Data_feed_Config::set_connections_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 post = adminive::make_drogon_constraints(drogon::Post, {});
const auto put = adminive::make_drogon_constraints(drogon::Put, {});
const auto patch = adminive::make_drogon_constraints(drogon::Patch, {});
const auto remove = adminive::make_drogon_constraints(drogon::Delete, {});
bind_collection_read_handlers<Data_Feed_Row>(app, data_feeds_path,
[] { return adminive::to_descriptor_json<Json, Data_Feed_Row>(); },
[this] { return feed_rows(global_); }, [this] { return feed_amis_schema(global_); }, true);
app.registerHandler(data_feeds_path, [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
complete_business_request(callback, [this, body = std::string(request->getBody())] {
std::lock_guard lock(topology_mutex_);
auto input = read_request_body(body);
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);
if(!row.source_key.empty() && !global_->mode_acs.data_source_config.map.get(row.source_key))
throw std::invalid_argument("连接的数据源不存在: " + row.source_key);
auto feed = create_data_feed_from_type(feed_type_name(row.type));
feed->key = row.key;
apply_feed_row(feed, row);
global_->mode_acs.data_feed_config.map.push_back(feed);
Data_feed_Config::set_connections_need_refresh();
save_data_feeds(global_);
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(feed_row(feed)), "created");
});
}, 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);
auto input = read_request_body(body);
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);
if(!row.source_key.empty() && !global_->mode_acs.data_source_config.map.get(row.source_key))
throw std::invalid_argument("连接的数据源不存在: " + row.source_key);
apply_feed_row(feed, row);
Data_feed_Config::set_connections_need_refresh();
save_data_feeds(global_);
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(feed_row(feed)), result.message);
});
};
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);
feed->async_stop();
if(!global_->mode_acs.data_feed_config.map.remove(id - 1))
throw std::out_of_range("record not found");
Data_feed_Config::set_connections_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");
Data_feed_Config::set_connections_need_refresh();
save_data_feeds(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("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"},
{"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()},
{"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 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();
}
}