This commit is contained in:
Nanako 2024-07-10 21:24:16 +08:00
commit 8ced83c325
10 changed files with 531 additions and 0 deletions

6
.gitmodules vendored Normal file
View File

@ -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

85
src/data_types.h Normal file
View File

@ -0,0 +1,85 @@
#pragma once
#include <rapidjson/document.h>
#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()
};
}
};

33
src/main.cpp Normal file
View File

@ -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;
}

63
src/request.h Normal file
View File

@ -0,0 +1,63 @@
#pragma once
#include <string>
#include <thread>
#include "httplib.h"
#include "rapidjson/document.h"
inline httplib::SSLClient* cli = nullptr;
inline httplib::Headers headers;
template<typename T>
bool json_to_obj(const std::string& json, std::vector<T>& 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<typename T>
bool json_to_obj(const std::string& json, T& out) {
std::vector<T> vec;
const bool ok = json_to_obj<T>(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<typename T>
bool post_request(const std::string& api, const httplib::Params& params, T& out) {
return json_to_obj(post_request(api, params), out);
}
template<typename T>
bool get_request(const std::string& api, T& out) {
return json_to_obj(get_request(api), out);
}

135
src/request_task.h Normal file
View File

@ -0,0 +1,135 @@
#pragma once
#include <functional>
#include <future>
#include <httplib.h>
#include <memory>
#include <utility>
#include "data_types.h"
#include "request.h"
#include "utils.h"
#define SIMPLE_GET_TASK(name, api, type) \
class name : public get_task<type> { \
public: \
explicit name() : get_task(api) {} \
void setup() {} \
};
template<typename T>
class task {
public:
using result_type = T;
virtual ~task() = default;
explicit task(const char* api) : api_(api) {}
std::future<T> 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<typename T>
class task_executor {
public:
template<typename ...Args>
static auto run_task(Args&&... args) {
static T task_instance;
task_instance.setup(std::forward<Args>(args)...);
return task_instance.async_process();
}
};
template<typename T>
class post_task : public task<T> {
public:
explicit post_task(const char* api) : task<T>(api) {
}
void set_params(const httplib::Params& params) {
params_ = params;
}
protected:
T process() override {
T result;
post_request(task<T>::api(), params_, result);
return result;
}
httplib::Params params_;
};
template<>
inline void post_task<void>::process() {
post_request(api(), params_);
}
template<typename T>
class get_task : public task<T> {
public:
explicit get_task(const char* api) : task<T>(api) {
}
protected:
T process() override {
T result;
get_request(task<T>::api(), result);
return result;
}
};
class login_task : public post_task<session_data> {
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<void> {
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<fan_info>)
SIMPLE_GET_TASK(get_running_time_task, "getrunningtime", running_time)
SIMPLE_GET_TASK(get_psu_info_task, "getallpsuinfo", std::vector<psu_status>)

50
src/utils.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include <iomanip>
#include <regex>
#include <set>
#include <string>
#include <bits/ranges_algo.h>
// 定义包含需要编码的字符的集合
const std::set<char> 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 "";
}

51
src/widget/login.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include <future>
#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<session_data> 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<login_task>::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();
}

106
src/widget/main_page.h Normal file
View File

@ -0,0 +1,106 @@
#pragma once
#include <vector>
#include "data_types.h"
#include "imgui.h"
#include "request_task.h"
#define FUTURE_PARAM(task_type, name) \
inline std::future<task_type::result_type> 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<task_type>::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<set_fan_speed_task>::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();
}

1
third_party/cpp-httplib vendored Submodule

@ -0,0 +1 @@
Subproject commit c8bcaf8a913f1ac087b076488e0cbbc272cccd19

1
third_party/imgui vendored Submodule

@ -0,0 +1 @@
Subproject commit 79b37f14318110c08d72c26003f622bf6bbdcb54