feat(mining-admin): 算力同步完成前禁用激活挖矿按钮
- 后端:getMiningStatus 接口并行获取 contribution-service 总算力,对比两边是否一致 - 前端:未同步时显示"全网算力同步中..."提示,禁用激活按钮 - 前端:同步中每 3 秒刷新状态,同步完成后恢复 30 秒刷新 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
72b3b44d37
commit
7c00c900a0
|
|
@ -41,21 +41,45 @@ export class ConfigController {
|
||||||
@ApiOperation({ summary: '获取挖矿状态' })
|
@ApiOperation({ summary: '获取挖矿状态' })
|
||||||
async getMiningStatus() {
|
async getMiningStatus() {
|
||||||
const miningServiceUrl = this.appConfigService.get<string>('MINING_SERVICE_URL', 'http://localhost:3021');
|
const miningServiceUrl = this.appConfigService.get<string>('MINING_SERVICE_URL', 'http://localhost:3021');
|
||||||
|
const contributionServiceUrl = this.appConfigService.get<string>('CONTRIBUTION_SERVICE_URL', 'http://localhost:3020');
|
||||||
|
|
||||||
this.logger.log(`Fetching mining status from ${miningServiceUrl}/api/v2/admin/status`);
|
this.logger.log(`Fetching mining status from ${miningServiceUrl}/api/v2/admin/status`);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${miningServiceUrl}/api/v2/admin/status`);
|
// 并行获取 mining-service 状态和 contribution-service 总算力
|
||||||
if (!response.ok) {
|
const [miningResponse, contributionResponse] = await Promise.all([
|
||||||
throw new Error(`Failed to fetch mining status: ${response.status}`);
|
fetch(`${miningServiceUrl}/api/v2/admin/status`),
|
||||||
|
fetch(`${contributionServiceUrl}/api/v1/contribution/stats`).catch(() => null),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!miningResponse.ok) {
|
||||||
|
throw new Error(`Failed to fetch mining status: ${miningResponse.status}`);
|
||||||
}
|
}
|
||||||
const result = await response.json();
|
const miningResult = await miningResponse.json();
|
||||||
this.logger.log(`Mining service response: ${JSON.stringify(result)}`);
|
this.logger.log(`Mining service response: ${JSON.stringify(miningResult)}`);
|
||||||
if (result.data) {
|
|
||||||
return result.data;
|
// 获取 contribution-service 的总有效算力
|
||||||
|
let contributionTotal: string | null = null;
|
||||||
|
if (contributionResponse && contributionResponse.ok) {
|
||||||
|
const contributionResult = await contributionResponse.json();
|
||||||
|
// contribution-service 返回的是 data.totalContribution
|
||||||
|
contributionTotal = contributionResult.data?.totalContribution || contributionResult.totalContribution || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const miningData = miningResult.data || miningResult;
|
||||||
|
const miningTotal = miningData.totalContribution || '0';
|
||||||
|
|
||||||
|
// 判断算力是否同步完成:两边总算力相等
|
||||||
|
const isSynced = contributionTotal !== null &&
|
||||||
|
parseFloat(contributionTotal) > 0 &&
|
||||||
|
Math.abs(parseFloat(miningTotal) - parseFloat(contributionTotal)) < 0.01;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initialized: false,
|
...miningData,
|
||||||
isActive: false,
|
contributionSyncStatus: {
|
||||||
error: 'Invalid response from mining service',
|
isSynced,
|
||||||
|
miningTotal,
|
||||||
|
contributionTotal: contributionTotal || '0',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Failed to get mining status', error);
|
this.logger.error('Failed to get mining status', error);
|
||||||
|
|
@ -63,6 +87,11 @@ export class ConfigController {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
error: `Unable to connect to mining service: ${error.message}`,
|
error: `Unable to connect to mining service: ${error.message}`,
|
||||||
|
contributionSyncStatus: {
|
||||||
|
isSynced: false,
|
||||||
|
miningTotal: '0',
|
||||||
|
contributionTotal: '0',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Pencil, Save, X, Play, Pause, AlertCircle, CheckCircle2 } from 'lucide-react';
|
import { Pencil, Save, X, Play, Pause, AlertCircle, CheckCircle2, Loader2 } from 'lucide-react';
|
||||||
import type { SystemConfig } from '@/types/config';
|
import type { SystemConfig } from '@/types/config';
|
||||||
|
|
||||||
const categoryLabels: Record<string, string> = {
|
const categoryLabels: Record<string, string> = {
|
||||||
|
|
@ -154,6 +154,20 @@ export default function ConfigsPage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 算力同步状态提示 */}
|
||||||
|
{miningStatus.contributionSyncStatus && !miningStatus.contributionSyncStatus.isSynced && (
|
||||||
|
<div className="flex items-center gap-2 p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin text-yellow-600" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-sm font-medium text-yellow-800 dark:text-yellow-200">全网算力同步中...</p>
|
||||||
|
<p className="text-xs text-yellow-600 dark:text-yellow-400">
|
||||||
|
已同步: {formatNumber(miningStatus.contributionSyncStatus.miningTotal)} /
|
||||||
|
总计: {formatNumber(miningStatus.contributionSyncStatus.contributionTotal)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex justify-end pt-4 border-t">
|
<div className="flex justify-end pt-4 border-t">
|
||||||
{miningStatus.isActive ? (
|
{miningStatus.isActive ? (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -167,10 +181,19 @@ export default function ConfigsPage() {
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => activateMining.mutate()}
|
onClick={() => activateMining.mutate()}
|
||||||
disabled={activateMining.isPending}
|
disabled={activateMining.isPending || (miningStatus.contributionSyncStatus && !miningStatus.contributionSyncStatus.isSynced)}
|
||||||
>
|
>
|
||||||
<Play className="h-4 w-4 mr-2" />
|
{miningStatus.contributionSyncStatus && !miningStatus.contributionSyncStatus.isSynced ? (
|
||||||
{activateMining.isPending ? '激活中...' : '激活挖矿'}
|
<>
|
||||||
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
全网算力同步中...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Play className="h-4 w-4 mr-2" />
|
||||||
|
{activateMining.isPending ? '激活中...' : '激活挖矿'}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { apiClient } from '@/lib/api/client';
|
import { apiClient } from '@/lib/api/client';
|
||||||
import type { SystemConfig } from '@/types/config';
|
import type { SystemConfig } from '@/types/config';
|
||||||
|
|
||||||
|
export interface ContributionSyncStatus {
|
||||||
|
isSynced: boolean;
|
||||||
|
miningTotal: string;
|
||||||
|
contributionTotal: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MiningStatus {
|
export interface MiningStatus {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
@ -15,6 +21,7 @@ export interface MiningStatus {
|
||||||
};
|
};
|
||||||
accountCount: number;
|
accountCount: number;
|
||||||
totalContribution: string;
|
totalContribution: string;
|
||||||
|
contributionSyncStatus?: ContributionSyncStatus;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,14 @@ export function useMiningStatus() {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['configs', 'mining-status'],
|
queryKey: ['configs', 'mining-status'],
|
||||||
queryFn: () => configsApi.getMiningStatus(),
|
queryFn: () => configsApi.getMiningStatus(),
|
||||||
refetchInterval: 30000,
|
// 当算力未同步完成时,每 3 秒刷新一次;同步完成后每 30 秒刷新一次
|
||||||
|
refetchInterval: (query) => {
|
||||||
|
const data = query.state.data;
|
||||||
|
if (data?.contributionSyncStatus && !data.contributionSyncStatus.isSynced) {
|
||||||
|
return 3000; // 3 秒
|
||||||
|
}
|
||||||
|
return 30000; // 30 秒
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue