feat(lite): real backend integration — controller, M0-M2a wiring, smoke tool, tests
- LiteWalletController (src/wallet/lite_wallet_controller.*): App-owned; runs real create/open/restore via the linked SDXL bridge with allowBridgeCalls=true; wipes seed/passphrase with sodium_memzero; persists on a ready wallet. M2a: applyLiteRefreshModelToWalletState maps a parsed refresh bundle into WalletState (zatoshi->DRGX, z/t split, tx typing + confirmations, sync progress). - App wiring: liteWallet() accessor + init() construction when supportsLiteBackend(); persist -> settings save. - settings_page: "Validate" reroutes to the controller for real execution (validation- only fallback otherwise); wipes UI secret buffers after submit. - chain name default -> "main" with load-time migration of legacy "DRAGONX" (settings.cpp), preventing the backend "Unknown chain" panic. - M0: build.sh --lite-backend flag; lite_smoke real-backend tool + CMake targets; tests/fake_lite_backend.h deterministic harness. - Tests (test_phase4): injectable-fake bridge, controller lifecycle, chain-name migration, refresh->WalletState mapping; plus the lite test-suite churn-cleanup rewrite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
#include "../../app.h"
|
||||
#include "../../config/version.h"
|
||||
#include "../../config/settings.h"
|
||||
#include "../../wallet/lite_wallet_lifecycle_ui_adapter.h"
|
||||
#include "../../wallet/lite_wallet_server_selection_adapter.h"
|
||||
#include "../../wallet/lite_wallet_controller.h"
|
||||
#include <sodium.h>
|
||||
#include "../../util/logger.h"
|
||||
#include "../windows/balance_tab.h"
|
||||
#include "../windows/console_tab.h"
|
||||
@@ -42,6 +46,8 @@
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
@@ -99,6 +105,22 @@ struct SettingsPageState {
|
||||
LowSpecSnapshot low_spec_snapshot;
|
||||
bool keep_daemon_running = false;
|
||||
bool stop_external_daemon = false;
|
||||
int lite_server_mode = 0;
|
||||
char lite_server_url[256] = "https://lite.dragonx.is";
|
||||
int lite_random_seed = 0;
|
||||
bool lite_persist_selected_server = true;
|
||||
std::vector<wallet::LiteServerEndpoint> lite_servers;
|
||||
std::string lite_server_status;
|
||||
bool lite_lifecycle_expanded = false;
|
||||
int lite_lifecycle_operation = 0;
|
||||
char lite_wallet_path[256] = "";
|
||||
char lite_lifecycle_passphrase[128] = "";
|
||||
char lite_restore_seed[512] = "";
|
||||
int lite_restore_birthday = 0;
|
||||
int lite_restore_account = 0;
|
||||
bool lite_restore_overwrite = false;
|
||||
std::string lite_lifecycle_status;
|
||||
std::string lite_lifecycle_summary;
|
||||
bool mine_when_idle = false;
|
||||
int mine_idle_delay = 120;
|
||||
bool idle_thread_scaling = false;
|
||||
@@ -117,6 +139,148 @@ struct SettingsPageState {
|
||||
|
||||
static SettingsPageState s_settingsState;
|
||||
|
||||
static void copyToSettingsBuffer(char* dest, std::size_t destSize, const std::string& value) {
|
||||
if (!dest || destSize == 0) return;
|
||||
std::strncpy(dest, value.c_str(), destSize - 1);
|
||||
dest[destSize - 1] = '\0';
|
||||
}
|
||||
|
||||
static wallet::LiteConnectionSettings liteConnectionSettingsFromPageState(config::Settings* settings) {
|
||||
wallet::LiteConnectionSettings connectionSettings = settings
|
||||
? wallet::liteConnectionSettingsFromAppSettings(*settings)
|
||||
: wallet::defaultLiteConnectionSettings();
|
||||
if (!s_settingsState.lite_servers.empty()) {
|
||||
connectionSettings.servers = s_settingsState.lite_servers;
|
||||
}
|
||||
connectionSettings.selectionMode = s_settingsState.lite_server_mode == 1
|
||||
? wallet::LiteServerSelectionMode::Random
|
||||
: wallet::LiteServerSelectionMode::Sticky;
|
||||
connectionSettings.stickyServerUrl = s_settingsState.lite_server_url;
|
||||
connectionSettings.chainName = wallet::kDragonXLiteChainName;
|
||||
connectionSettings.randomSelectionSeed = static_cast<std::size_t>(std::max(0, s_settingsState.lite_random_seed));
|
||||
return connectionSettings;
|
||||
}
|
||||
|
||||
static wallet::LiteWalletLifecycleOperation liteLifecycleOperationFromPageState() {
|
||||
switch (s_settingsState.lite_lifecycle_operation) {
|
||||
case 1: return wallet::LiteWalletLifecycleOperation::OpenExisting;
|
||||
case 2: return wallet::LiteWalletLifecycleOperation::RestoreFromSeed;
|
||||
default: return wallet::LiteWalletLifecycleOperation::CreateNew;
|
||||
}
|
||||
}
|
||||
|
||||
static void saveLiteServerSelectionFromPageState(App* app) {
|
||||
if (!app || !app->settings()) return;
|
||||
|
||||
const auto connectionSettings = liteConnectionSettingsFromPageState(app->settings());
|
||||
wallet::LiteWalletServerSelectionUiExecutionInput input;
|
||||
input.capabilities = app->walletCapabilities();
|
||||
input.intent.selectedServerIntentProvided = true;
|
||||
input.intent.selectionMode = connectionSettings.selectionMode;
|
||||
input.intent.selectedServerUrl = connectionSettings.stickyServerUrl;
|
||||
input.intent.randomSelectionSeed = connectionSettings.randomSelectionSeed;
|
||||
input.intent.chainName = connectionSettings.chainName;
|
||||
input.intent.replaceServers = true;
|
||||
input.intent.servers = connectionSettings.servers;
|
||||
input.persistence.settingsLoaded = true;
|
||||
input.persistence.havePersistedSelectionIntent = true;
|
||||
input.persistence.persistSelectedServer = s_settingsState.lite_persist_selected_server;
|
||||
input.persistence.persistenceOwnerReady = true;
|
||||
input.persistence.writeSettings = true;
|
||||
input.ui.selectedServerDisplayReady = true;
|
||||
input.ui.lifecycleUiOwnerReady = true;
|
||||
input.ui.operationConfirmed = true;
|
||||
input.ui.privateDataRedactionReady = true;
|
||||
input.ui.syncPlannerFeedReady = true;
|
||||
input.requireLifecycleReadiness = false;
|
||||
|
||||
const auto result = wallet::executeLiteWalletServerSelectionUi(*app->settings(), input);
|
||||
if (result.settingsWritten) {
|
||||
s_settingsState.lite_server_status = "Saved";
|
||||
} else if (!result.error.empty()) {
|
||||
s_settingsState.lite_server_status = result.error;
|
||||
Notifications::instance().warning(result.error);
|
||||
}
|
||||
}
|
||||
|
||||
static void evaluateLiteLifecycleRequestFromPageState(App* app) {
|
||||
if (!app || !app->settings()) return;
|
||||
|
||||
wallet::LiteWalletLifecycleUiExecutionInput input;
|
||||
input.capabilities = app->walletCapabilities();
|
||||
input.settingsLoaded = true;
|
||||
input.requirePersistedServerSelectionIntent = true;
|
||||
input.ui.selectedServerDisplayReady = true;
|
||||
input.ui.lifecycleUiOwnerReady = true;
|
||||
input.ui.operationConfirmed = true;
|
||||
input.ui.privateDataRedactionReady = true;
|
||||
input.ui.syncPlannerFeedReady = true;
|
||||
input.request.requestProvided = true;
|
||||
input.request.operation = liteLifecycleOperationFromPageState();
|
||||
|
||||
switch (input.request.operation) {
|
||||
case wallet::LiteWalletLifecycleOperation::CreateNew:
|
||||
input.request.createRequest.passphrase = s_settingsState.lite_lifecycle_passphrase;
|
||||
break;
|
||||
case wallet::LiteWalletLifecycleOperation::OpenExisting:
|
||||
input.request.openRequest.walletPath = s_settingsState.lite_wallet_path;
|
||||
input.request.openRequest.passphrase = s_settingsState.lite_lifecycle_passphrase;
|
||||
break;
|
||||
case wallet::LiteWalletLifecycleOperation::RestoreFromSeed:
|
||||
input.request.restoreRequest.walletPath = s_settingsState.lite_wallet_path;
|
||||
input.request.restoreRequest.seedPhrase = s_settingsState.lite_restore_seed;
|
||||
input.request.restoreRequest.passphrase = s_settingsState.lite_lifecycle_passphrase;
|
||||
input.request.restoreRequest.birthday = static_cast<unsigned long long>(std::max(0, s_settingsState.lite_restore_birthday));
|
||||
input.request.restoreRequest.account = static_cast<unsigned long long>(std::max(0, s_settingsState.lite_restore_account));
|
||||
input.request.restoreRequest.overwrite = s_settingsState.lite_restore_overwrite;
|
||||
break;
|
||||
}
|
||||
|
||||
// When a linked lite backend is present, execute the operation for real through the
|
||||
// App-owned controller. Otherwise fall back to the validation-only adapter.
|
||||
if (auto* lite = app->liteWallet()) {
|
||||
wallet::LiteWalletLifecycleResult result;
|
||||
switch (input.request.operation) {
|
||||
case wallet::LiteWalletLifecycleOperation::CreateNew:
|
||||
result = lite->createWallet(input.request.createRequest);
|
||||
break;
|
||||
case wallet::LiteWalletLifecycleOperation::OpenExisting:
|
||||
result = lite->openWallet(input.request.openRequest);
|
||||
break;
|
||||
case wallet::LiteWalletLifecycleOperation::RestoreFromSeed:
|
||||
result = lite->restoreWallet(input.request.restoreRequest);
|
||||
break;
|
||||
}
|
||||
|
||||
// Secrets have been consumed; wipe the UI buffers (and the request copies) so they
|
||||
// do not linger in memory after the attempt.
|
||||
sodium_memzero(s_settingsState.lite_lifecycle_passphrase, sizeof(s_settingsState.lite_lifecycle_passphrase));
|
||||
sodium_memzero(s_settingsState.lite_restore_seed, sizeof(s_settingsState.lite_restore_seed));
|
||||
|
||||
s_settingsState.lite_lifecycle_summary = result.bridgeResponseRedacted;
|
||||
if (result.walletReady) {
|
||||
s_settingsState.lite_lifecycle_status = "Wallet ready";
|
||||
} else {
|
||||
s_settingsState.lite_lifecycle_status = result.error.empty()
|
||||
? result.status.message
|
||||
: result.error;
|
||||
Notifications::instance().warning(s_settingsState.lite_lifecycle_status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = wallet::executeLiteWalletLifecycleUiRequest(*app->settings(), input);
|
||||
s_settingsState.lite_lifecycle_summary = result.requestSummaryRedacted;
|
||||
if (result.ok) {
|
||||
s_settingsState.lite_lifecycle_status = "Ready";
|
||||
} else {
|
||||
s_settingsState.lite_lifecycle_status = result.error.empty()
|
||||
? wallet::liteWalletLifecycleUiExecutionStatusName(result.status)
|
||||
: result.error;
|
||||
Notifications::instance().warning(s_settingsState.lite_lifecycle_status);
|
||||
}
|
||||
}
|
||||
|
||||
// (APPEARANCE card now uses ChannelsSplit like all other cards)
|
||||
|
||||
static void loadSettingsPageState(config::Settings* settings) {
|
||||
@@ -166,6 +330,17 @@ static void loadSettingsPageState(config::Settings* settings) {
|
||||
Layout::setUserFontScale(s_settingsState.font_scale); // sync with Layout on load
|
||||
s_settingsState.keep_daemon_running = settings->getKeepDaemonRunning();
|
||||
s_settingsState.stop_external_daemon = settings->getStopExternalDaemon();
|
||||
{
|
||||
const auto liteSettings = wallet::liteConnectionSettingsFromAppSettings(*settings);
|
||||
s_settingsState.lite_server_mode = liteSettings.selectionMode == wallet::LiteServerSelectionMode::Random ? 1 : 0;
|
||||
copyToSettingsBuffer(s_settingsState.lite_server_url,
|
||||
sizeof(s_settingsState.lite_server_url),
|
||||
liteSettings.stickyServerUrl);
|
||||
s_settingsState.lite_random_seed = static_cast<int>(liteSettings.randomSelectionSeed);
|
||||
s_settingsState.lite_persist_selected_server = settings->getLitePersistSelectedServer();
|
||||
s_settingsState.lite_servers = liteSettings.servers;
|
||||
s_settingsState.lite_server_status.clear();
|
||||
}
|
||||
s_settingsState.mine_when_idle = settings->getMineWhenIdle();
|
||||
s_settingsState.mine_idle_delay = settings->getMineIdleDelay();
|
||||
s_settingsState.idle_thread_scaling = settings->getIdleThreadScaling();
|
||||
@@ -220,6 +395,11 @@ static void saveSettingsPageState(config::Settings* settings) {
|
||||
settings->setFontScale(s_settingsState.font_scale);
|
||||
settings->setKeepDaemonRunning(s_settingsState.keep_daemon_running);
|
||||
settings->setStopExternalDaemon(s_settingsState.stop_external_daemon);
|
||||
{
|
||||
auto liteSettings = liteConnectionSettingsFromPageState(settings);
|
||||
wallet::applyLiteConnectionSettingsToAppSettings(*settings, liteSettings);
|
||||
settings->setLitePersistSelectedServer(s_settingsState.lite_persist_selected_server);
|
||||
}
|
||||
settings->setMineWhenIdle(s_settingsState.mine_when_idle);
|
||||
settings->setMineIdleDelay(s_settingsState.mine_idle_delay);
|
||||
settings->setIdleThreadScaling(s_settingsState.idle_thread_scaling);
|
||||
@@ -1073,7 +1253,7 @@ void RenderSettingsPage(App* app) {
|
||||
|
||||
// Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit
|
||||
{
|
||||
const bool showDaemonOptions = !app->isLiteBuild();
|
||||
const bool showDaemonOptions = app->supportsFullNodeLifecycleActions();
|
||||
float cbSpacing = Layout::spacingMd();
|
||||
float fh = ImGui::GetFrameHeight();
|
||||
float inner = ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
@@ -1221,6 +1401,7 @@ void RenderSettingsPage(App* app) {
|
||||
TR("tt_backup"),
|
||||
TR("tt_export_csv")
|
||||
};
|
||||
const bool showFullNodeLifecycleActions = app->supportsFullNodeLifecycleActions();
|
||||
const char* wizLabel = TR("setup_wizard");
|
||||
const char* bsLabel = TR("download_bootstrap");
|
||||
float sp = Layout::spacingSm();
|
||||
@@ -1230,9 +1411,10 @@ void RenderSettingsPage(App* app) {
|
||||
float naturalW = 0;
|
||||
for (int i = 0; i < 5; i++)
|
||||
naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
|
||||
float wizW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
|
||||
float bsW = ImGui::CalcTextSize(bsLabel).x + btnPadX;
|
||||
float totalW = naturalW + wizW + bsW + sp * 7;
|
||||
float wizW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(wizLabel).x + btnPadX : 0.0f;
|
||||
float bsW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(bsLabel).x + btnPadX : 0.0f;
|
||||
float totalW = naturalW + sp * 5;
|
||||
if (showFullNodeLifecycleActions) totalW += wizW + bsW + sp * 2;
|
||||
|
||||
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
|
||||
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
|
||||
@@ -1259,27 +1441,29 @@ void RenderSettingsPage(App* app) {
|
||||
ExportTransactionsDialog::show();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", t1[4]);
|
||||
|
||||
// Right-align Setup Wizard + Download Bootstrap
|
||||
float framePadX2 = ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
float curX = ImGui::GetCursorScreenPos().x;
|
||||
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + framePadX2;
|
||||
float bsBtnW = ImGui::CalcTextSize(bsLabel).x + framePadX2;
|
||||
float rightEdge = cardMin.x + availWidth - pad;
|
||||
float rightGroupW = bsBtnW + scaledSp + wizBtnW;
|
||||
float groupX = rightEdge - rightGroupW;
|
||||
if (groupX > curX) {
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorScreenPos(ImVec2(groupX, ImGui::GetCursorScreenPos().y));
|
||||
} else {
|
||||
if (showFullNodeLifecycleActions) {
|
||||
// Right-align Setup Wizard + Download Bootstrap
|
||||
float framePadX2 = ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
float curX = ImGui::GetCursorScreenPos().x;
|
||||
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + framePadX2;
|
||||
float bsBtnW = ImGui::CalcTextSize(bsLabel).x + framePadX2;
|
||||
float rightEdge = cardMin.x + availWidth - pad;
|
||||
float rightGroupW = bsBtnW + scaledSp + wizBtnW;
|
||||
float groupX = rightEdge - rightGroupW;
|
||||
if (groupX > curX) {
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorScreenPos(ImVec2(groupX, ImGui::GetCursorScreenPos().y));
|
||||
} else {
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
}
|
||||
if (TactileButton(bsLabel, ImVec2(0, 0), btnFont))
|
||||
BootstrapDownloadDialog::show(app);
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_download_bootstrap"));
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(wizLabel, ImVec2(0, 0), btnFont))
|
||||
app->restartWizard();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_wizard"));
|
||||
}
|
||||
if (TactileButton(bsLabel, ImVec2(0, 0), btnFont))
|
||||
BootstrapDownloadDialog::show(app);
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_download_bootstrap"));
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(wizLabel, ImVec2(0, 0), btnFont))
|
||||
app->restartWizard();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_wizard"));
|
||||
|
||||
if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
|
||||
}
|
||||
@@ -1342,6 +1526,189 @@ void RenderSettingsPage(App* app) {
|
||||
if (!body2Info) body2Info = Type().body2();
|
||||
ImGui::PushFont(body2Info);
|
||||
|
||||
if (app->isLiteBuild()) {
|
||||
float liteLabelW = std::min(leftColW * 0.35f, 132.0f);
|
||||
float liteInputW = std::max(80.0f, leftColW - liteLabelW - Layout::spacingSm());
|
||||
const char* modeLabels[] = {"Sticky", "Random"};
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Mode");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
if (ImGui::BeginCombo("##LiteServerMode", modeLabels[s_settingsState.lite_server_mode == 1 ? 1 : 0])) {
|
||||
for (int modeIndex = 0; modeIndex < 2; ++modeIndex) {
|
||||
const bool selected = s_settingsState.lite_server_mode == modeIndex;
|
||||
if (ImGui::Selectable(modeLabels[modeIndex], selected)) {
|
||||
s_settingsState.lite_server_mode = modeIndex;
|
||||
saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Preset");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
std::string presetPreview = s_settingsState.lite_server_url[0] != '\0'
|
||||
? std::string(s_settingsState.lite_server_url)
|
||||
: std::string("Select");
|
||||
for (const auto& server : s_settingsState.lite_servers) {
|
||||
if (server.url == s_settingsState.lite_server_url && !server.label.empty()) {
|
||||
presetPreview = server.label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
if (ImGui::BeginCombo("##LiteServerPreset", presetPreview.c_str())) {
|
||||
for (const auto& server : s_settingsState.lite_servers) {
|
||||
if (!server.enabled) continue;
|
||||
const std::string label = server.label.empty() ? server.url : server.label;
|
||||
const bool selected = server.url == s_settingsState.lite_server_url;
|
||||
if (ImGui::Selectable(label.c_str(), selected)) {
|
||||
copyToSettingsBuffer(s_settingsState.lite_server_url,
|
||||
sizeof(s_settingsState.lite_server_url),
|
||||
server.url);
|
||||
s_settingsState.lite_server_mode = 0;
|
||||
saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Server");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteServerUrl", s_settingsState.lite_server_url,
|
||||
sizeof(s_settingsState.lite_server_url));
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
s_settingsState.lite_server_mode = 0;
|
||||
saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
|
||||
if (s_settingsState.lite_server_mode == 1) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Seed");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
if (ImGui::InputInt("##LiteRandomSeed", &s_settingsState.lite_random_seed)) {
|
||||
if (s_settingsState.lite_random_seed < 0) s_settingsState.lite_random_seed = 0;
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (ImGui::Checkbox("Persist selected server##LitePersistServer",
|
||||
&s_settingsState.lite_persist_selected_server)) {
|
||||
saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
|
||||
if (!s_settingsState.lite_server_status.empty()) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
s_settingsState.lite_server_status.c_str());
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
if (ImGui::Button("Lite wallet request##LiteLifecycleToggle", ImVec2(liteInputW, 0))) {
|
||||
s_settingsState.lite_lifecycle_expanded = !s_settingsState.lite_lifecycle_expanded;
|
||||
}
|
||||
|
||||
if (s_settingsState.lite_lifecycle_expanded) {
|
||||
const char* lifecycleLabels[] = {"Create", "Open", "Restore"};
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Action");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
if (ImGui::BeginCombo("##LiteLifecycleOperation",
|
||||
lifecycleLabels[std::max(0, std::min(2, s_settingsState.lite_lifecycle_operation))])) {
|
||||
for (int operationIndex = 0; operationIndex < 3; ++operationIndex) {
|
||||
const bool selected = s_settingsState.lite_lifecycle_operation == operationIndex;
|
||||
if (ImGui::Selectable(lifecycleLabels[operationIndex], selected)) {
|
||||
s_settingsState.lite_lifecycle_operation = operationIndex;
|
||||
s_settingsState.lite_lifecycle_status.clear();
|
||||
s_settingsState.lite_lifecycle_summary.clear();
|
||||
}
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (s_settingsState.lite_lifecycle_operation == 1 ||
|
||||
s_settingsState.lite_lifecycle_operation == 2) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Wallet");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteWalletPath", s_settingsState.lite_wallet_path,
|
||||
sizeof(s_settingsState.lite_wallet_path));
|
||||
}
|
||||
|
||||
if (s_settingsState.lite_lifecycle_operation == 2) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Seed");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteRestoreSeed", s_settingsState.lite_restore_seed,
|
||||
sizeof(s_settingsState.lite_restore_seed),
|
||||
ImGuiInputTextFlags_Password);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Birthday");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
ImGui::InputInt("##LiteRestoreBirthday", &s_settingsState.lite_restore_birthday);
|
||||
if (s_settingsState.lite_restore_birthday < 0) s_settingsState.lite_restore_birthday = 0;
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Account");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
ImGui::InputInt("##LiteRestoreAccount", &s_settingsState.lite_restore_account);
|
||||
if (s_settingsState.lite_restore_account < 0) s_settingsState.lite_restore_account = 0;
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::Checkbox("Overwrite##LiteRestoreOverwrite",
|
||||
&s_settingsState.lite_restore_overwrite);
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Passphrase");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteLifecyclePassphrase",
|
||||
s_settingsState.lite_lifecycle_passphrase,
|
||||
sizeof(s_settingsState.lite_lifecycle_passphrase),
|
||||
ImGuiInputTextFlags_Password);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton("Validate##LiteLifecycleValidate", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
evaluateLiteLifecycleRequestFromPageState(app);
|
||||
}
|
||||
|
||||
if (!s_settingsState.lite_lifecycle_status.empty()) {
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
s_settingsState.lite_lifecycle_status.c_str());
|
||||
}
|
||||
if (!s_settingsState.lite_lifecycle_summary.empty()) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
s_settingsState.lite_lifecycle_summary.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
float rpcLblW = std::max(
|
||||
S.drawElement("components.settings-page", "rpc-label-min-width").size,
|
||||
std::min(leftColW * 0.35f, S.drawElement("components.settings-page", "rpc-label-width").size * hs));
|
||||
@@ -1476,7 +1843,7 @@ void RenderSettingsPage(App* app) {
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
// Node maintenance buttons (full-node build only)
|
||||
if (!app->isLiteBuild()) {
|
||||
if (app->supportsFullNodeLifecycleActions()) {
|
||||
ImFont* btnFont = S.resolveFont("button");
|
||||
float nodeBtnW;
|
||||
{
|
||||
@@ -1524,6 +1891,8 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
@@ -1950,7 +2319,7 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// "Restart daemon" button — only active when categories changed
|
||||
if (s_settingsState.debug_cats_dirty) {
|
||||
if (s_settingsState.debug_cats_dirty && app->supportsFullNodeLifecycleActions()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 218, 0, 255));
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
if (iconFont) {
|
||||
@@ -2085,7 +2454,8 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
|
||||
if (ImGui::Button(TrId("delete_blockchain_confirm", "del_bc_btn").c_str(), ImVec2(btnW, 40))) {
|
||||
app->deleteBlockchainData();
|
||||
if (app->supportsFullNodeLifecycleActions())
|
||||
app->deleteBlockchainData();
|
||||
s_settingsState.confirm_delete_blockchain = false;
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
Reference in New Issue
Block a user