import React, { useCallback, useEffect, useState } from 'react'; import { Card, Button, Space, Tag, Typography, Table, message, Tooltip, Empty, theme } from 'antd'; import { ReloadOutlined, ThunderboltOutlined, ClockCircleOutlined, RobotOutlined } from '@ant-design/icons'; import { useApp } from '@nocobase/client-v2'; import { useGitManager } from '../context/GitManagerContext'; import { useT } from '../locale'; const { Text } = Typography; const { useToken } = theme; interface PollerStatus { running: boolean; polling: boolean; lastTickAt: string | null; lastError: string | null; intervalSec: number; } export const PollingStatus: React.FC = () => { const t = useT(); const { token } = useToken(); const api = useApp().apiClient; const { repos, refreshRepos } = useGitManager(); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(false); const [pollingId, setPollingId] = useState(null); const [flows, setFlows] = useState([]); const reload = useCallback(async () => { setLoading(true); try { const [{ data }, flowsRes] = await Promise.all([ api.request({ url: 'gitManager:pollerStatus' }), api.request({ url: 'gitReviewFlows:list', params: { pageSize: 100, filter: { enabled: true, triggerMode: { $in: ['onMergeRequestCreated', 'both'] }, }, }, }), ]); setStatus(data?.data || null); setFlows(flowsRes?.data?.data || []); await refreshRepos(); } finally { setLoading(false); } }, [api, refreshRepos]); useEffect(() => { reload(); }, [reload]); const pollAll = async () => { setPollingId('all'); try { const { data } = await api.request({ url: 'gitManager:pollNow', method: 'post' }); const result = data?.data || {}; message.success( t('Polled {{scanned}} MRs, triggered {{triggered}} reviews', { scanned: result.scanned ?? 0, triggered: result.triggered ?? 0, }), ); reload(); } catch (err: any) { message.error(err?.response?.data?.errors?.[0]?.message || err?.message || t('Failed')); } finally { setPollingId(null); } }; const pollOne = async (repoId: number) => { setPollingId(repoId); try { const { data } = await api.request({ url: 'gitManager:pollNow', method: 'post', data: { repositoryId: repoId }, }); const result = data?.data || {}; message.success( t('Polled {{scanned}} MRs, triggered {{triggered}} reviews', { scanned: result.scanned ?? 0, triggered: result.triggered ?? 0, }), ); reload(); } catch (err: any) { message.error(err?.response?.data?.errors?.[0]?.message || err?.message || t('Failed')); } finally { setPollingId(null); } }; const autoRepos = repos.filter((r: any) => r.autoReview); return (
{t('Polling Status')} } extra={ } > {t('Background poller')}: {status?.running ? t('Running') : t('Stopped')} {status?.polling && {t('Polling now…')}} {t('Interval')}: {status?.intervalSec ? `${status.intervalSec}s` : '—'} {t('Last tick')}: {status?.lastTickAt ? new Date(status.lastTickAt).toLocaleString() : t('Never')} {status?.lastError && ( {t('Last error')}: {status.lastError} )} {t('Auto-Review Repositories')} } extra={ {t('Toggle Auto Review on the Repositories tab to enable polling')} } > {autoRepos.length === 0 ? ( ) : ( { const flow = flows.find((item) => Number(item.id) === Number(flowId)); const fallback = flows.find((item) => item.repositoryId === record.id || item.repositoryId == null); if (flow) return {flow.name}; if (fallback) return ( {t('Fallback')}: {fallback.name} ); return {t('No matching flow available')}; }, }, { title: t('Last Polled At'), dataIndex: 'lastPolledAt', width: 200, render: (v: string | null) => (v ? new Date(v).toLocaleString() : t('Never')), }, { title: '', key: 'actions', width: 120, render: (_: any, record: any) => ( ), }, ]} /> )} ); };