Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,14 @@ app.get('/api/status', async (_req, res) => {
try {
const state = await loadState();
const containers = await getStackStatus(state.mode);


// Cap the Docker reachability probe so a slow daemon ping does not stall
// the /api/status response. The frontend has its own 1.5s abort on top.
const dockerReachable = await Promise.race([
isDockerAvailable(),
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 500)),
]);

const running = state.mode === 'jd'
? (containers.translator?.status === 'healthy' || containers.translator?.status === 'starting') &&
(containers.jdc?.status === 'healthy' || containers.jdc?.status === 'starting')
Expand All @@ -118,6 +125,7 @@ app.get('/api/status', async (_req, res) => {
? 'Sovereign Solo Mining'
: (state.data?.pool?.name ?? null),
containers,
docker: { reachable: dockerReachable },
};

res.json(response);
Expand Down
3 changes: 3 additions & 0 deletions server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export interface StatusResponse {
translator: ContainerStatus | null;
jdc: ContainerStatus | null;
};
docker: {
reachable: boolean;
};
}

export interface SetupResponse {
Expand Down
5 changes: 5 additions & 0 deletions src/hooks/useSetupStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export interface SetupStatus {
translator: { id: string; name: string; status: string } | null;
jdc: { id: string; name: string; status: string } | null;
};
docker?: {
reachable: boolean;
};
}

/**
Expand Down Expand Up @@ -73,6 +76,8 @@ export function useSetupStatus() {
mode: status?.mode ?? null,
poolName: status?.poolName ?? null,
containers: status?.containers ?? { translator: null, jdc: null },
// null when standalone or before first status response; true/false from the backend.
dockerReachable: status?.docker?.reachable ?? null,
// User needs setup if: orchestrated mode AND not yet configured
needsSetup: status !== null && status !== undefined && !status.configured,
refetch: query.refetch,
Expand Down
109 changes: 85 additions & 24 deletions src/pages/UnifiedDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function UnifiedDashboard() {
miningMode,
mode: templateMode,
poolName: configPoolName,
dockerReachable,
} = useSetupStatus();

// Header connection status (shared with Settings via hook)
Expand Down Expand Up @@ -122,19 +123,28 @@ export function UnifiedDashboard() {
const diagnostics = logDiagnostics?.diagnostics ?? [];

const [isStarting, setIsStarting] = useState(false);
const [startError, setStartError] = useState<string | null>(null);

const handleStartMining = async () => {
setIsStarting(true);
setStartError(null);
try {
const response = await fetch('/api/restart', { method: 'POST' });
if (response.ok) {
// Give containers time to start, then refresh health checks
setTimeout(() => {
window.location.reload();
}, 3000);
const errorData = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(
errorData.error || errorData.message || `Failed (${response.status})`,
);
}
// Give containers time to start, then refresh health checks
setTimeout(() => {
window.location.reload();
}, 3000);
} catch (error) {
console.error('Failed to start mining:', error);
setStartError(
error instanceof Error ? error.message : 'Failed to start mining services',
);
setIsStarting(false);
}
};
Expand Down Expand Up @@ -467,26 +477,77 @@ export function UnifiedDashboard() {

{/* Start Mining Banner (configured but stopped) */}
{configuredButStopped && showError && (
<div className="flex items-center justify-between gap-3 rounded-xl border border-primary/40 bg-primary/10 px-5 py-4 text-sm">
<div className="flex items-center gap-3">
<Play className="h-4 w-4 shrink-0 text-primary" />
<span>Mining services are stopped.</span>
dockerReachable === false ? (
<div className="flex flex-col gap-3 rounded-xl border border-red-500/40 bg-red-500/10 px-5 py-4 text-sm text-red-500 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-start gap-3">
<AlertTriangle className="h-4 w-4 shrink-0 mt-0.5" />
<div className="flex flex-col gap-1">
<span className="font-medium">Docker isn't running</span>
<span>We couldn't reach the Docker daemon, so mining services can't start.</span>
<span className="text-current/80">Start Docker Desktop or Docker Engine, then click Start Mining.</span>
</div>
</div>
<button
onClick={handleStartMining}
disabled={isStarting}
className="inline-flex h-9 shrink-0 items-center justify-center gap-2 rounded-full bg-red-500 px-4 font-medium text-white transition-colors hover:bg-red-600 disabled:opacity-50 sm:ml-4"
>
{isStarting ? (
<>
<div className="h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
Starting...
</>
) : (
'Start Mining'
)}
</button>
</div>
<button
onClick={handleStartMining}
disabled={isStarting}
className="h-9 px-4 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors font-medium flex items-center gap-2"
>
{isStarting ? (
<>
<div className="h-4 w-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
Starting...
</>
) : (
'Start Mining'
)}
</button>
</div>
) : startError ? (
<div className="flex flex-col gap-3 rounded-xl border border-red-500/40 bg-red-500/10 px-5 py-4 text-sm text-red-500 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-start gap-3">
<AlertTriangle className="h-4 w-4 shrink-0 mt-0.5" />
<div className="flex flex-col gap-1">
<span className="font-medium">Could not start mining services</span>
<span>{startError}</span>
</div>
</div>
<button
onClick={handleStartMining}
disabled={isStarting}
className="inline-flex h-9 shrink-0 items-center justify-center gap-2 rounded-full bg-red-500 px-4 font-medium text-white transition-colors hover:bg-red-600 disabled:opacity-50 sm:ml-4"
>
{isStarting ? (
<>
<div className="h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
Starting...
</>
) : (
'Try again'
)}
</button>
</div>
) : (
<div className="flex items-center justify-between gap-3 rounded-xl border border-primary/40 bg-primary/10 px-5 py-4 text-sm">
<div className="flex items-center gap-3">
<Play className="h-4 w-4 shrink-0 text-primary" />
<span>Mining services are stopped.</span>
</div>
<button
onClick={handleStartMining}
disabled={isStarting}
className="h-9 px-4 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors font-medium flex items-center gap-2"
>
{isStarting ? (
<>
<div className="h-4 w-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
Starting...
</>
) : (
'Start Mining'
)}
</button>
</div>
)
)}

{/* Connection Error Banner (not configured or unknown error) */}
Expand Down
Loading