import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Plus, Pencil, Trash2, RefreshCw, GitBranch, Container, Globe, Settings2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { PageShell } from '@/components/layout/PageShell'; import { ContentPanel } from '@/components/layout/ContentPanel'; import { PageEmpty } from '@/components/layout/PageEmpty'; import { useSearchParams } from 'react-router-dom'; import BuildNodesManager from './BuildNodesManager'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { toast } from 'sonner'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { deployApi } from '@/api'; import CredentialFormDialog from './CredentialFormDialog'; import ToolConfigTab from './ToolConfigTab'; import type { JsonData } from '@/types/common'; interface Repo { id: number; name: string; description: string; url: string; auth_type: string; credential_id: number | null; enabled: boolean; created_by: string; updated_at: string; } interface Registry { id: number; name: string; description: string; registry_url: string; namespace: string; auth_type: string; credential_id: number | null; enabled: boolean; created_by: string; updated_at: string; } interface Environment { id: number; name: string; display_name: string; type: string; color: string; sort_order: number; description: string; enabled: boolean; } function EnvFormDialog({ open, onClose, onSaved, editData }: { open: boolean; onClose: () => void; onSaved: () => void; editData: Environment | null }) { const { t } = useTranslation(); const [name, setName] = useState(''); const [displayName, setDisplayName] = useState(''); const [envType, setEnvType] = useState('development'); const [color, setColor] = useState('#3B82F6'); const [description, setDescription] = useState(''); const [sortOrder, setSortOrder] = useState(50); const [submitting, setSubmitting] = useState(false); useEffect(() => { if (editData) { setName(editData.name); setDisplayName(editData.display_name); setEnvType(editData.type); setColor(editData.color); setDescription(editData.description || ''); setSortOrder(editData.sort_order); } else { setName(''); setDisplayName(''); setEnvType('development'); setColor('#3B82F6'); setDescription(''); setSortOrder(50); } }, [editData, open]); const handleSubmit = async () => { if (!name.trim() || !displayName.trim()) { toast.error(t('deploy.form.nameRequired')); return; } setSubmitting(true); try { if (editData) await deployApi.updateEnvironment(editData.id, { name, display_name: displayName, type: envType, color, sort_order: sortOrder, description }); else await deployApi.createEnvironment({ name, display_name: displayName, type: envType, color, sort_order: sortOrder, description }); toast.success(editData ? t('deploy.toast.updated') : t('deploy.toast.created')); onSaved(); onClose(); } catch { toast.error(t('deploy.toast.operationFailed')); } finally { setSubmitting(false); } }; return ( {editData ? t('deploy.env.editEnv') : t('deploy.env.addEnv')}
setName(e.target.value)} placeholder="production" />
setDisplayName(e.target.value)} placeholder={t('deploy.env.prodEnv')} />
setColor(e.target.value)} type="color" className="w-16 h-9 p-1" />
setSortOrder(Number(e.target.value))} type="number" />
setDescription(e.target.value)} placeholder={t('common.optional')} />
); } const CONFIG_TABS = ['repos', 'registries', 'environments', 'tools', 'build-nodes'] as const; export default function DeployCredentials() { const { t } = useTranslation(); const _confirm = useConfirm(); const [searchParams] = useSearchParams(); const tabFromUrl = searchParams.get('tab'); const [repos, setRepos] = useState([]); const [registries, setRegistries] = useState([]); const [environments, setEnvironments] = useState([]); const [loading, setLoading] = useState(false); const [editorOpen, setEditorOpen] = useState(false); const [editType, setEditType] = useState<'repo' | 'registry'>('repo'); const [editingId, setEditingId] = useState(null); const [envDialogOpen, setEnvDialogOpen] = useState(false); const [editEnv, setEditEnv] = useState(null); const [activeTab, setActiveTab] = useState( tabFromUrl && (CONFIG_TABS as readonly string[]).includes(tabFromUrl) ? tabFromUrl : 'repos', ); const fetchAll = async () => { setLoading(true); try { const [rR, rReg, rEnv] = await Promise.all([deployApi.getRepos(), deployApi.getRegistries(), deployApi.getEnvironments()]); setRepos((rR as JsonData)?.list || []); setRegistries((rReg as JsonData)?.list || []); setEnvironments((rEnv as JsonData)?.list || []); } catch { toast.error(t('deploy.toast.getDataFailed')); } finally { setLoading(false); } }; useEffect(() => { fetchAll(); }, []); const handleDelete = async (type: 'repo' | 'registry' | 'env', item: JsonData) => { const ok = await _confirm({ title: t('common.delete'), description: t('deploy.toast.deleteConfirm', { name: item.name || item.display_name }), confirmText: t('common.delete'), variant: 'destructive' }); if (!ok) return; try { if (type === 'repo') await deployApi.deleteRepo(item.id); else if (type === 'registry') await deployApi.deleteRegistry(item.id); else await deployApi.deleteEnvironment(item.id); toast.success(t('deploy.toast.deleted')); fetchAll(); } catch { toast.error(t('deploy.toast.deleteFailed')); } }; const envTypeLabel: Record = { production: t('deploy.env.production'), staging: t('deploy.env.staging'), testing: t('deploy.env.testing'), development: t('deploy.env.development') }; const pageTabs = [ { id: 'repos', label: t('deploy.credForm.gitRepo'), badge: repos.length || undefined }, { id: 'registries', label: t('deploy.credForm.imageRegistry'), badge: registries.length || undefined }, { id: 'environments', label: t('deploy.label.envManage'), badge: environments.length || undefined }, { id: 'build-nodes', label: t('deploy.deployNav.buildNodes') }, { id: 'tools', label: t('deploy.tool.title') }, ]; return ( } loading={loading} tabs={pageTabs} activeTab={activeTab} onTabChange={setActiveTab} actions={activeTab === 'build-nodes' ? undefined : ( <> )} contentClassName="space-y-4" > {activeTab === 'repos' && ( repos.length === 0 ? ( } title={t('deploy.credForm.noRepo')} /> ) : (
{repos.map(r => ( ))}
{t('deploy.table.name')} {t('deploy.table.repoUrl')} {t('deploy.cred.authMethod')} {t('deploy.table.status')} {t('common.actions')}
{r.name} {r.url} {r.auth_type} {r.enabled ? t('common.enable') : t('common.disable')}
) )} {activeTab === 'registries' && ( registries.length === 0 ? ( } title={t('deploy.credForm.noRegistry')} /> ) : (
{registries.map(r => ( ))}
{t('deploy.table.name')} Registry URL {t('deploy.credForm.namespace')} {t('deploy.cred.authMethod')} {t('deploy.table.status')} {t('common.actions')}
{r.name} {r.registry_url} {r.namespace || '-'} {r.auth_type} {r.enabled ? t('common.enable') : t('common.disable')}
) )} {activeTab === 'environments' && ( <>
{environments.length === 0 ? ( } title="暂无环境配置" description='点击"新增环境"创建' action={( )} /> ) : (
{environments.map(env => ( ))}
环境标识 显示名称 类型 颜色 排序 状态 操作
{env.name} {env.display_name} {envTypeLabel[env.type] || env.type}
{env.sort_order} {env.enabled ? '启用' : '禁用'}
)} )} {activeTab === 'build-nodes' && } {activeTab === 'tools' && } setEnvDialogOpen(false)} onSaved={fetchAll} editData={editEnv} />
); }