238 lines
10 KiB
C++
238 lines
10 KiB
C++
#pragma once
|
|
#include "adminive/adminive.hpp"
|
|
#include "adminive/adapters/magic_enum.hpp"
|
|
#include "adminive/adapters/nlohmann_json.hpp"
|
|
#include "adminive/adapters/httplib.hpp"
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <ctime>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
namespace adminive::example {
|
|
using Json = nlohmann::json;
|
|
template <class T>
|
|
using Http_Resource = adminive::Http_Resource<T, Json>;
|
|
template <class T>
|
|
using Http_Collection_Resource = adminive::Http_Collection_Resource<T, Json>;
|
|
inline std::string format_utc_time(std::chrono::system_clock::time_point value) {
|
|
const std::time_t raw_time = std::chrono::system_clock::to_time_t(value);
|
|
std::tm utc_time{};
|
|
#ifdef _WIN32
|
|
gmtime_s(&utc_time, &raw_time);
|
|
#else
|
|
gmtime_r(&raw_time, &utc_time);
|
|
#endif
|
|
std::ostringstream stream;
|
|
stream << std::put_time(&utc_time, "%Y-%m-%dT%H:%M:%SZ");
|
|
return stream.str();
|
|
}
|
|
enum class Radio_Mode {
|
|
receive,
|
|
transmit,
|
|
duplex,
|
|
maintenance
|
|
};
|
|
enum class Radio_Operating_State {
|
|
starting,
|
|
running,
|
|
degraded,
|
|
stopped
|
|
};
|
|
enum class Log_Level {
|
|
trace,
|
|
debug,
|
|
info,
|
|
warning,
|
|
error
|
|
};
|
|
enum class Alert_Channel {
|
|
dashboard,
|
|
email,
|
|
webhook
|
|
};
|
|
struct Server_Status {
|
|
std::string service_state{"running"};
|
|
std::string server_time;
|
|
std::uint64_t uptime_seconds{};
|
|
std::uint64_t poll_sequence{};
|
|
std::uint64_t radio_state_count{};
|
|
int listen_port{};
|
|
};
|
|
struct Radio_Item_Status {
|
|
Status_Value<Radio_Operating_State> operating_state;
|
|
Status_Value<std::uint64_t> refresh_sequence;
|
|
Status_Value<std::string> refresh_time;
|
|
};
|
|
struct Endpoint_Config {
|
|
bool enabled{true};
|
|
std::string host{"127.0.0.1"};
|
|
Range_Value<int, 1, 65535> port{9000};
|
|
};
|
|
struct Appearance_Config {
|
|
std::string panel_title{"Radio Control"};
|
|
std::string effective_date{"2026-08-06"};
|
|
std::string accent_color{"#2563eb"};
|
|
};
|
|
struct Radio_Service_Config {
|
|
std::string profile_name{"Primary Radio Profile"};
|
|
Radio_Mode mode{Radio_Mode::receive};
|
|
Range_Value<int, 1, 64> worker_count{4};
|
|
double receive_gain{1.25};
|
|
Endpoint_Config primary_endpoint{};
|
|
Endpoint_Config backup_endpoint{true, "127.0.0.2", Range_Value<int, 1, 65535>{9001}};
|
|
Appearance_Config appearance{};
|
|
};
|
|
struct Alert_Config {
|
|
bool enabled{true};
|
|
std::string rule_name{"Radio Health Alert"};
|
|
Alert_Channel channel{Alert_Channel::dashboard};
|
|
Log_Level minimum_level{Log_Level::warning};
|
|
Range_Value<int, 1, 1440> repeat_minutes{10};
|
|
std::string effective_date{"2026-08-06"};
|
|
std::string highlight_color{"#d97706"};
|
|
Endpoint_Config webhook_endpoint{true, "127.0.0.1", Range_Value<int, 1, 65535>{9100}};
|
|
};
|
|
struct Radio_State {
|
|
struct Even_Validator {
|
|
static constexpr int multiple_of = 2;
|
|
static constexpr std::string_view message = "buffer_count must be even";
|
|
void operator()(const int& value) const {
|
|
if(value % 2 != 0) {
|
|
throw std::invalid_argument("buffer_count must be even");
|
|
}
|
|
}
|
|
};
|
|
struct Watermark_Validator {
|
|
void operator()(const Radio_State& value) const {
|
|
if(value.low_watermark > value.high_watermark) {
|
|
throw std::invalid_argument("low_watermark must not exceed high_watermark");
|
|
}
|
|
}
|
|
};
|
|
Radio_Mode mode{Radio_Mode::receive};
|
|
Range_Value<int, 1, 65535> port{9999};
|
|
Validated_Value<int, Even_Validator> buffer_count{2};
|
|
int low_watermark{};
|
|
int high_watermark{};
|
|
Radio_Item_Status status() {
|
|
const auto now = std::chrono::system_clock::now();
|
|
const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
|
|
const auto state_index = (static_cast<std::uint64_t>(seconds / 4) + static_cast<std::uint64_t>(port.value())) % 4;
|
|
Radio_Operating_State state{};
|
|
std::string color;
|
|
switch(state_index) {
|
|
case 0:
|
|
state = Radio_Operating_State::starting;
|
|
color = "#2563eb";
|
|
break;
|
|
case 1:
|
|
state = Radio_Operating_State::running;
|
|
color = "#16a34a";
|
|
break;
|
|
case 2:
|
|
state = Radio_Operating_State::degraded;
|
|
color = "#d97706";
|
|
break;
|
|
default:
|
|
state = Radio_Operating_State::stopped;
|
|
color = "#dc2626";
|
|
break;
|
|
}
|
|
++status_sequence_;
|
|
return Radio_Item_Status{status_value(state, std::move(color)), status_value(status_sequence_, "#475569"), status_value(format_utc_time(now), "#475569")};
|
|
}
|
|
private:
|
|
std::uint64_t status_sequence_{};
|
|
};
|
|
}
|
|
namespace adminive {
|
|
template <>
|
|
struct Type_Descriptor<example::Server_Status> {
|
|
static auto get() {
|
|
using T = example::Server_Status;
|
|
return object<T>("server_status", ADMINIVE_FIELD(T, service_state), ADMINIVE_FIELD(T, server_time), ADMINIVE_FIELD(T, uptime_seconds), ADMINIVE_FIELD(T, poll_sequence), ADMINIVE_FIELD(T, radio_state_count), ADMINIVE_FIELD(T, listen_port)).label("Server Status");
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_Descriptor<example::Radio_Item_Status> {
|
|
static auto get() {
|
|
using T = example::Radio_Item_Status;
|
|
return object<T>("radio_item_status", ADMINIVE_FIELD_LABEL(T, operating_state, "State"), ADMINIVE_FIELD_LABEL(T, refresh_sequence, "Refresh Sequence"), ADMINIVE_FIELD_LABEL(T, refresh_time, "Refresh Time")).label("Runtime Status");
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_Descriptor<example::Endpoint_Config> {
|
|
static auto get() {
|
|
using T = example::Endpoint_Config;
|
|
return object<T>(
|
|
"endpoint_config",
|
|
ADMINIVE_FIELD_LABEL(T, enabled, "Enable Endpoint").editable().creatable().description("Controls whether this endpoint participates in the current configuration"),
|
|
ADMINIVE_FIELD_LABEL(T, host, "Host Address").editable().creatable().required().description("String input example"),
|
|
ADMINIVE_FIELD_LABEL(T, port, "Port").editable().creatable().required().description("Integer input example")
|
|
).label("Endpoint");
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_Descriptor<example::Appearance_Config> {
|
|
static auto get() {
|
|
using T = example::Appearance_Config;
|
|
return object<T>(
|
|
"appearance_config",
|
|
ADMINIVE_FIELD_LABEL(T, panel_title, "Panel Title").editable().creatable().required().description("String input example"),
|
|
ADMINIVE_FIELD_LABEL(T, effective_date, "Effective Date").editable().creatable().required().widget("input-date").description("Date selector example"),
|
|
ADMINIVE_FIELD_LABEL(T, accent_color, "Accent Color").editable().creatable().required().widget("input-color").description("Color selector example")
|
|
).label("Appearance");
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_Descriptor<example::Radio_Service_Config> {
|
|
static auto get() {
|
|
using T = example::Radio_Service_Config;
|
|
return object<T>(
|
|
"radio_service_config",
|
|
ADMINIVE_FIELD_LABEL(T, profile_name, "Profile Name").editable().required().description("Editable string configuration"),
|
|
ADMINIVE_FIELD_LABEL(T, mode, "Operating Mode").editable().required().description("Enum options are generated by magic_enum"),
|
|
ADMINIVE_FIELD_LABEL(T, worker_count, "Worker Count").editable().required().description("Integer number input"),
|
|
ADMINIVE_FIELD_LABEL(T, receive_gain, "Receive Gain").editable().required().description("Floating-point number input"),
|
|
ADMINIVE_FIELD_LABEL(T, primary_endpoint, "Primary Endpoint").editable().description("First child configuration group"),
|
|
ADMINIVE_FIELD_LABEL(T, backup_endpoint, "Backup Endpoint").editable().visible_on("${mode == 'duplex'}").description("Displayed only when Operating Mode is duplex"),
|
|
ADMINIVE_FIELD_LABEL(T, appearance, "Appearance").editable().description("Nested date and color controls")
|
|
).label("Radio Service Configuration");
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_Descriptor<example::Alert_Config> {
|
|
static auto get() {
|
|
using T = example::Alert_Config;
|
|
return object<T>(
|
|
"alert_config",
|
|
ADMINIVE_FIELD_LABEL(T, enabled, "Enable Alerts").editable(),
|
|
ADMINIVE_FIELD_LABEL(T, rule_name, "Rule Name").editable().required(),
|
|
ADMINIVE_FIELD_LABEL(T, channel, "Delivery Channel").editable().required().description("Enum options are generated by magic_enum"),
|
|
ADMINIVE_FIELD_LABEL(T, minimum_level, "Minimum Log Level").editable().required().description("Second enum example"),
|
|
ADMINIVE_FIELD_LABEL(T, repeat_minutes, "Repeat Interval Minutes").editable().required(),
|
|
ADMINIVE_FIELD_LABEL(T, effective_date, "Effective Date").editable().required().widget("input-date"),
|
|
ADMINIVE_FIELD_LABEL(T, highlight_color, "Highlight Color").editable().required().widget("input-color"),
|
|
ADMINIVE_FIELD_LABEL(T, webhook_endpoint, "Webhook Endpoint").editable().visible_on("${enabled && channel == 'webhook'}").description("Displayed only when alerts are enabled and channel is webhook")
|
|
).label("Alert Configuration");
|
|
}
|
|
};
|
|
template <>
|
|
struct Type_Descriptor<example::Radio_State> {
|
|
static auto get() {
|
|
using T = example::Radio_State;
|
|
return object<T>(
|
|
"radio_state",
|
|
ADMINIVE_FIELD_LABEL(T, mode, "Operating Mode").creatable().editable().required().list_label("Mode").order(5).sortable().description("Enum list column generated through magic_enum"),
|
|
ADMINIVE_FIELD(T, port).creatable().editable().required().description("Listening port").list_label("Listen Port").order(20).sortable(),
|
|
ADMINIVE_FIELD(T, buffer_count).creatable().editable().required().list_label("Buffers").order(10).sortable(),
|
|
ADMINIVE_FIELD(T, low_watermark).creatable().editable().order(30),
|
|
ADMINIVE_FIELD(T, high_watermark).creatable().editable().order(40)
|
|
).label("Radio State").validator(T::Watermark_Validator{});
|
|
}
|
|
};
|
|
}
|