From 8ced83c3253e20a7507d6a43c7b218a32020632b Mon Sep 17 00:00:00 2001 From: Nanako <469449812@qq.com> Date: Wed, 10 Jul 2024 21:24:16 +0800 Subject: [PATCH] init --- .gitmodules | 6 ++ src/data_types.h | 85 +++++++++++++++++++++++++ src/main.cpp | 33 ++++++++++ src/request.h | 63 +++++++++++++++++++ src/request_task.h | 135 ++++++++++++++++++++++++++++++++++++++++ src/utils.h | 50 +++++++++++++++ src/widget/login.h | 51 +++++++++++++++ src/widget/main_page.h | 106 +++++++++++++++++++++++++++++++ third_party/cpp-httplib | 1 + third_party/imgui | 1 + 10 files changed, 531 insertions(+) create mode 100644 .gitmodules create mode 100644 src/data_types.h create mode 100644 src/main.cpp create mode 100644 src/request.h create mode 100644 src/request_task.h create mode 100644 src/utils.h create mode 100644 src/widget/login.h create mode 100644 src/widget/main_page.h create mode 160000 third_party/cpp-httplib create mode 160000 third_party/imgui diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f25e719 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "third_party/imgui"] + path = third_party/imgui + url = https://www.nanako.site:49004/Nanako/imgui.git +[submodule "third_party/cpp-httplib"] + path = third_party/cpp-httplib + url = https://github.com/yhirose/cpp-httplib.git diff --git a/src/data_types.h b/src/data_types.h new file mode 100644 index 0000000..54945b6 --- /dev/null +++ b/src/data_types.h @@ -0,0 +1,85 @@ +#pragma once +#include + +#include "utils.h" + +inline struct session_data { + std::string session_cookie; + std::string bmc_ip; + std::string csrf_token; + static session_data from_json(const rapidjson::Value& obj) { + return { + .session_cookie = obj["SESSION_COOKIE"].GetString(), + .bmc_ip = obj["BMC_IP_ADDR"].GetString(), + .csrf_token = obj["CSRFTOKEN"].GetString() + }; + } +} session; + +struct fan_info { + int id; + int present; + int status; + int speed_percent; + int speed_rpm; + static fan_info from_json(const rapidjson::Value& obj) { + return { + .id = obj["Id"].GetInt(), + .present = obj["Present"].GetInt(), + .status = obj["Status"].GetInt(), + .speed_percent = obj["SpeedPercent"].GetInt(), + .speed_rpm = obj["SpeedRPM"].GetInt() + }; + } +}; + +struct running_time { + int days; + int hours; + static running_time from_json(const rapidjson::Value& obj) { + return { + .days = obj["Days"].GetInt(), + .hours = obj["Hours"].GetInt(), + }; + } +}; + +struct psu_status { + int id; + int present; + int power_status; + int temperature; + int pwr_in_watts; + int input_power; + int input_volt; + int output_volt; + int input_current; + int output_current; + int err_status; + std::string fw_version; + int output_power_max; + std::string mfr_model; + std::string mfr_id; + std::string sn; + + static psu_status from_json(const rapidjson::Value& obj) { + return { + .id = obj["Id"].GetInt(), + .present = obj["Present"].GetInt(), + .power_status = obj["PowerStatus"].GetInt(), + .temperature = obj["Temperature"].GetInt(), + .pwr_in_watts = obj["PwrInWatts"].GetInt(), + .input_power = obj["InputPower"].GetInt(), + .input_volt = obj["InputVolt"].GetInt(), + .output_volt = obj["OutputVolt"].GetInt(), + .input_current = obj["InputCurrent"].GetInt(), + .output_current = obj["OutputCurrent"].GetInt(), + .err_status = obj["ErrStatus"].GetInt(), + .fw_version = obj["FWVersion"].GetString(), + .output_power_max = obj["OutputPowerMax"].GetInt(), + .mfr_model = obj["MFRModel"].GetString(), + .mfr_id = obj["MFRID"].GetString(), + .sn = obj["SN"].GetString() + }; + } +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5c23f11 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,33 @@ +#include "imgui_main.h" +#include "widget/login.h" +#include "widget/main_page.h" + +std::string get_window_title() { + return "Inspur BMC"; +} + +// 配置imgui, 比如加载字体, 启用多视口等 +void configure_imgui(ImGuiIO& io) { + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows + io.ConfigViewportsNoAutoMerge = true; +} + +// 执行一些数据更新代码在这里 +void tick_imgui(float delta_time) { + update_infos(); +} + +// 执行imgui绘制(在tick_imgui之后执行) +void draw_imgui(float delta_time) { + ImGui::DockSpaceOverViewport(); + draw_login(); + draw_page(); +} + +int main(int argc, char *argv[]) { + run_imgui(); + return 0; +} diff --git a/src/request.h b/src/request.h new file mode 100644 index 0000000..4b0111b --- /dev/null +++ b/src/request.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +#include "httplib.h" +#include "rapidjson/document.h" + +inline httplib::SSLClient* cli = nullptr; +inline httplib::Headers headers; + +template +bool json_to_obj(const std::string& json, std::vector& out) { + if (json.empty()) + return false; + rapidjson::Document d; + d.Parse(json.c_str()); + + const auto& obj_array = d.GetArray(); + for (const auto& obj: obj_array) { + out.push_back(T::from_json(obj)); + } + return !out.empty(); +} + +template +bool json_to_obj(const std::string& json, T& out) { + std::vector vec; + const bool ok = json_to_obj(json, vec); + if (ok) { + out = vec[0]; + } + return ok; +} + +inline std::string post_request(const std::string& api, const httplib::Params& params) { + if (!cli) + return ""; + auto res = cli->Post("/rpc/" + api + ".asp", headers, params); + if (!res || res->status != 200) + return ""; + return parser_js(res->body); +} + +inline std::string get_request(const std::string& api) { + if (!cli) + return ""; + auto res = cli->Get("/rpc/" + api + ".asp", headers); + if (!res || res->status != 200) + return ""; + return parser_js(res->body); +} + +template +bool post_request(const std::string& api, const httplib::Params& params, T& out) { + return json_to_obj(post_request(api, params), out); +} + +template +bool get_request(const std::string& api, T& out) { + return json_to_obj(get_request(api), out); +} + diff --git a/src/request_task.h b/src/request_task.h new file mode 100644 index 0000000..17527cb --- /dev/null +++ b/src/request_task.h @@ -0,0 +1,135 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "data_types.h" +#include "request.h" +#include "utils.h" + +#define SIMPLE_GET_TASK(name, api, type) \ + class name : public get_task { \ + public: \ + explicit name() : get_task(api) {} \ + void setup() {} \ + }; + +template +class task { +public: + using result_type = T; + virtual ~task() = default; + + explicit task(const char* api) : api_(api) {} + std::future async_process() { + return std::async(std::launch::async, [this] { + return process(); + }); + } + + [[nodiscard]] const char* api() const { + return api_; + } +protected: + virtual T process() = 0; + + const char* api_; +}; + +template +class task_executor { +public: + template + static auto run_task(Args&&... args) { + static T task_instance; + task_instance.setup(std::forward(args)...); + return task_instance.async_process(); + } +}; + +template +class post_task : public task { +public: + explicit post_task(const char* api) : task(api) { + } + + void set_params(const httplib::Params& params) { + params_ = params; + } + +protected: + T process() override { + T result; + post_request(task::api(), params_, result); + return result; + } + httplib::Params params_; +}; + +template<> +inline void post_task::process() { + post_request(api(), params_); +} + +template +class get_task : public task { +public: + explicit get_task(const char* api) : task(api) { + } + +protected: + T process() override { + T result; + get_request(task::api(), result); + return result; + } +}; + +class login_task : public post_task { +public: + login_task() : post_task("WEBSES/create") {} + + void setup(const std::string& ip, const char* username, const char* password) { + ip_ = ip; + httplib::Params params; + params.emplace("WEBVAR_USERNAME", encode_content(username)); + params.emplace("WEBVAR_PASSWORD", encode_content(password)); + set_params(params); + } +protected: + session_data process() override { + delete cli; + headers.clear(); + headers.emplace("Content-Type", "application/json;charset=UTF-8"); + + cli = new httplib::SSLClient(ip_); // host + cli->enable_server_certificate_verification(false); + + auto result = post_task::process(); + + headers.emplace("X-CSRFTOKEN", result.csrf_token); + headers.emplace("Cookie", "test=1; SessionExpired=false; SessionCookie=" + result.session_cookie + "; BMP_IP_ADDR=" + result.bmc_ip + "; "); + return result; + } +private: + std::string ip_; +}; + +class set_fan_speed_task : public post_task { +public: + set_fan_speed_task() : post_task("setfanspeed") {} + void setup(int fan_id, int speed_percent) { + httplib::Params params; + params.emplace("ID", std::to_string(fan_id)); + params.emplace("PERCENT", std::to_string(speed_percent)); + set_params(params); + } +private: + +}; + +SIMPLE_GET_TASK(get_fan_info_task, "getfaninfo", std::vector) +SIMPLE_GET_TASK(get_running_time_task, "getrunningtime", running_time) +SIMPLE_GET_TASK(get_psu_info_task, "getallpsuinfo", std::vector) diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..3bf442e --- /dev/null +++ b/src/utils.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include +#include + +// 定义包含需要编码的字符的集合 +const std::set CUSTOM_ENCODE_SET = { + ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', + ',', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', + '^', '`', '{', '|', '}', '~' +}; + +// URL编码函数 +inline std::string encode_content(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (char c : value) { + // 如果字符在自定义的编码集合中,进行百分号编码 + if (CUSTOM_ENCODE_SET.contains(c) || !isprint(c)) { + escaped << '%' << std::setw(2) << int((unsigned char)c); + } else { + escaped << c; + } + } + + return escaped.str(); +} + +inline std::string parser_js(const std::string& in_js) { + // 第一个正则表达式 + // std::regex re(R"(\[\s*\{[^{}]*\}\s*,\s*\{[^{}]*\}\s*\])"); + // std::regex re(R"(\[.*?\])", std::regex::dotall); + std::regex bracket_regex(R"(\[([\s\S]*?)\])"); + std::smatch match; + + if (std::regex_search(in_js, match, bracket_regex)) { + std::string matched_str = match.str(); + // remove ", {}" + if (auto f = matched_str.find(", {}"); f != std::string::npos) + matched_str = matched_str.erase(f, 5); + // replace ' with " + std::ranges::replace(matched_str, '\'', '\"'); + return matched_str; + } + return ""; +} \ No newline at end of file diff --git a/src/widget/login.h b/src/widget/login.h new file mode 100644 index 0000000..f578fa3 --- /dev/null +++ b/src/widget/login.h @@ -0,0 +1,51 @@ +#pragma once +#include + +#include "data_types.h" +#include "imgui.h" +#include "request_task.h" +#define BUF_SIZE 256 + +inline bool is_login = false; +inline char ip_buf[BUF_SIZE] { "192.168.1.10" }; +inline char username_buf[BUF_SIZE] { "admin" }; +inline char password_buf[BUF_SIZE] { "admin" }; +inline std::future login_future; + +inline void draw_login() { + if (is_login) + return; + ImGui::Begin("Login"); + ImGui::Text("BMC IP: "); + ImGui::SameLine(); + ImGui::InputText("##bmc_ip", ip_buf, BUF_SIZE); + ImGui::NewLine(); + + ImGui::Text("User: "); + ImGui::SameLine(); + ImGui::InputText("##user", username_buf, BUF_SIZE); + ImGui::NewLine(); + + ImGui::Text("Password: "); + ImGui::SameLine(); + ImGui::InputText("##password", password_buf, BUF_SIZE); + ImGui::NewLine(); + + if (ImGui::Button("Login")) { + if (!login_future.valid()) + login_future = task_executor::run_task(ip_buf, username_buf, password_buf); + } + if (login_future.valid()) { + ImGui::Text("Logging in..."); + if (login_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + session_data session = login_future.get(); + if (session.session_cookie.empty()) { + ImGui::Text("Login failed"); + } else { + is_login = true; + ImGui::Text("Login success"); + } + } + } + ImGui::End(); +} diff --git a/src/widget/main_page.h b/src/widget/main_page.h new file mode 100644 index 0000000..4d56362 --- /dev/null +++ b/src/widget/main_page.h @@ -0,0 +1,106 @@ +#pragma once +#include +#include "data_types.h" +#include "imgui.h" +#include "request_task.h" + +#define FUTURE_PARAM(task_type, name) \ + inline std::future name##_future; \ + inline task_type::result_type name##_; \ + inline void update_##name() { \ + if (name##_future.valid()) { \ + if (name##_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { \ + name##_ = name##_future.get(); \ + } \ + } else { \ + name##_future = task_executor::run_task(); \ + } \ + } + +FUTURE_PARAM(get_fan_info_task, fan_infos) +FUTURE_PARAM(get_running_time_task, running_time) +FUTURE_PARAM(get_psu_info_task, psu_status) + +inline void update_infos() { + update_fan_infos(); + update_running_time(); + update_psu_status(); +} + +inline void draw_running_time() { + ImGui::Text("Server running time: %d days %d hours", running_time_.days, running_time_.hours); +} + +inline void draw_fan_infos() { + for (auto& fan_info : fan_infos_) { + fan_info.status; + + ImGui::Text("Fan %d: %d RPM", fan_info.id, fan_info.speed_rpm); + std::string label = "Speed##" + std::to_string(fan_info.id); + ImGui::InputInt(label.c_str(), &fan_info.speed_percent, 1, 100); + if (ImGui::IsItemDeactivatedAfterEdit()) { + // 更新风扇转速 + fan_info.speed_percent = std::clamp(fan_info.speed_percent, 0, 100); + auto future = task_executor::run_task(fan_info.id, fan_info.speed_percent); + } + } +} + +inline void draw_psu_infos() { + // 将psu信息绘制成表格 + ImGui::Columns(9, "psu_status"); + ImGui::Separator(); + ImGui::Text("ID"); + ImGui::NextColumn(); + ImGui::Text("Present"); + ImGui::NextColumn(); + ImGui::Text("Power Status"); + ImGui::NextColumn(); + ImGui::Text("Temperature"); + ImGui::NextColumn(); + ImGui::Text("Power In Watts"); + ImGui::NextColumn(); + ImGui::Text("Input Power"); + ImGui::NextColumn(); + ImGui::Text("Input Volt"); + ImGui::NextColumn(); + ImGui::Text("Output Volt"); + ImGui::NextColumn(); + ImGui::Text("Input Current"); + ImGui::NextColumn(); + ImGui::Separator(); + for (const auto& psu : psu_status_) { + ImGui::Text("%d", psu.id); + ImGui::NextColumn(); + ImGui::Text("%d", psu.present); + ImGui::NextColumn(); + ImGui::Text("%d", psu.power_status); + ImGui::NextColumn(); + ImGui::Text("%d", psu.temperature); + ImGui::NextColumn(); + ImGui::Text("%d", psu.pwr_in_watts); + ImGui::NextColumn(); + ImGui::Text("%d", psu.input_power); + ImGui::NextColumn(); + ImGui::Text("%d", psu.input_volt); + ImGui::NextColumn(); + ImGui::Text("%f", psu.output_volt / 100.f); + ImGui::NextColumn(); + ImGui::Text("%d", psu.input_current); + ImGui::NextColumn(); + } +} + +inline void draw_page() { + ImGui::Begin("Page"); + draw_running_time(); + ImGui::Separator(); + // 绘制可折叠 + if (ImGui::CollapsingHeader("Fan Infos")) { + draw_fan_infos(); + } + if (ImGui::CollapsingHeader("PSU Infos")) { + draw_psu_infos(); + } + ImGui::End(); +} diff --git a/third_party/cpp-httplib b/third_party/cpp-httplib new file mode 160000 index 0000000..c8bcaf8 --- /dev/null +++ b/third_party/cpp-httplib @@ -0,0 +1 @@ +Subproject commit c8bcaf8a913f1ac087b076488e0cbbc272cccd19 diff --git a/third_party/imgui b/third_party/imgui new file mode 160000 index 0000000..79b37f1 --- /dev/null +++ b/third_party/imgui @@ -0,0 +1 @@ +Subproject commit 79b37f14318110c08d72c26003f622bf6bbdcb54