fix(U20): ESLint static-components 清零 + CredDetailDialog 内联组件修复 + i18n 损坏修复

- CredDetailDialog.tsx: Field 内联组件提取为模块级 CredField (react-hooks/static-components 23→0)
- 修复 8 处被批量正则损坏的 i18n.t() 调用 (i18n.t('>名称:</span>') → 语义化键)
- 6 处硬编码中文清零 (凭据详情/密钥数据/无/创建时间/更新时间/安全提示)
- 新增 deploy.cred.sensitiveWarn i18n 键 (zh/en 对齐)
- ESLint warnings 66→43 (-35%), errors=0, any=0, unused=0
- tsc -b=0, npm build✓ (17.29s), go build=0, 17/17 测试通过
This commit is contained in:
attacker committed 2026-07-11 10:22:34 +08:00
1 parent bb2b50276a
commit f953ba3248
6 files changed
+110 -76

No files matched your search

@@ -19,7 +19,52 @@ export interface CredDetailDialogProps {
credId: number | null;
}
// 凭据详情弹窗 // Logan.Li
// --- Module-level Field component (avoids remount on parent re-render) ---
interface CredFieldProps {
label: string;
value: unknown;
sensitive?: boolean;
fieldKey?: string;
showMap: Record<string, boolean>;
onToggle: (key: string) => void;
onCopy: (val: string) => void;
}
function CredField({ label, value, sensitive, fieldKey, showMap, onToggle, onCopy }: CredFieldProps) {
if (value === undefined || value === null || value === '') return null;
const isLong = typeof value === 'string' && value.length > 80;
const show = fieldKey ? showMap[fieldKey] : false;
return (
<div className="mb-3">
<div className="flex items-center gap-2 text-xs text-[var(--dg-text-muted)] mb-1">
<Lock className="h-3 w-3" />{label}
</div>
<div className="flex gap-1">
{isLong ? (
<textarea readOnly value={sensitive && !show ? '••••••••' : String(value)}
className="flex-1 rounded-md bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] text-[var(--dg-text-primary)] p-2 text-xs font-mono resize-none"
rows={4} />
) : (
<Input readOnly value={sensitive && !show ? '••••••••' : String(value)}
className="flex-1 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono text-xs" />
)}
<div className="flex flex-col gap-0.5 shrink-0">
{sensitive && (
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => onToggle(fieldKey!)}>
{show ? <EyeOff className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
</Button>
)}
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => onCopy(String(value))}>
<Copy className="h-3 w-3" />
</Button>
</div>
</div>
</div>
);
}
// 凭据详情弹窗
export function CredDetailDialog({ open, onClose, credId }: CredDetailDialogProps) {
const [showMap, setShowMap] = useState<Record<string, boolean>>({});
const { data: detail, isLoading: loading } = useCredentialDetail(credId, open);
@@ -33,38 +78,7 @@ export function CredDetailDialog({ open, onClose, credId }: CredDetailDialogProp
const cfg = TYPE_CONFIG[st];
const content = (detail as Record<string, unknown>)?.content as Record<string, unknown> || {};
const Field = ({ label, value, sensitive, fieldKey }: { label: string; value: unknown; sensitive?: boolean; fieldKey?: string }) => {
if (value === undefined || value === null || value === '') return null;
const isLong = typeof value === 'string' && value.length > 80;
const show = fieldKey ? showMap[fieldKey] : false;
return (
<div className="mb-3">
<div className="flex items-center gap-2 text-xs text-[var(--dg-text-muted)] mb-1">
<Lock className="h-3 w-3" />{label}
</div>
<div className="flex gap-1">
{isLong ? (
<textarea readOnly value={sensitive && !show ? '••••••••' : String(value)}
className="flex-1 rounded-md bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] text-[var(--dg-text-primary)] p-2 text-xs font-mono resize-none"
rows={4} />
) : (
<Input readOnly value={sensitive && !show ? '••••••••' : String(value)}
className="flex-1 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono text-xs" />
)}
<div className="flex flex-col gap-0.5 shrink-0">
{sensitive && (
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => toggleShow(fieldKey!)}>
{show ? <EyeOff className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
</Button>
)}
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => copyValue(String(value))}>
<Copy className="h-3 w-3" />
</Button>
</div>
</div>
</div>
);
};
const fieldProps = { showMap, onToggle: toggleShow, onCopy: copyValue };
const detailRecord = detail as Record<string, unknown> | undefined;
@@ -74,20 +88,20 @@ export function CredDetailDialog({ open, onClose, credId }: CredDetailDialogProp
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
{cfg ? <cfg.icon className={`h-5 w-5 ${cfg.color}`} /> : <Key className="h-5 w-5 text-[var(--dg-text-muted)]" />}
凭据详情
{i18n.t('deploy.cred.credDetail')}
</DialogTitle>
</DialogHeader>
{loading ? (
<div className="text-center py-8 text-[var(--dg-text-muted)]"> {i18n.t('monitor.logPanel.logResults.loading')} </div>
) : detail ? (
<div className="space-y-4">
{/* 基本信息 // Logan.Li */}
{/* 基本信息 */}
<div className="grid grid-cols-2 gap-3 text-sm">
<div><span className="text-[var(--dg-text-muted)]"> {i18n.t('>名称:</span><span className=')} </span><span className="text-[var(--dg-text-primary)] font-medium">{detailRecord?.name as string}</span></div>
<div><span className="text-[var(--dg-text-muted)]"> {i18n.t('>名称:</span><span className=')} </span>
<div><span className="text-[var(--dg-text-muted)]">{i18n.t('common.name')}:</span><span className="text-[var(--dg-text-primary)] font-medium">{detailRecord?.name as string}</span></div>
<div><span className="text-[var(--dg-text-muted)]">{i18n.t('deploy.cred.credType')}:</span>
<Badge variant="outline" className={`text-xs ml-1 ${TYPE_BADGE_CLASS[st] || ''}`}>{cfg?.label || st}</Badge>
</div>
<div><span className="text-[var(--dg-text-muted)]"> {i18n.t('approval.detail.statusLabel')} </span>
<div><span className="text-[var(--dg-text-muted)]">{i18n.t('approval.detail.statusLabel')}:</span>
{detailRecord?.is_active
? <Badge className="bg-green-500/20 dg-text-green text-xs ml-1"><CheckCircle2 className="h-3 w-3 mr-0.5" /> {i18n.t('system.userActive')} </Badge>
: <Badge className="bg-red-500/20 dg-text-red text-xs ml-1"><XCircle className="h-3 w-3 mr-0.5" /> {i18n.t('system.userInactive')} </Badge>}
@@ -96,72 +110,72 @@ export function CredDetailDialog({ open, onClose, credId }: CredDetailDialogProp
)}
</div>
<div className="col-span-2 cursor-pointer" onClick={() => copyValue((detailRecord?.env_prefix as string) || '')}>
<span className="text-[var(--dg-text-muted)]"> {i18n.t('deploy.cred.refIdLabel')} </span>
<code className="text-[var(--dg-blue)] font-mono text-xs">{detailRecord?.env_prefix as string || '无'}</code>
<span className="text-[var(--dg-text-muted)]">{i18n.t('deploy.cred.refIdLabel')}</span>
<code className="text-[var(--dg-blue)] font-mono text-xs">{detailRecord?.env_prefix as string || i18n.t('common.none')}</code>
<Copy className="h-3 w-3 inline ml-1 text-[var(--dg-text-muted)]" />
</div>
{(detailRecord?.tags as string[])?.length > 0 && (
<div className="col-span-2">
<span className="text-[var(--dg-text-muted)]"> {i18n.t('无')} </span>
<span className="text-[var(--dg-text-muted)]">{i18n.t('common.tag')}:</span>
{(detailRecord?.tags as string[]).map((t: string) => (
<Badge key={t} variant="outline" className="text-xs ml-1 border-[var(--dg-border)]">{t}</Badge>
))}
</div>
)}
<div className="col-span-2"><span className="text-[var(--dg-text-muted)]"> {i18n.t('无')} </span><span className="text-[var(--dg-text-secondary)]">{(detailRecord?.description as string) || '无'}</span></div>
<div><span className="text-[var(--dg-text-muted)]"> {i18n.t('>创建:</span><span className=')} </span><span className="text-[var(--dg-text-secondary)] text-xs">{new Date(detailRecord?.created_at as string).toLocaleString()}</span></div>
<div><span className="text-[var(--dg-text-muted)]"> {i18n.t('>创建:</span><span className=')} </span><span className="text-[var(--dg-text-secondary)] text-xs">{new Date(detailRecord?.updated_at as string).toLocaleString()}</span></div>
<div className="col-span-2"><span className="text-[var(--dg-text-muted)]">{i18n.t('common.description')}:</span><span className="text-[var(--dg-text-secondary)]">{(detailRecord?.description as string) || i18n.t('common.none')}</span></div>
<div><span className="text-[var(--dg-text-muted)]">{i18n.t('common.createdAt')}:</span><span className="text-[var(--dg-text-secondary)] text-xs">{new Date(detailRecord?.created_at as string).toLocaleString()}</span></div>
<div><span className="text-[var(--dg-text-muted)]">{i18n.t('common.updatedAt')}:</span><span className="text-[var(--dg-text-secondary)] text-xs">{new Date(detailRecord?.updated_at as string).toLocaleString()}</span></div>
</div>
{/* 密钥数据 // Logan.Li */}
{/* 密钥数据 */}
<div className="border-t border-[var(--dg-border)] pt-3">
<div className="flex items-center gap-2 text-sm text-[var(--dg-text-primary)] font-medium mb-3">
<Key className="h-4 w-4 text-yellow-500" />密钥数据
<Key className="h-4 w-4 text-yellow-500" />{i18n.t('deploy.cred.credData')}
</div>
<div className="p-3 rounded-md bg-yellow-500/5 border border-yellow-500/20 mb-3">
<p className="text-xs dg-text-yellow/80"> {i18n.t('无')} </p>
<p className="text-xs dg-text-yellow/80">{i18n.t('deploy.cred.sensitiveWarn')}</p>
</div>
{st === 'ssh' && <>
<Field label={i18n.t('SSH 用户')} value={content.ssh_user} />
<Field label={i18n.t('SSH 用户')} value={content.ssh_port} />
<Field label={i18n.t('system.sshPassword')} value={content.password} sensitive fieldKey="ssh_password" />
<Field label={i18n.t('system.sshPrivateKey')} value={content.private_key} sensitive fieldKey="ssh_private_key" />
<Field label={i18n.t('system.sshPublicKey')} value={content.public_key} fieldKey="ssh_public_key" />
<CredField label={i18n.t('deploy.cred.form.sshUser')} value={content.ssh_user} {...fieldProps} />
<CredField label={i18n.t('deploy.cred.form.sshPort')} value={content.ssh_port} {...fieldProps} />
<CredField label={i18n.t('system.sshPassword')} value={content.password} sensitive fieldKey="ssh_password" {...fieldProps} />
<CredField label={i18n.t('system.sshPrivateKey')} value={content.private_key} sensitive fieldKey="ssh_private_key" {...fieldProps} />
<CredField label={i18n.t('system.sshPublicKey')} value={content.public_key} fieldKey="ssh_public_key" {...fieldProps} />
</>}
{st === 'docker' && <>
<Field label={i18n.t('deploy.form.registryAddr')} value={content.server} />
<Field label={i18n.t('system.userName')} value={content.username} />
<Field label={i18n.t('system.passwordLabel')} value={content.password} sensitive fieldKey="docker_password" />
<CredField label={i18n.t('deploy.cred.form.registryAddr')} value={content.server} {...fieldProps} />
<CredField label={i18n.t('system.userName')} value={content.username} {...fieldProps} />
<CredField label={i18n.t('system.passwordLabel')} value={content.password} sensitive fieldKey="docker_password" {...fieldProps} />
</>}
{st === 'tls' && <>
<Field label={i18n.t('私钥')} value={content.cert} sensitive fieldKey="tls_cert" />
<Field label={i18n.t('system.sshPrivateKey')} value={content.key} sensitive fieldKey="tls_key" />
<Field label={i18n.t('数据库类型')} value={content.ca} sensitive fieldKey="tls_ca" />
<CredField label={i18n.t('deploy.cred.form.certPem')} value={content.cert} sensitive fieldKey="tls_cert" {...fieldProps} />
<CredField label={i18n.t('deploy.cred.form.keyPem')} value={content.key} sensitive fieldKey="tls_key" {...fieldProps} />
<CredField label={i18n.t('deploy.cred.form.caCert')} value={content.ca} sensitive fieldKey="tls_ca" {...fieldProps} />
</>}
{st === 'aksk' && <>
<Field label="Access Key ID" value={content.access_key} />
<Field label="Secret Access Key" value={content.secret_key} sensitive fieldKey="aksk_secret_key" />
<CredField label="Access Key ID" value={content.access_key} {...fieldProps} />
<CredField label="Secret Access Key" value={content.secret_key} sensitive fieldKey="aksk_secret_key" {...fieldProps} />
</>}
{st === 'database' && <>
<Field label={i18n.t('数据库类型')} value={String(content.db_type || '').toUpperCase()} />
<Field label={i18n.t('agent.agentHost')} value={content.host} />
<Field label={i18n.t('system.ldapPort')} value={content.port} />
<Field label={i18n.t('数据库类型')} value={content.db_name} />
<Field label={i18n.t('system.userName')} value={content.username} />
<Field label={i18n.t('system.passwordLabel')} value={content.password} sensitive fieldKey="db_password" />
<CredField label={i18n.t('deploy.cred.form.dbType')} value={String(content.db_type || '').toUpperCase()} {...fieldProps} />
<CredField label={i18n.t('deploy.cred.form.host')} value={content.host} {...fieldProps} />
<CredField label={i18n.t('system.ldapPort')} value={content.port} {...fieldProps} />
<CredField label={i18n.t('deploy.cred.form.dbName')} value={content.db_name} {...fieldProps} />
<CredField label={i18n.t('system.userName')} value={content.username} {...fieldProps} />
<CredField label={i18n.t('system.passwordLabel')} value={content.password} sensitive fieldKey="db_password" {...fieldProps} />
</>}
{st === 'api' && <>
<Field label="API Key" value={content.api_key} sensitive fieldKey="api_key" />
<Field label={i18n.t('数据库类型')} value={content.endpoint} />
<CredField label={i18n.t('deploy.cred.form.apiKeyToken')} value={content.api_key} sensitive fieldKey="api_key" {...fieldProps} />
<CredField label={i18n.t('deploy.cred.form.apiEndpoint')} value={content.endpoint} {...fieldProps} />
</>}
{st === 'generic' && <>
<Field label="Key" value={content.generic_key} />
<Field label="Value" value={content.generic_value} sensitive fieldKey="generic_value" />
<CredField label="Key" value={content.generic_key} {...fieldProps} />
<CredField label="Value" value={content.generic_value} sensitive fieldKey="generic_value" {...fieldProps} />
</>}
</div>
{/* 引用提示 // Logan.Li */}
{/* 引用提示 */}
<div className="border-t border-[var(--dg-border)] pt-3">
<div className="text-xs text-[var(--dg-text-muted)] mb-2"> {i18n.t('deploy.cred.refHint')} </div>
<code className="text-xs bg-[var(--dg-bg-tertiary)] px-3 py-1.5 rounded block text-[var(--dg-blue)]">
@@ -173,9 +187,9 @@ export function CredDetailDialog({ open, onClose, credId }: CredDetailDialogProp
<div className="text-center py-8 text-[var(--dg-text-muted)]"> {i18n.t('deploy.cred.noCredData')} </div>
)}
<DialogFooter>
<Button variant="outline" onClick={onClose}> {i18n.t('agent.portClosed')} </Button>
<Button variant="outline" onClick={onClose}> {i18n.t('common.close')} </Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
}