import React, { useCallback, useEffect, useState } from 'react'; import { Table, Button, Modal, Form, Input, Space, Tag, Popconfirm, Switch, Tooltip, Select, message } from 'antd'; import { PlusOutlined, DeleteOutlined, LinkOutlined, RobotOutlined } from '@ant-design/icons'; import { useApp } from '@nocobase/client-v2'; import { GitManagerProvider, useGitManager } from '../context/GitManagerContext'; import { useT } from '../locale'; export const RepositoryConfig: React.FC = () => { const t = useT(); const api = useApp().apiClient; const { repos, refreshRepos, selectedRepo, setSelectedRepo } = useGitManager(); const [modalOpen, setModalOpen] = useState(false); const [editingRepo, setEditingRepo] = useState(null); const [form] = Form.useForm(); const [actionLoading, setActionLoading] = useState(null); const [reviewFlows, setReviewFlows] = useState([]); const [gitAccounts, setGitAccounts] = useState([]); const loadReviewFlows = useCallback(async () => { const { data } = await api.request({ url: 'gitReviewFlows:list', params: { pageSize: 100, filter: { enabled: true, triggerMode: { $in: ['onMergeRequestCreated', 'both'] }, }, }, }); setReviewFlows(data?.data || []); }, [api]); const loadGitAccounts = useCallback(async () => { const { data } = await api.request({ url: 'gitAccounts:list', params: { pageSize: 100 } }); setGitAccounts(data?.data || []); }, [api]); useEffect(() => { loadReviewFlows().catch(() => undefined); loadGitAccounts().catch(() => undefined); }, [loadReviewFlows, loadGitAccounts]); useEffect(() => { refreshRepos().catch(() => undefined); }, [refreshRepos]); const getAutoFlowOptions = (repoId: number) => reviewFlows .filter((flow) => flow.repositoryId === repoId || flow.repositoryId == null) .map((flow) => ({ value: flow.id, label: `${flow.name}${flow.repositoryId == null ? ` (${t('global')})` : ''}`, })); const handleSave = async () => { const values = await form.validateFields(); try { if (editingRepo) { await api.request({ url: 'gitRepositories:update', method: 'post', params: { filterByTk: editingRepo.id }, data: values, }); message.success(t('Repository updated')); } else { await api.request({ url: 'gitRepositories:create', method: 'post', data: values, }); message.success(t('Repository added')); } setModalOpen(false); setEditingRepo(null); form.resetFields(); await refreshRepos(); } catch (err) { message.error(err?.message || t('Failed to save')); } }; const handleDelete = async (id: number) => { await api.request({ url: 'gitRepositories:destroy', method: 'post', params: { filterByTk: id } }); if (selectedRepo?.id === id) setSelectedRepo(null); message.success(t('Deleted')); await refreshRepos(); }; const handleClone = async (repo: any) => { setActionLoading(`clone-${repo.id}`); try { await api.request({ url: 'gitManager:clone', method: 'post', params: { repositoryId: repo.id }, }); message.success(t('Clone successful')); await refreshRepos(); } catch (err) { message.error(err?.response?.data?.errors?.[0]?.message || t('Clone failed')); } finally { setActionLoading(null); } }; const columns = [ { title: t('Repository Name'), dataIndex: 'name', key: 'name', render: (text: string, record: any) => setSelectedRepo(record)}>{text}, }, { title: t('Repository URL'), dataIndex: 'repoUrl', key: 'repoUrl', ellipsis: true }, { title: t('Git Account'), dataIndex: 'gitAccountId', key: 'gitAccountId', width: 160, render: (gitAccountId: number | null) => { if (!gitAccountId) return {t('None')}; const account = gitAccounts.find((acc) => acc.id === gitAccountId); return account ? account.name : {t('None')}; }, }, { title: t('Local Path'), dataIndex: 'localPath', key: 'localPath', ellipsis: true }, { title: t('Default Branch'), dataIndex: 'defaultBranch', key: 'defaultBranch', width: 120 }, { title: ( {t('Auto Review')} ), dataIndex: 'autoReview', key: 'autoReview', width: 110, render: (v: boolean, record: any) => ( { try { const options = getAutoFlowOptions(record.id); await api.request({ url: 'gitRepositories:update', method: 'post', params: { filterByTk: record.id }, data: { autoReview: checked, autoReviewFlowId: checked && !record.autoReviewFlowId ? options[0]?.value ?? null : record.autoReviewFlowId, }, }); await refreshRepos(); } catch (err: any) { message.error(err?.message || t('Failed to save')); } }} /> ), }, { title: t('Primary Auto Flow'), dataIndex: 'autoReviewFlowId', key: 'autoReviewFlowId', width: 220, render: (value: number | null, record: any) => { const options = getAutoFlowOptions(record.id); return (