迁移范围: - deploy 模块 (PipelineEditor/PipelineDetailDialog/PipelineFormDialog/PipelineRunDialog/ReleaseCreateDialog/ReleaseDetailDialog/ReleaseManager/ToolConfigTab/JobRoleCreateDialog 等 14 文件) - kubernetes 模块 (YamlEditorToolbar/DeploymentHpaTab/DeploymentLogsTab/DeploymentPodsTab/DnsHostSection/SchedulingSection/EventsTab/HPACronHPATab/HistoryTab/PodsTab/ServicesTab/ActionModals 等 12 文件) - monitor 模块 (AlertEventsTab/AlertCenterDetail/DatasourcePage/LogAlertBell/LogCenter/LogCenterArchBanner/StatChartPanel/LogRoleCreateDialog 等 8 文件) - task 模块 (CronJobStatsPanel/CronJobTable/CronJobViewDialog/ExecutionHistoryDialog 4 文件) - layout (PageEmpty/PageLoading 2 文件) - site-monitor/snmp-monitor 模块 (6 文件) 翻译键: 3975 → 4003 (+28 缺失键补全), 22 命名空间, zh-CN/en-US 完全对齐 新增命名空间: agent/aiops/cloud/kubernetes/monitor/pipeline/rbac/system/task/ui 验证: go build✅ tsc -b=0 npm build✅ (20.78s) 17/17 集成测试通过
123 lines
4.8 KiB
TypeScript
123 lines
4.8 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Loader2, Save } from 'lucide-react';
|
|
import { matchPipelines, type PipelineMatchItem } from './matchPipeline';
|
|
|
|
interface JobRoleCreateDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
pipelines: PipelineMatchItem[];
|
|
saving: boolean;
|
|
name: string;
|
|
code: string;
|
|
pattern: string;
|
|
role: string;
|
|
description: string;
|
|
onNameChange: (v: string) => void;
|
|
onCodeChange: (v: string) => void;
|
|
onPatternChange: (v: string) => void;
|
|
onRoleChange: (v: string) => void;
|
|
onDescriptionChange: (v: string) => void;
|
|
onSubmit: () => void;
|
|
}
|
|
|
|
// 新建 Job 权限角色弹窗 // Logan.Li
|
|
export function JobRoleCreateDialog({
|
|
open,
|
|
onOpenChange,
|
|
pipelines,
|
|
saving,
|
|
name,
|
|
code,
|
|
pattern,
|
|
role,
|
|
description,
|
|
onNameChange,
|
|
onCodeChange,
|
|
onPatternChange,
|
|
onRoleChange,
|
|
onDescriptionChange,
|
|
onSubmit,
|
|
}: JobRoleCreateDialogProps) {
|
|
const { t } = useTranslation();
|
|
const preview = useMemo(() => matchPipelines(pattern, pipelines), [pattern, pipelines]);
|
|
const patternHint = useMemo(() => {
|
|
if (!pattern.trim()) return t('rbac.jobRole.patternHint.empty');
|
|
try {
|
|
// eslint-disable-next-line no-new
|
|
new RegExp(pattern);
|
|
return preview.length > 0 ? t('rbac.jobRole.patternHint.matched', { count: preview.length }) : t('rbac.jobRole.patternHint.noMatch');
|
|
} catch {
|
|
return t('rbac.jobRole.patternHint.syntaxError');
|
|
}
|
|
}, [pattern, preview.length, t]);
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-md bg-[var(--dg-bg-primary)] border-[var(--dg-border)]">
|
|
<DialogHeader>
|
|
<DialogTitle>{t('rbac.jobRole.newTitle')}</DialogTitle>
|
|
<DialogDescription className="text-[var(--dg-text-muted)]">
|
|
{t('rbac.jobRole.desc')}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-3">
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">{t('rbac.jobRole.label.name')}</Label>
|
|
<Input value={name} onChange={(e) => onNameChange(e.target.value)} placeholder={t('rbac.jobRole.placeholder.name')} className="h-8 text-sm dg-input" />
|
|
</div>
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">{t('rbac.jobRole.label.code')}</Label>
|
|
<Input value={code} onChange={(e) => onCodeChange(e.target.value)} placeholder={t('rbac.jobRole.placeholder.code')} className="h-8 text-sm font-mono dg-input" />
|
|
</div>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">{t('rbac.jobRole.label.pattern')}</Label>
|
|
<Input
|
|
value={pattern}
|
|
onChange={(e) => onPatternChange(e.target.value)}
|
|
placeholder="^cex-.* 或 ^api-server$"
|
|
className="h-8 text-sm font-mono dg-input"
|
|
/>
|
|
<p className="text-[10px] text-[var(--dg-text-muted)]">{patternHint}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">{t('rbac.jobRole.label.roleLevel')}</Label>
|
|
<Select value={role} onValueChange={onRoleChange}>
|
|
<SelectTrigger className="h-8 text-sm dg-input"><SelectValue /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="owner">{t('rbac.jobRole.role.owner')}</SelectItem>
|
|
<SelectItem value="editor">{t('rbac.jobRole.role.editor')}</SelectItem>
|
|
<SelectItem value="viewer">{t('rbac.jobRole.role.viewer')}</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">{t('rbac.jobRole.label.description')}</Label>
|
|
<Input value={description} onChange={(e) => onDescriptionChange(e.target.value)} placeholder={t('rbac.jobRole.placeholder.description')} className="h-8 text-sm dg-input" />
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>{t('rbac.jobRole.cancel')}</Button>
|
|
<Button onClick={onSubmit} disabled={saving}>
|
|
{saving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : <Save className="h-4 w-4 mr-1" />}
|
|
{t('rbac.jobRole.create')}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
} |