feat(mining): persistent start/stop error caption + timeout-aware pool status
- A mining start/stop/pool failure now sets a persistent App::mining_last_error_ (cleared on the next attempt), rendered as a red caption with "Press Mine to try again" below the Mine button (plus a Dismiss, and a Windows-Defender "How to fix" on an AV block), so a crashed/blocked/failed miner is no longer just a button that "did nothing" once its toast faded. Two more hardcoded English strings (pool-payout-invalid, AV-blocked) moved to i18n. - Pool connection status is now a persistent "Status" row in the pool stats card (PoolMiningState.running_since / last_error): it flips from "Connecting…" to "Can't reach pool — check the URL" after a 30s timeout instead of sitting on red forever, and no longer vanishes once logs arrive. The old transient banner in mining_tab.cpp was removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -991,6 +991,30 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
||||
|
||||
// Persistent error caption (a crashed/AV-blocked/failed miner otherwise leaves a button that
|
||||
// "did nothing" once its toast faded). Normal ImGui flow, OUTSIDE the hand-drawn button card.
|
||||
const std::string& merr = app->miningLastError();
|
||||
if (!merr.empty() && !app->isMiningToggleInProgress()) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
ImGui::PushTextWrapPos(cardMin.x + availWidth);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Error()));
|
||||
ImGui::TextWrapped("%s", merr.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()));
|
||||
ImGui::TextUnformatted(TR("mining_error_retry_hint"));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopTextWrapPos();
|
||||
#ifdef _WIN32
|
||||
if (app->miningErrorIsAntivirus()) {
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (ImGui::SmallButton(TR("mining_error_av_help"))) app->showAntivirusHelp();
|
||||
}
|
||||
#endif
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
if (ImGui::SmallButton(TR("mining_error_dismiss"))) app->clearMiningError();
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
}
|
||||
|
||||
|
||||
@@ -248,6 +248,21 @@ static void RenderLeftPoolCard(App* app, const WalletState& state, ImDrawList* d
|
||||
else snprintf(uptBuf, sizeof(uptBuf), "%lldh %lldm", (long long)(up / 3600), (long long)((up % 3600) / 60));
|
||||
metricRow(TR("mining_uptime"), uptBuf, OnSurface());
|
||||
}
|
||||
// Connection status — replaces the old transient "Connecting" banner, with a timeout so a bad URL /
|
||||
// blocked port flips from "Connecting" to "Can't reach pool" instead of spinning on red forever.
|
||||
{
|
||||
static constexpr int kPoolConnectTimeoutSec = 30;
|
||||
const bool shares = pm.accepted + pm.rejected > 0;
|
||||
const bool ok = pm.connected || shares || pm.hashrate_10s > 0;
|
||||
const bool failed = pm.xmrig_running && !ok && pm.running_since > 0 &&
|
||||
(std::time(nullptr) - pm.running_since) >= kPoolConnectTimeoutSec;
|
||||
const char* stateText; ImU32 col;
|
||||
if (ok) { stateText = TR("mining_status_connected"); col = Success(); }
|
||||
else if (failed) { stateText = TR("mining_pool_unreachable"); col = Error(); }
|
||||
else if (pm.xmrig_running) { stateText = TR("mining_status_connecting"); col = Warning(); }
|
||||
else { stateText = TR("mining_status_stopped"); col = OnSurfaceDisabled(); }
|
||||
metricRow(TR("mining_status_label"), stateText, col);
|
||||
}
|
||||
y += gap * 0.5f;
|
||||
|
||||
// The pool list = official pools ∪ user-saved favorites ∪ the current custom pool.
|
||||
@@ -397,7 +412,7 @@ void RenderMiningStats(const WalletState& state, const MiningInfo& mining,
|
||||
const float mRowH = sub1->LegacySize + 7 * dp;
|
||||
const float listMinH = (capFont->LegacySize + 10 * dp) * 2.6f; // ~2–3 rows before scroll
|
||||
float leftContentH = pad * 0.75f + toggleH + gap
|
||||
+ mRowH * 4.0f + gap * 0.5f
|
||||
+ mRowH * 5.0f + gap * 0.5f // 4 metric rows + the connection-status row
|
||||
+ (ovFont->LegacySize + 4 * dp) + listMinH + pad * 0.75f;
|
||||
float cardH = std::max(leftContentH, statRowH + chartBudgetH + pad);
|
||||
|
||||
|
||||
@@ -335,24 +335,9 @@ static void RenderMiningTabContent(App* app)
|
||||
isMiningActive, s_pool_mode, availWidth, glassSpec, sHdr, gapOver);
|
||||
|
||||
// ================================================================
|
||||
// POOL CONNECTION STATUS — inline indicator (pool mode, no log)
|
||||
// ================================================================
|
||||
if (s_pool_mode && state.pool_mining.log_lines.empty() && state.pool_mining.xmrig_running) {
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
const char* dotIcon = ICON_MD_CIRCLE;
|
||||
ImU32 dotCol = state.pool_mining.connected ? WithAlpha(Success(), 200) : WithAlpha(Error(), 200);
|
||||
const char* statusText = state.pool_mining.connected
|
||||
? (state.pool_mining.pool_url.empty() ? TR("mining_connected") : state.pool_mining.pool_url.c_str())
|
||||
: TR("mining_connecting");
|
||||
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
ImVec2 dotSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, dotIcon);
|
||||
dl->AddText(iconFont, iconFont->LegacySize, pos, dotCol, dotIcon);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(pos.x + dotSz.x + 4 * dp, pos.y + (dotSz.y - capFont->LegacySize) * 0.5f),
|
||||
OnSurfaceMedium(), statusText);
|
||||
ImGui::Dummy(ImVec2(availWidth, capFont->LegacySize + 4 * dp));
|
||||
}
|
||||
// (Pool connection status now lives as a persistent, timeout-aware row in RenderLeftPoolCard
|
||||
// (mining_stats.cpp) — the old transient "Connecting" banner that vanished once logs arrived and
|
||||
// sat on red forever on failure was removed.)
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
Reference in New Issue
Block a user