Files
Adminive/backend/include/adminive/amis.hpp
T
2026-08-06 20:33:28 +08:00

380 lines
19 KiB
C++

#pragma once
#include "adminive/status.hpp"
#include <algorithm>
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace adminive {
inline std::string make_field_name(std::string_view prefix, std::string_view name) {
if(prefix.empty()) {
return std::string(name);
}
return std::string(prefix) + "." + std::string(name);
}
template <Json_Type Json>
Json make_amis_form_actions(std::string submit_label = "Apply") {
Json reset = json_object<Json>();
json_set(reset, "type", "reset");
json_set(reset, "label", "Reset");
Json submit = json_object<Json>();
json_set(submit, "type", "submit");
json_set(submit, "label", std::move(submit_label));
json_set(submit, "level", "primary");
Json result = json_array<Json>();
json_append(result, std::move(reset));
json_append(result, std::move(submit));
return result;
}
template <Json_Type Json>
Json make_amis_control(const Json& field, std::string_view permission, std::string_view prefix = {}) {
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
const std::string full_name = make_field_name(prefix, name);
const std::string value_type = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "value_type"));
if(value_type == "object" && Json_Adapter<Json>::contains(field, "children")) {
Json body = json_array<Json>();
const auto& children = Json_Adapter<Json>::at(field, "children");
for(std::size_t index = 0; index < Json_Adapter<Json>::size(children); ++index) {
const auto& child = Json_Adapter<Json>::at(children, index);
if(json_get<Json, bool>(Json_Adapter<Json>::at(child, "visible"))) {
json_append(body, make_amis_control<Json>(child, permission, full_name));
}
}
Json control = json_object<Json>();
json_set(control, "type", "fieldset");
json_set(control, "title", Json_Adapter<Json>::at(field, "label"));
json_set(control, "body", std::move(body));
json_set(control, "collapsable", true);
json_set(control, "collapsed", false);
if(Json_Adapter<Json>::contains(field, "description")) {
json_set(control, "description", Json_Adapter<Json>::at(field, "description"));
}
if(Json_Adapter<Json>::contains(field, "visible_on")) {
json_set(control, "visibleOn", Json_Adapter<Json>::at(field, "visible_on"));
}
return control;
}
Json control = json_object<Json>();
json_set(control, "name", full_name);
json_set(control, "label", Json_Adapter<Json>::at(field, "label"));
if(Json_Adapter<Json>::contains(field, "description")) {
json_set(control, "description", Json_Adapter<Json>::at(field, "description"));
}
if(Json_Adapter<Json>::contains(field, "visible_on")) {
json_set(control, "visibleOn", Json_Adapter<Json>::at(field, "visible_on"));
}
if(!json_get<Json, bool>(Json_Adapter<Json>::at(field, permission))) {
json_set(control, "type", "static");
return control;
}
if(Json_Adapter<Json>::contains(field, "widget")) {
json_set(control, "type", Json_Adapter<Json>::at(field, "widget"));
} else if(value_type == "boolean") {
json_set(control, "type", "switch");
} else if(value_type == "integer" || value_type == "number") {
json_set(control, "type", "input-number");
} else if(value_type == "enum") {
json_set(control, "type", "select");
} else {
json_set(control, "type", "input-text");
}
const std::string control_type = json_get<Json, std::string>(Json_Adapter<Json>::at(control, "type"));
if(control_type == "input-date") {
json_set(control, "valueFormat", "YYYY-MM-DD");
json_set(control, "displayFormat", "YYYY-MM-DD");
json_set(control, "clearable", true);
} else if(control_type == "input-color") {
json_set(control, "clearable", true);
}
if(json_get<Json, bool>(Json_Adapter<Json>::at(field, "required"))) {
json_set(control, "required", true);
}
if(Json_Adapter<Json>::contains(field, "minimum")) {
json_set(control, "min", Json_Adapter<Json>::at(field, "minimum"));
}
if(Json_Adapter<Json>::contains(field, "maximum")) {
json_set(control, "max", Json_Adapter<Json>::at(field, "maximum"));
}
if(Json_Adapter<Json>::contains(field, "multiple_of")) {
json_set(control, "step", Json_Adapter<Json>::at(field, "multiple_of"));
}
if(Json_Adapter<Json>::contains(field, "validation_message")) {
Json errors = json_object<Json>();
json_set(errors, "isInt", Json_Adapter<Json>::at(field, "validation_message"));
json_set(control, "validationErrors", std::move(errors));
}
if(Json_Adapter<Json>::contains(field, "options")) {
json_set(control, "options", Json_Adapter<Json>::at(field, "options"));
}
return control;
}
template <Json_Type Json>
Json make_amis_form_body(const Json& descriptor, std::string_view permission) {
Json body = json_array<Json>();
const auto& fields = Json_Adapter<Json>::at(descriptor, "fields");
for(std::size_t index = 0; index < Json_Adapter<Json>::size(fields); ++index) {
const auto& field = Json_Adapter<Json>::at(fields, index);
if(json_get<Json, bool>(Json_Adapter<Json>::at(field, "visible"))) {
json_append(body, make_amis_control<Json>(field, permission));
}
}
return body;
}
template <Json_Type Json>
Json make_amis_column(const Json& field) {
Json column = json_object<Json>();
json_set(column, "name", Json_Adapter<Json>::at(field, "name"));
json_set(column, "label", Json_Adapter<Json>::at(field, "list_label"));
if(Json_Adapter<Json>::contains(field, "order")) {
json_set(column, "order", Json_Adapter<Json>::at(field, "order"));
}
if(Json_Adapter<Json>::contains(field, "sortable")) {
json_set(column, "sortable", Json_Adapter<Json>::at(field, "sortable"));
}
const std::string value_type = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "value_type"));
if(value_type == "boolean") {
json_set(column, "type", "status");
} else if(value_type == "enum" && Json_Adapter<Json>::contains(field, "options")) {
json_set(column, "type", "mapping");
Json map = json_object<Json>();
const auto& options = Json_Adapter<Json>::at(field, "options");
for(std::size_t index = 0; index < Json_Adapter<Json>::size(options); ++index) {
const auto& option = Json_Adapter<Json>::at(options, index);
json_set(map, json_get<Json, std::string>(Json_Adapter<Json>::at(option, "value")), Json_Adapter<Json>::at(option, "label"));
}
json_set(column, "map", std::move(map));
}
return column;
}
template <Json_Type Json>
Json make_amis_status_service(std::string status_api) {
Json view = json_object<Json>();
json_set(view, "type", "tpl");
json_set(view, "tpl", R"(<div class="adminive-status-grid"><div><strong>Service</strong><span>${service_state}</span></div><div><strong>Server time</strong><span>${server_time}</span></div><div><strong>Uptime</strong><span>${uptime_seconds} s</span></div><div><strong>Polling sequence</strong><span>${poll_sequence}</span></div><div><strong>Radio states</strong><span>${radio_state_count}</span></div><div><strong>Listen port</strong><span>${listen_port}</span></div></div>)");
Json panel = json_object<Json>();
json_set(panel, "type", "panel");
json_set(panel, "title", "Backend Status");
json_set(panel, "body", std::move(view));
Json api = json_object<Json>();
json_set(api, "method", "get");
json_set(api, "url", std::move(status_api));
Json result = json_object<Json>();
json_set(result, "type", "service");
json_set(result, "name", "backend_status");
json_set(result, "api", std::move(api));
json_set(result, "initFetch", true);
json_set(result, "interval", 2000);
json_set(result, "silentPolling", true);
json_set(result, "showErrorMsg", true);
json_set(result, "body", std::move(panel));
return result;
}
template <Json_Type Json>
Json make_amis_object_status_body(const Json& descriptor) {
Json body = json_array<Json>();
const auto& fields = Json_Adapter<Json>::at(descriptor, "fields");
for(std::size_t index = 0; index < Json_Adapter<Json>::size(fields); ++index) {
const auto& field = Json_Adapter<Json>::at(fields, index);
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
const std::string label = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "label"));
const bool color = json_get<Json, bool>(Json_Adapter<Json>::at(field, "color"));
std::string tpl = "<div class=\"adminive-object-status-row\"><span class=\"adminive-object-status-label\">" + label + "</span><strong";
if(color) {
tpl += " style=\"color: ${" + name + ".color}\"";
}
tpl += ">${" + name + ".value}</strong></div>";
Json item = json_object<Json>();
json_set(item, "type", "tpl");
json_set(item, "tpl", std::move(tpl));
json_append(body, std::move(item));
}
Json result = json_object<Json>();
json_set(result, "type", "container");
json_set(result, "className", "adminive-object-status");
json_set(result, "body", std::move(body));
return result;
}
template <Json_Type Json>
Json make_amis_object_status_service(const Json& descriptor, std::string status_api, std::uint64_t interval) {
Json api = json_object<Json>();
json_set(api, "method", "get");
json_set(api, "url", std::move(status_api));
Json result = json_object<Json>();
json_set(result, "type", "service");
json_set(result, "api", std::move(api));
json_set(result, "initFetch", true);
json_set(result, "interval", interval);
json_set(result, "silentPolling", true);
json_set(result, "showErrorMsg", true);
json_set(result, "body", make_amis_object_status_body<Json>(descriptor));
return result;
}
template <Json_Type Json>
Json make_amis_object_status_column(const Json& descriptor, std::string status_api, std::uint64_t interval, std::string action_label) {
Json pop_over = json_object<Json>();
json_set(pop_over, "trigger", "click");
json_set(pop_over, "position", "left-top");
json_set(pop_over, "showIcon", false);
json_set(pop_over, "body", make_amis_object_status_service<Json>(descriptor, std::move(status_api), interval));
Json result = json_object<Json>();
json_set(result, "type", "tpl");
json_set(result, "label", Json_Adapter<Json>::at(descriptor, "label"));
json_set(result, "tpl", "<span class=\"text-primary\">" + std::move(action_label) + "</span>");
json_set(result, "popOver", std::move(pop_over));
return result;
}
template <Json_Type Json, Described_Type T>
Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::string submit_label = "Apply") {
const Json descriptor = to_descriptor_json<Json, T>();
Json result = json_object<Json>();
json_set(result, "type", "form");
json_set(result, "title", Json_Adapter<Json>::at(descriptor, "label"));
json_set(result, "data", to_frontend_json<Json>(value));
json_set(result, "body", make_amis_form_body<Json>(descriptor, "editable"));
json_set(result, "actions", make_amis_form_actions<Json>(std::move(submit_label)));
json_set(result, "affixFooter", true);
if(!submit_api.empty()) {
Json api = json_object<Json>();
json_set(api, "method", "post");
json_set(api, "url", std::move(submit_api));
json_set(result, "api", std::move(api));
}
return result;
}
template <Json_Type Json, Described_Type T>
Json to_amis_crud_schema(std::string base_api, const Json* status_descriptor = nullptr, std::uint64_t status_interval = 2000) {
const Json descriptor = to_descriptor_json<Json, T>();
const Json& list = Json_Adapter<Json>::at(descriptor, "list");
const std::string crud_id = json_get<Json, std::string>(Json_Adapter<Json>::at(descriptor, "name")) + "_crud";
Json columns = json_array<Json>();
Json id_column = json_object<Json>();
json_set(id_column, "name", "id");
json_set(id_column, "label", Json_Adapter<Json>::at(list, "id_label"));
json_set(id_column, "sortable", true);
json_append(columns, std::move(id_column));
std::vector<Json> list_fields;
const auto& fields = Json_Adapter<Json>::at(descriptor, "fields");
for(std::size_t index = 0; index < Json_Adapter<Json>::size(fields); ++index) {
const auto& field = Json_Adapter<Json>::at(fields, index);
if(json_get<Json, bool>(Json_Adapter<Json>::at(field, "readable")) && json_get<Json, bool>(Json_Adapter<Json>::at(field, "list_visible")) && json_get<Json, std::string>(Json_Adapter<Json>::at(field, "value_type")) != "object") {
list_fields.push_back(field);
}
}
std::stable_sort(list_fields.begin(), list_fields.end(), [](const Json& left, const Json& right) {
return json_get<Json, int>(Json_Adapter<Json>::at(left, "order")) < json_get<Json, int>(Json_Adapter<Json>::at(right, "order"));
});
for(const auto& field : list_fields) {
json_append(columns, make_amis_column<Json>(field));
}
if(status_descriptor) {
json_append(columns, make_amis_object_status_column<Json>(*status_descriptor, base_api + "/${id}/status", status_interval, json_get<Json, std::string>(Json_Adapter<Json>::at(list, "view_status_label"))));
}
const std::string create_label = json_get<Json, std::string>(Json_Adapter<Json>::at(list, "create_label"));
const std::string edit_label = json_get<Json, std::string>(Json_Adapter<Json>::at(list, "edit_label"));
Json create_api = json_object<Json>();
json_set(create_api, "method", "post");
json_set(create_api, "url", base_api);
Json create_form = json_object<Json>();
json_set(create_form, "type", "form");
json_set(create_form, "api", std::move(create_api));
json_set(create_form, "reload", crud_id);
json_set(create_form, "body", make_amis_form_body<Json>(descriptor, "creatable"));
json_set(create_form, "actions", make_amis_form_actions<Json>(create_label));
Json edit_api = json_object<Json>();
json_set(edit_api, "method", "put");
json_set(edit_api, "url", base_api + "/${id}");
Json edit_form = json_object<Json>();
json_set(edit_form, "type", "form");
json_set(edit_form, "api", std::move(edit_api));
json_set(edit_form, "reload", crud_id);
json_set(edit_form, "body", make_amis_form_body<Json>(descriptor, "editable"));
json_set(edit_form, "actions", make_amis_form_actions<Json>(json_get<Json, std::string>(Json_Adapter<Json>::at(list, "confirm_label"))));
Json create_dialog = json_object<Json>();
json_set(create_dialog, "title", create_label + " " + json_get<Json, std::string>(Json_Adapter<Json>::at(descriptor, "label")));
json_set(create_dialog, "body", std::move(create_form));
Json create_button = json_object<Json>();
json_set(create_button, "type", "button");
json_set(create_button, "label", create_label);
json_set(create_button, "level", "primary");
json_set(create_button, "actionType", "dialog");
json_set(create_button, "dialog", std::move(create_dialog));
Json edit_dialog = json_object<Json>();
json_set(edit_dialog, "title", edit_label + " " + json_get<Json, std::string>(Json_Adapter<Json>::at(descriptor, "label")));
json_set(edit_dialog, "body", std::move(edit_form));
Json edit_button = json_object<Json>();
json_set(edit_button, "type", "button");
json_set(edit_button, "label", edit_label);
json_set(edit_button, "level", "link");
json_set(edit_button, "actionType", "dialog");
json_set(edit_button, "dialog", std::move(edit_dialog));
Json delete_api = json_object<Json>();
json_set(delete_api, "method", "delete");
json_set(delete_api, "url", base_api + "/${id}");
Json delete_button = json_object<Json>();
json_set(delete_button, "type", "button");
json_set(delete_button, "label", Json_Adapter<Json>::at(list, "delete_label"));
json_set(delete_button, "level", "link");
json_set(delete_button, "className", "text-danger");
json_set(delete_button, "actionType", "ajax");
json_set(delete_button, "confirmText", Json_Adapter<Json>::at(list, "delete_confirm"));
json_set(delete_button, "api", std::move(delete_api));
json_set(delete_button, "reload", crud_id);
Json buttons = json_array<Json>();
json_append(buttons, std::move(edit_button));
json_append(buttons, std::move(delete_button));
Json operation = json_object<Json>();
json_set(operation, "type", "operation");
json_set(operation, "label", Json_Adapter<Json>::at(list, "actions_label"));
json_set(operation, "buttons", std::move(buttons));
json_append(columns, std::move(operation));
Json toggler = json_object<Json>();
json_set(toggler, "type", "columns-toggler");
json_set(toggler, "draggable", Json_Adapter<Json>::at(list, "column_reorderable"));
Json toolbar = json_array<Json>();
json_append(toolbar, std::move(toggler));
json_append(toolbar, "reload");
json_append(toolbar, std::move(create_button));
Json crud_api = json_object<Json>();
json_set(crud_api, "method", "get");
json_set(crud_api, "url", base_api);
Json crud = json_object<Json>();
json_set(crud, "type", "crud");
json_set(crud, "id", crud_id);
json_set(crud, "api", std::move(crud_api));
json_set(crud, "syncLocation", false);
json_set(crud, "primaryField", "id");
json_set(crud, "headerToolbar", std::move(toolbar));
json_set(crud, "columns", std::move(columns));
const std::string default_order_by = json_get<Json, std::string>(Json_Adapter<Json>::at(list, "default_order_by"));
if(!default_order_by.empty()) {
Json params = json_object<Json>();
json_set(params, "orderBy", default_order_by);
json_set(params, "orderDir", Json_Adapter<Json>::at(list, "default_order_dir"));
json_set(crud, "defaultParams", std::move(params));
}
if(json_get<Json, bool>(Json_Adapter<Json>::at(list, "user_reorderable"))) {
json_set(crud, "draggable", true);
Json order_api = json_object<Json>();
json_set(order_api, "method", "post");
json_set(order_api, "url", base_api + "/order");
json_set(crud, "saveOrderApi", std::move(order_api));
}
Json result = json_object<Json>();
json_set(result, "type", "page");
json_set(result, "title", Json_Adapter<Json>::at(list, "management_label"));
json_set(result, "body", std::move(crud));
return result;
}
template <Json_Type Json, Described_Type T>
Json to_amis_crud_status_schema(std::string base_api, std::string status_api, const Json* object_status_descriptor = nullptr, std::uint64_t object_status_interval = 2000) {
Json result = to_amis_crud_schema<Json, T>(std::move(base_api), object_status_descriptor, object_status_interval);
Json crud = std::move(Json_Adapter<Json>::at(result, "body"));
Json body = json_array<Json>();
json_append(body, make_amis_status_service<Json>(std::move(status_api)));
json_append(body, std::move(crud));
json_set(result, "body", std::move(body));
return result;
}
}