fix(ui): move Daemon binary into its own full-width row below Node & Security
The Daemon binary panel (info + the all-actions toolbar) lived inside the 60%-wide NODE column, so the six-button toolbar clipped. Pull it out into a dedicated full-width row rendered after the NODE + SECURITY columns reconcile, so it spans the whole card: Installed | Bundled info side by side, status line, and the Install bundled | Refresh | Test connection | Rescan | Delete blockchain | Repair wallet toolbar now have the full container width and no longer clip. The NODE column keeps only the node/RPC info. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2005,148 +2005,6 @@ void RenderSettingsPage(App* app) {
|
|||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
|
||||||
// Node maintenance buttons (full-node build only)
|
|
||||||
if (app->supportsFullNodeLifecycleActions()) {
|
|
||||||
ImFont* btnFont = S.resolveFont("button");
|
|
||||||
|
|
||||||
// ---- Daemon binary: installed vs bundled, with explicit reinstall ----
|
|
||||||
// The wallet only auto-places dragonxd when it's missing (never overwrites a
|
|
||||||
// size-mismatched one), so this panel reports the installed binary and lets the
|
|
||||||
// user deliberately replace it with the version bundled in this wallet build.
|
|
||||||
if (!s_settingsState.daemon_info_loaded) {
|
|
||||||
s_settingsState.installed_daemon = dragonx::resources::getInstalledDaemonInfo();
|
|
||||||
s_settingsState.bundled_daemon = dragonx::resources::getBundledDaemonInfo();
|
|
||||||
s_settingsState.daemon_info_loaded = true;
|
|
||||||
}
|
|
||||||
const auto& inst = s_settingsState.installed_daemon;
|
|
||||||
const auto& bun = s_settingsState.bundled_daemon;
|
|
||||||
|
|
||||||
auto fmtDate = [](std::int64_t epoch) -> std::string {
|
|
||||||
if (epoch <= 0) return "—";
|
|
||||||
std::time_t t = static_cast<std::time_t>(epoch);
|
|
||||||
std::tm tmv{};
|
|
||||||
#ifdef _WIN32
|
|
||||||
localtime_s(&tmv, &t);
|
|
||||||
#else
|
|
||||||
localtime_r(&t, &tmv);
|
|
||||||
#endif
|
|
||||||
char buf[32];
|
|
||||||
std::strftime(buf, sizeof(buf), "%Y-%m-%d", &tmv);
|
|
||||||
return std::string(buf);
|
|
||||||
};
|
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
|
||||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_binary"));
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
|
||||||
|
|
||||||
// Installed | Bundled side by side, spanning the node column's width.
|
|
||||||
const float dbStartY = ImGui::GetCursorScreenPos().y;
|
|
||||||
const float dbLineH = ImGui::GetTextLineHeightWithSpacing();
|
|
||||||
const float dbCol2X = leftX + leftColW * 0.5f;
|
|
||||||
const ImVec4 dbDim = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium());
|
|
||||||
|
|
||||||
// Column 1 — Installed (version, then size · date)
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY));
|
|
||||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_installed"));
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY + dbLineH));
|
|
||||||
if (inst.exists) {
|
|
||||||
ImGui::TextUnformatted(inst.version.empty() ? TR("unknown") : inst.version.c_str());
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY + dbLineH * 2));
|
|
||||||
ImGui::TextColored(dbDim, "%s · %s",
|
|
||||||
util::Platform::formatFileSize(inst.size).c_str(),
|
|
||||||
fmtDate(inst.modifiedEpoch).c_str());
|
|
||||||
} else {
|
|
||||||
ImGui::TextColored(dbDim, "%s", TR("daemon_not_installed"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Column 2 — Bundled (version, then size)
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY));
|
|
||||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_bundled"));
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH));
|
|
||||||
if (bun.available) {
|
|
||||||
ImGui::TextUnformatted(bun.version.empty() ? TR("unknown") : bun.version.c_str());
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH * 2));
|
|
||||||
ImGui::TextColored(dbDim, "%s", util::Platform::formatFileSize(bun.size).c_str());
|
|
||||||
} else {
|
|
||||||
ImGui::TextColored(dbDim, "%s", TR("daemon_none_bundled"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status line, below both sub-columns, spanning the node column.
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY + dbLineH * 3 + Layout::spacingXs()));
|
|
||||||
if (bun.available) {
|
|
||||||
const bool sameSize = inst.exists && inst.size == bun.size;
|
|
||||||
if (!inst.exists)
|
|
||||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_missing"));
|
|
||||||
else if (sameSize)
|
|
||||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("daemon_status_match"));
|
|
||||||
else
|
|
||||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_differ"));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
|
||||||
// One toolbar row of all node actions: the daemon-binary actions first
|
|
||||||
// (Install bundled | Refresh), then the maintenance actions right after
|
|
||||||
// (Test connection | Rescan | Delete blockchain | Repair wallet). Auto-sized so
|
|
||||||
// they pack onto a single line.
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
|
||||||
// Install bundled (embedded daemon + a bundled daemon present)
|
|
||||||
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
|
|
||||||
if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), btnFont)) {
|
|
||||||
s_settingsState.confirm_reinstall_daemon = true;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_daemon_install_bundled"));
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
ImGui::SameLine(0, Layout::spacingMd());
|
|
||||||
// Refresh (always available)
|
|
||||||
if (TactileButton(TR("refresh"), ImVec2(0, 0), btnFont)) {
|
|
||||||
s_settingsState.daemon_info_loaded = false; // recompute next frame
|
|
||||||
}
|
|
||||||
ImGui::SameLine(0, Layout::spacingMd());
|
|
||||||
// Test connection | Rescan blockchain (need an active RPC connection)
|
|
||||||
ImGui::BeginDisabled(!app->isConnected());
|
|
||||||
if (TactileButton(TR("test_connection"), ImVec2(0, 0), btnFont)) {
|
|
||||||
if (app->rpc() && app->rpc()->isConnected() && app->worker()) {
|
|
||||||
app->worker()->post([rpc = app->rpc()]() -> rpc::RPCWorker::MainCb {
|
|
||||||
try {
|
|
||||||
rpc::RPCClient::TraceScope trace("Settings / Test connection");
|
|
||||||
rpc->call("getinfo");
|
|
||||||
return []() {
|
|
||||||
Notifications::instance().success("RPC connection OK");
|
|
||||||
};
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::string err = e.what();
|
|
||||||
return [err]() {
|
|
||||||
Notifications::instance().error("RPC error: " + err);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Notifications::instance().warning("Not connected to daemon");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_test_conn"));
|
|
||||||
ImGui::SameLine(0, Layout::spacingMd());
|
|
||||||
if (TactileButton(TR("rescan"), ImVec2(0, 0), btnFont)) {
|
|
||||||
s_settingsState.confirm_rescan = true;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_rescan"));
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
ImGui::SameLine(0, Layout::spacingMd());
|
|
||||||
// Delete blockchain | Repair wallet (embedded daemon only)
|
|
||||||
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon());
|
|
||||||
if (TactileButton(TR("delete_blockchain"), ImVec2(0, 0), btnFont)) {
|
|
||||||
s_settingsState.confirm_delete_blockchain = true;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_delete_blockchain"));
|
|
||||||
ImGui::SameLine(0, Layout::spacingMd());
|
|
||||||
if (TactileButton(TR("repair_wallet"), ImVec2(0, 0), btnFont)) {
|
|
||||||
s_settingsState.confirm_repair_wallet = true;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_repair_wallet"));
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
@@ -2273,6 +2131,132 @@ void RenderSettingsPage(App* app) {
|
|||||||
// Advance cursor past both columns
|
// Advance cursor past both columns
|
||||||
float maxBottom = std::max(leftBottom, rightBottom);
|
float maxBottom = std::max(leftBottom, rightBottom);
|
||||||
ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, maxBottom));
|
ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, maxBottom));
|
||||||
|
|
||||||
|
// ---- DAEMON BINARY — full-width row beneath the NODE + SECURITY columns ----
|
||||||
|
if (app->supportsFullNodeLifecycleActions()) {
|
||||||
|
ImFont* dbBtnFont = S.resolveFont("button");
|
||||||
|
if (!s_settingsState.daemon_info_loaded) {
|
||||||
|
s_settingsState.installed_daemon = dragonx::resources::getInstalledDaemonInfo();
|
||||||
|
s_settingsState.bundled_daemon = dragonx::resources::getBundledDaemonInfo();
|
||||||
|
s_settingsState.daemon_info_loaded = true;
|
||||||
|
}
|
||||||
|
const auto& inst = s_settingsState.installed_daemon;
|
||||||
|
const auto& bun = s_settingsState.bundled_daemon;
|
||||||
|
|
||||||
|
auto fmtDate = [](std::int64_t epoch) -> std::string {
|
||||||
|
if (epoch <= 0) return "—";
|
||||||
|
std::time_t t = static_cast<std::time_t>(epoch);
|
||||||
|
std::tm tmv{};
|
||||||
|
#ifdef _WIN32
|
||||||
|
localtime_s(&tmv, &t);
|
||||||
|
#else
|
||||||
|
localtime_r(&t, &tmv);
|
||||||
|
#endif
|
||||||
|
char buf[32];
|
||||||
|
std::strftime(buf, sizeof(buf), "%Y-%m-%d", &tmv);
|
||||||
|
return std::string(buf);
|
||||||
|
};
|
||||||
|
|
||||||
|
const float dbLeftX = sectionOrigin.x;
|
||||||
|
const ImVec4 dbDim = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium());
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingLg()));
|
||||||
|
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_binary"));
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
|
|
||||||
|
// Installed | Bundled side by side across the full container width.
|
||||||
|
const float dbStartY = ImGui::GetCursorScreenPos().y;
|
||||||
|
const float dbLineH = ImGui::GetTextLineHeightWithSpacing();
|
||||||
|
const float dbCol2X = dbLeftX + std::min(contentW * 0.4f, 340.0f);
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY));
|
||||||
|
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_installed"));
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY + dbLineH));
|
||||||
|
if (inst.exists) {
|
||||||
|
ImGui::TextUnformatted(inst.version.empty() ? TR("unknown") : inst.version.c_str());
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY + dbLineH * 2));
|
||||||
|
ImGui::TextColored(dbDim, "%s · %s",
|
||||||
|
util::Platform::formatFileSize(inst.size).c_str(),
|
||||||
|
fmtDate(inst.modifiedEpoch).c_str());
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(dbDim, "%s", TR("daemon_not_installed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY));
|
||||||
|
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_bundled"));
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH));
|
||||||
|
if (bun.available) {
|
||||||
|
ImGui::TextUnformatted(bun.version.empty() ? TR("unknown") : bun.version.c_str());
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH * 2));
|
||||||
|
ImGui::TextColored(dbDim, "%s", util::Platform::formatFileSize(bun.size).c_str());
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(dbDim, "%s", TR("daemon_none_bundled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY + dbLineH * 3 + Layout::spacingXs()));
|
||||||
|
if (bun.available) {
|
||||||
|
const bool sameSize = inst.exists && inst.size == bun.size;
|
||||||
|
if (!inst.exists)
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_missing"));
|
||||||
|
else if (sameSize)
|
||||||
|
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("daemon_status_match"));
|
||||||
|
else
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_differ"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
|
// All node actions on one full-width toolbar row: daemon-binary actions first
|
||||||
|
// (Install bundled | Refresh), then maintenance (Test connection | Rescan |
|
||||||
|
// Delete blockchain | Repair wallet).
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
|
||||||
|
if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), dbBtnFont)) {
|
||||||
|
s_settingsState.confirm_reinstall_daemon = true;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_daemon_install_bundled"));
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine(0, Layout::spacingMd());
|
||||||
|
if (TactileButton(TR("refresh"), ImVec2(0, 0), dbBtnFont)) {
|
||||||
|
s_settingsState.daemon_info_loaded = false;
|
||||||
|
}
|
||||||
|
ImGui::SameLine(0, Layout::spacingMd());
|
||||||
|
ImGui::BeginDisabled(!app->isConnected());
|
||||||
|
if (TactileButton(TR("test_connection"), ImVec2(0, 0), dbBtnFont)) {
|
||||||
|
if (app->rpc() && app->rpc()->isConnected() && app->worker()) {
|
||||||
|
app->worker()->post([rpc = app->rpc()]() -> rpc::RPCWorker::MainCb {
|
||||||
|
try {
|
||||||
|
rpc::RPCClient::TraceScope trace("Settings / Test connection");
|
||||||
|
rpc->call("getinfo");
|
||||||
|
return []() { Notifications::instance().success("RPC connection OK"); };
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::string err = e.what();
|
||||||
|
return [err]() { Notifications::instance().error("RPC error: " + err); };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Notifications::instance().warning("Not connected to daemon");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_test_conn"));
|
||||||
|
ImGui::SameLine(0, Layout::spacingMd());
|
||||||
|
if (TactileButton(TR("rescan"), ImVec2(0, 0), dbBtnFont)) {
|
||||||
|
s_settingsState.confirm_rescan = true;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_rescan"));
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine(0, Layout::spacingMd());
|
||||||
|
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon());
|
||||||
|
if (TactileButton(TR("delete_blockchain"), ImVec2(0, 0), dbBtnFont)) {
|
||||||
|
s_settingsState.confirm_delete_blockchain = true;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_delete_blockchain"));
|
||||||
|
ImGui::SameLine(0, Layout::spacingMd());
|
||||||
|
if (TactileButton(TR("repair_wallet"), ImVec2(0, 0), dbBtnFont)) {
|
||||||
|
s_settingsState.confirm_repair_wallet = true;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_repair_wallet"));
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, bottomPad));
|
ImGui::Dummy(ImVec2(0, bottomPad));
|
||||||
|
|||||||
Reference in New Issue
Block a user