refactor(i18n): U27 多模块 phantom key 批量清理 — Cloud/Deploy/Agent/Monitor/System 5 模块 25 文件, 180 个 phantom key → 51 残留, 129 个替换为语义化键, 新增 12 子命名空间, zh/en 7185→7294 +109 键对齐, tsc -b=0, npm build✓ (16.31s), ESLint=0/0/0, go build=0, 17/17 测试通过
This commit is contained in:
1 parent
7f5295cfc2
commit
b812b3b20c
27 files changed
+459
-148
No files matched your search
@@ -53,7 +53,7 @@ export function AgentAlerts() {
|
||||
setRules(asList(rulesRes));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(i18n.t('加载告警数据失败'));
|
||||
toast.error(i18n.t('agent.alerts.loadFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -65,11 +65,11 @@ export function AgentAlerts() {
|
||||
|
||||
// ============ 告警事件 ============
|
||||
const handleResolve = async (id: number) => {
|
||||
const ok = await confirm({ title: i18n.t('恢复告警'), description: i18n.t('确定将此告警标记为已恢复?'), confirmText: i18n.t('kubernetes.deployment.resume') });
|
||||
const ok = await confirm({ title: i18n.t('agent.alerts.resolveAlert'), description: i18n.t('agent.alerts.resolveConfirm'), confirmText: i18n.t('kubernetes.deployment.resume') });
|
||||
if (!ok) return;
|
||||
try {
|
||||
await agentApi.resolveAlert(id);
|
||||
toast.success(i18n.t('告警已恢复'));
|
||||
toast.success(i18n.t('agent.alerts.resolved'));
|
||||
loadData();
|
||||
} catch (err) {
|
||||
toast.error(err?.message || i18n.t('cloud.operationFailed'));
|
||||
@@ -88,11 +88,11 @@ export function AgentAlerts() {
|
||||
};
|
||||
|
||||
const handleDeleteRule = async (id: number) => {
|
||||
const ok = await confirm({ title: i18n.t('monitor.deleteRule'), description: i18n.t('删除规则'), confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive' });
|
||||
const ok = await confirm({ title: i18n.t('monitor.deleteRule'), description: i18n.t('agent.alerts.deleteRule'), confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive' });
|
||||
if (!ok) return;
|
||||
try {
|
||||
await agentApi.deleteRule(id);
|
||||
toast.success(i18n.t('告警已恢复'));
|
||||
toast.success(i18n.t('agent.alerts.resolved'));
|
||||
loadData();
|
||||
} catch (err) {
|
||||
toast.error(err?.message || i18n.t('kubernetes.msg.deleteFailed2'));
|
||||
@@ -114,7 +114,7 @@ export function AgentAlerts() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">Agent 告警</h2>
|
||||
<p className="text-sm dg-text-secondary mt-0.5"> {i18n.t('触发告警阈值')} </p>
|
||||
<p className="text-sm dg-text-secondary mt-0.5"> {i18n.t('agent.alerts.thresholdTriggered')} </p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={loadData} disabled={loading} className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]">
|
||||
<RefreshCw className={`h-4 w-4 mr-1.5 ${loading ? 'animate-spin' : ''}`} />
|
||||
@@ -160,7 +160,7 @@ export function AgentAlerts() {
|
||||
<Card className="dg-card">
|
||||
<CardContent className="flex flex-col items-center justify-center py-16">
|
||||
<CheckCircle className="h-10 w-10 text-emerald-500 mb-3" />
|
||||
<p className="dg-text-secondary">{resolvedView ? i18n.t('暂无已恢复的告警') : i18n.t('当前无活动告警,一切正常')}</p>
|
||||
<p className="dg-text-secondary">{resolvedView ? i18n.t('agent.alerts.noResolved') : i18n.t('agent.alerts.noActive')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -178,7 +178,7 @@ export function AgentAlerts() {
|
||||
{severityBadge(alert.severity)}
|
||||
<span className="font-medium text-[var(--dg-text-primary)]">{alert.rule_name || alert.name || alert.title || i18n.t('monitor.hostDashboard.alert')}</span>
|
||||
</div>
|
||||
<p className="text-sm dg-text-secondary mt-1">{alert.message || alert.description || i18n.t('触发告警阈值')}</p>
|
||||
<p className="text-sm dg-text-secondary mt-1">{alert.message || alert.description || i18n.t('agent.alerts.thresholdTriggered')}</p>
|
||||
<div className="flex items-center gap-4 mt-2 text-xs dg-text-muted flex-wrap">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Cpu className="h-3 w-3" />
|
||||
|
||||
@@ -71,7 +71,7 @@ export function AgentMonitor() {
|
||||
if (fresh) setSelectedHost(fresh);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!silent) toast.error(i18n.t('加载主机列表失败'));
|
||||
if (!silent) toast.error(i18n.t('agent.monitor.loadHostFailed'));
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -100,7 +100,7 @@ export function AgentMonitor() {
|
||||
// 提交远程命令(直连 SSH 即时返回)
|
||||
const submitCommand = async () => {
|
||||
if (!cmdCommand.trim()) {
|
||||
toast.error(i18n.t('请输入要执行的命令'));
|
||||
toast.error(i18n.t('agent.monitor.enterCommand'));
|
||||
return;
|
||||
}
|
||||
setCmdSubmitting(true);
|
||||
@@ -114,7 +114,7 @@ export function AgentMonitor() {
|
||||
const resData = (res?.data ?? {}) as Record<string, unknown>;
|
||||
setCmdResult({
|
||||
status: res?.code === 200 ? 'completed' : 'failed',
|
||||
result: (resData.output as string) || (res?.message as string) || i18n.t('无输出'),
|
||||
result: (resData.output as string) || (res?.message as string) || i18n.t('agent.monitor.noOutput'),
|
||||
error: resData.error as string | undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -154,7 +154,7 @@ export function AgentMonitor() {
|
||||
className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 mr-1.5 ${refreshing ? 'animate-spin' : ''}`} />
|
||||
{t('例如:free -m && df -h')}
|
||||
{t('agent.monitor.commandPlaceholder')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -162,7 +162,7 @@ export function AgentMonitor() {
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||||
>
|
||||
<Rocket className="h-4 w-4 mr-1.5" />
|
||||
{t('例如:free -m && df -h')}
|
||||
{t('agent.monitor.commandPlaceholder')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -325,7 +325,7 @@ export function AgentMonitor() {
|
||||
<Textarea
|
||||
value={cmdCommand}
|
||||
onChange={(e) => setCmdCommand(e.target.value)}
|
||||
placeholder={i18n.t('例如:free -m && df -h')}
|
||||
placeholder={i18n.t('agent.monitor.commandPlaceholder')}
|
||||
rows={3}
|
||||
className="bg-[var(--dg-bg-primary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] font-mono text-sm resize-none"
|
||||
/>
|
||||
@@ -359,7 +359,7 @@ export function AgentMonitor() {
|
||||
</Badge>
|
||||
</div>
|
||||
<pre className="text-xs text-[var(--dg-text-secondary)] font-mono whitespace-pre-wrap break-all bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-lg p-3 max-h-48 overflow-auto">
|
||||
{cmdResult.result || cmdResult.output || cmdResult.error || i18n.t('无输出')}
|
||||
{cmdResult.result || cmdResult.output || cmdResult.error || i18n.t('agent.monitor.noOutput')}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -70,7 +70,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!ruleForm.name.trim()) {
|
||||
toast.error(i18n.t('请输入规则名称'));
|
||||
toast.error(i18n.t('agent.ruleDialog.enterName'));
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
@@ -107,7 +107,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
<Dialog open={open} onOpenChange={(v) => { if (!v) onOpenChange(false); }}>
|
||||
<DialogContent className="max-w-md dg-card text-[var(--dg-text-primary)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)]">{editingRule ? i18n.t('monitor.editRule') : i18n.t('编辑规则')}</DialogTitle>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)]">{editingRule ? i18n.t('monitor.editRule') : i18n.t('agent.ruleDialog.editRule')}</DialogTitle>
|
||||
<DialogDescription className="dg-text-secondary"> {i18n.t('>阈值 {ruleForm.metric !== ')} </DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -117,7 +117,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
<Input
|
||||
value={ruleForm.name}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })}
|
||||
placeholder={i18n.t('例如:CPU 使用率告警')}
|
||||
placeholder={i18n.t('agent.ruleDialog.namePlaceholder')}
|
||||
className="h-9 dg-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -185,7 +185,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
type="number"
|
||||
value={ruleForm.port}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, port: e.target.value })}
|
||||
placeholder={i18n.t('例如:8080')}
|
||||
placeholder={i18n.t('agent.ruleDialog.portPlaceholder')}
|
||||
className="h-9 dg-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -197,7 +197,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
|
||||
type="number"
|
||||
value={ruleForm.duration}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, duration: e.target.value })}
|
||||
placeholder={i18n.t('例如:CPU 使用率告警')}
|
||||
placeholder={i18n.t('agent.ruleDialog.namePlaceholder')}
|
||||
className="h-9 dg-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -10,23 +10,23 @@ import i18n from '@/i18n';
|
||||
// 主机监控指标(与后端 alert_engine 对齐)// Logan.Li
|
||||
export const METRIC_OPTIONS = [
|
||||
{ value: 'cpu', label: i18n.t('kubernetes.cluster.cpuUsage'), icon: Cpu },
|
||||
{ value: 'cpu_iowait', label: i18n.t('入网带宽'), icon: Cpu },
|
||||
{ value: 'cpu_iowait', label: i18n.t('agent.alertHelper.netInBps'), icon: Cpu },
|
||||
{ value: 'mem', label: i18n.t('kubernetes.cluster.memoryUsage'), icon: MemoryStick },
|
||||
{ value: 'mem_available', label: i18n.t('可用内存(GB)'), icon: MemoryStick },
|
||||
{ value: 'mem_available', label: i18n.t('agent.alertHelper.memAvailable'), icon: MemoryStick },
|
||||
{ value: 'swap', label: i18n.t('Swap 使用率'), icon: MemoryStick },
|
||||
{ value: 'load1', label: i18n.t('1分钟负载'), icon: Gauge },
|
||||
{ value: 'disk', label: i18n.t('monitor.hostDisk'), icon: HardDrive },
|
||||
{ value: 'disk_inode', label: i18n.t('Inode 使用率'), icon: HardDrive },
|
||||
{ value: 'disk_io_util', label: i18n.t('磁盘 IO 利用率'), icon: HardDrive },
|
||||
{ value: 'disk_io_util', label: i18n.t('agent.alertHelper.diskIOUtil'), icon: HardDrive },
|
||||
{ value: 'tcp_established', label: i18n.t('TCP 已建立'), icon: Network },
|
||||
{ value: 'tcp_time_wait', label: 'TCP TIME_WAIT', icon: Network },
|
||||
{ value: 'tcp_close_wait', label: 'TCP CLOSE_WAIT', icon: Network },
|
||||
{ value: 'net_rx_bps', label: i18n.t('monitor.hostNetIn'), icon: Network },
|
||||
{ value: 'net_tx_bps', label: i18n.t('monitor.hostNetOut'), icon: Network },
|
||||
{ value: 'net_rx_errors', label: i18n.t('入网错误'), icon: Network },
|
||||
{ value: 'net_rx_errors', label: i18n.t('agent.alertHelper.netInErrors'), icon: Network },
|
||||
{ value: 'fd', label: i18n.t('monitor.hostFileDescriptor'), icon: Activity },
|
||||
{ value: 'offline', label: i18n.t('主机离线'), icon: WifiOff },
|
||||
{ value: 'port_down', label: i18n.t('端口不通'), icon: Network },
|
||||
{ value: 'offline', label: i18n.t('agent.alertHelper.hostOffline'), icon: WifiOff },
|
||||
{ value: 'port_down', label: i18n.t('agent.alertHelper.portDown'), icon: Network },
|
||||
{ value: 'health_probe', label: i18n.t('kubernetes.container.probes'), icon: Timer },
|
||||
];
|
||||
|
||||
|
||||
@@ -121,8 +121,8 @@ export interface ComputeCostPredictionsInput {
|
||||
/** 指标定义:后端 metric name → 展示标签 / 告警表达式前缀 / 阈值 */
|
||||
export const METRIC_DEFS = [
|
||||
{ key: 'cpu', label: i18n.t('CPU使用率'), exprKey: 'cpu_usage', threshold: 85 },
|
||||
{ key: 'memory', label: i18n.t('内存使用率'), exprKey: 'memory_usage', threshold: 85 },
|
||||
{ key: 'disk', label: i18n.t('磁盘使用率'), exprKey: 'disk_usage', threshold: 90 },
|
||||
{ key: 'memory', label: i18n.t('aiops.predictTypes.memUsage'), exprKey: 'memory_usage', threshold: 85 },
|
||||
{ key: 'disk', label: i18n.t('aiops.predictTypes.diskUsage'), exprKey: 'disk_usage', threshold: 90 },
|
||||
] as const;
|
||||
|
||||
export const TIME_RANGE_LABELS: Record<string, string> = {
|
||||
|
||||
@@ -66,7 +66,7 @@ export function BillingRecordDialog({ open, onOpenChange, editingRecord, onSucce
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!form.provider || !form.billing_date || !form.cost) {
|
||||
toast.error(i18n.t('请填写必填字段'));
|
||||
toast.error(i18n.t('cloud.billingRecordDialog.fillFields'));
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
@@ -92,7 +92,7 @@ export function BillingRecordDialog({ open, onOpenChange, editingRecord, onSucce
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingRecord ? i18n.t('编辑成本记录') : i18n.t('cloud.recordCost')}</DialogTitle>
|
||||
<DialogTitle>{editingRecord ? i18n.t('cloud.billingRecordDialog.editRecord') : i18n.t('cloud.recordCost')}</DialogTitle>
|
||||
<DialogDescription> {i18n.t('cloud.billingrecorddi.s09e541fd')} </DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
@@ -130,7 +130,7 @@ export function BillingRecordDialog({ open, onOpenChange, editingRecord, onSucce
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('cloud.accountName')} </Label>
|
||||
<Input className="mt-1" placeholder={i18n.t('主账号')} value={form.account_name} onChange={(e) => setForm({ ...form, account_name: e.target.value })} />
|
||||
<Input className="mt-1" placeholder={i18n.t('cloud.billingRecordDialog.mainAccount')} value={form.account_name} onChange={(e) => setForm({ ...form, account_name: e.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -58,7 +58,7 @@ export function BillingRuleDialog({ open, onOpenChange, editingRule, onSuccess }
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!form.name || !form.threshold) {
|
||||
toast.error(i18n.t('请填写规则名称和阈值'));
|
||||
toast.error(i18n.t('cloud.billingRuleDialog.fillFields'));
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
@@ -83,13 +83,13 @@ export function BillingRuleDialog({ open, onOpenChange, editingRule, onSuccess }
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingRule ? i18n.t('编辑预警规则') : i18n.t('添加预警规则')}</DialogTitle>
|
||||
<DialogTitle>{editingRule ? i18n.t('cloud.billingRuleDialog.editRule') : i18n.t('cloud.billingRuleDialog.addRule')}</DialogTitle>
|
||||
<DialogDescription> {i18n.t('cloud.billingruledial.s75227f20')} </DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('monitor.silenceDialog.ruleNameRequired')} </Label>
|
||||
<Input className="mt-1" placeholder={i18n.t('如:日成本超 500 元预警')} value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
|
||||
<Input className="mt-1" placeholder={i18n.t('cloud.billingRuleDialog.namePlaceholder')} value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
|
||||
@@ -39,7 +39,7 @@ import type { JsonData } from '@/types/common';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
const typeLabels: Record<string, string> = {
|
||||
public: i18n.t('公有云'),
|
||||
public: i18n.t('cloud.platform.publicCloud'),
|
||||
private: i18n.t('cloud.privateCloud'),
|
||||
hybrid: i18n.t('cloud.hybridCloud'),
|
||||
};
|
||||
@@ -138,7 +138,7 @@ export function PlatformManager() {
|
||||
<div className="relative w-72">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 dg-text-muted" />
|
||||
<Input
|
||||
placeholder={i18n.t('搜索平台名称或编码...')}
|
||||
placeholder={i18n.t('cloud.platform.searchPlaceholder')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9 h-9 dg-input"
|
||||
@@ -150,7 +150,7 @@ export function PlatformManager() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all"> {i18n.t('k8s.topology.allTypes')} </SelectItem>
|
||||
<SelectItem value="public"> {i18n.t('公有云')} </SelectItem>
|
||||
<SelectItem value="public"> {i18n.t('cloud.platform.publicCloud')} </SelectItem>
|
||||
<SelectItem value="private"> {i18n.t('cloud.privateCloud')} </SelectItem>
|
||||
<SelectItem value="hybrid"> {i18n.t('cloud.hybridCloud')} </SelectItem>
|
||||
</SelectContent>
|
||||
@@ -162,7 +162,7 @@ export function PlatformManager() {
|
||||
<PageShell
|
||||
variant="list"
|
||||
title={i18n.t('cloud.title')}
|
||||
subtitle={i18n.t('管理公有云、私有云与混合云平台信息')}
|
||||
subtitle={i18n.t('cloud.platform.subtitle')}
|
||||
icon={<CloudCog className="h-5 w-5" />}
|
||||
metrics={
|
||||
!loading ? (
|
||||
@@ -170,7 +170,7 @@ export function PlatformManager() {
|
||||
items={[
|
||||
{ label: i18n.t('system.auditTotal'), value: stats.total, color: 'blue' },
|
||||
{ label: i18n.t('system.userActive'), value: stats.active, color: 'green' },
|
||||
{ label: i18n.t('公有云'), value: stats.public, color: 'cyan' },
|
||||
{ label: i18n.t('cloud.platform.publicCloud'), value: stats.public, color: 'cyan' },
|
||||
]}
|
||||
/>
|
||||
) : undefined
|
||||
@@ -180,11 +180,11 @@ export function PlatformManager() {
|
||||
<>
|
||||
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadData}>
|
||||
<RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />
|
||||
刷新
|
||||
{i18n.t('common.refresh')}
|
||||
</Button>
|
||||
<Button size="sm" className="dg-btn-primary" onClick={openCreate}>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
添加平台
|
||||
{i18n.t('cloud.platform.addPlatform')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -194,11 +194,11 @@ export function PlatformManager() {
|
||||
) : filtered.length === 0 ? (
|
||||
<PageEmpty
|
||||
icon={<CloudCog className="h-12 w-12" />}
|
||||
title={i18n.t('暂无云平台数据')}
|
||||
title={i18n.t('cloud.platform.noData')}
|
||||
action={(
|
||||
<Button size="sm" className="dg-btn-primary" onClick={openCreate}>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
添加平台
|
||||
{i18n.t('cloud.platform.addPlatform')}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
@@ -244,11 +244,11 @@ export function PlatformManager() {
|
||||
</Badge>
|
||||
{p.status === 'active' ? (
|
||||
<Badge className="dg-status-running gap-1 border-0">
|
||||
<CheckCircle2 className="h-3 w-3" /> 启用
|
||||
<CheckCircle2 className="h-3 w-3" /> {i18n.t('cloud.platform.enabled')}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className="dg-status-stopped gap-1 border-0">
|
||||
<XCircle className="h-3 w-3" /> 禁用
|
||||
<XCircle className="h-3 w-3" /> {i18n.t('cloud.platform.disabled')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -261,26 +261,26 @@ export function PlatformManager() {
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingItem ? i18n.t('编辑云平台') : i18n.t('添加云平台')}</DialogTitle>
|
||||
<DialogTitle>{editingItem ? i18n.t('cloud.platform.editPlatform') : i18n.t('cloud.platform.addPlatform')}</DialogTitle>
|
||||
<DialogDescription className="sr-only"> {i18n.t('aiops.llmModelDesc')} </DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-2">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label> {i18n.t('如:阿里云')} </Label>
|
||||
<Label> {i18n.t('common.name')} </Label>
|
||||
<Input
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder={i18n.t('如:阿里云')}
|
||||
placeholder={i18n.t('cloud.platform.namePlaceholder')}
|
||||
className="dg-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label> {i18n.t('如:阿里云')} </Label>
|
||||
<Label> {i18n.t('common.code')} </Label>
|
||||
<Input
|
||||
value={formData.code}
|
||||
onChange={(e) => setFormData({ ...formData, code: e.target.value })}
|
||||
placeholder={i18n.t('如:阿里云')}
|
||||
placeholder={i18n.t('cloud.platform.namePlaceholder')}
|
||||
className="dg-input"
|
||||
disabled={!!editingItem}
|
||||
/>
|
||||
@@ -294,7 +294,7 @@ export function PlatformManager() {
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="public"> {i18n.t('公有云')} </SelectItem>
|
||||
<SelectItem value="public"> {i18n.t('cloud.platform.publicCloud')} </SelectItem>
|
||||
<SelectItem value="private"> {i18n.t('cloud.privateCloud')} </SelectItem>
|
||||
<SelectItem value="hybrid"> {i18n.t('cloud.hybridCloud')} </SelectItem>
|
||||
</SelectContent>
|
||||
@@ -314,7 +314,7 @@ export function PlatformManager() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label> {i18n.t('如:阿里云')} </Label>
|
||||
<Label> {i18n.t('cloud.platform.namePlaceholder')} </Label>
|
||||
<Input
|
||||
value={formData.website}
|
||||
onChange={(e) => setFormData({ ...formData, website: e.target.value })}
|
||||
@@ -327,7 +327,7 @@ export function PlatformManager() {
|
||||
<Input
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
placeholder={i18n.t('平台描述...')}
|
||||
placeholder={i18n.t('cloud.platform.descPlaceholder')}
|
||||
className="dg-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
const srvList = (srvRes as { list?: ServerItem[] })?.list || [];
|
||||
setServers(srvList);
|
||||
} catch {
|
||||
toast.error(i18n.t('加载构建节点失败'));
|
||||
toast.error(i18n.t('deploy.buildNodes.loadFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!name.trim() || !serverId) {
|
||||
toast.error(i18n.t('加载构建节点失败'));
|
||||
toast.error(i18n.t('deploy.buildNodes.loadFailed'));
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
@@ -121,14 +121,14 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
};
|
||||
|
||||
const handleDelete = async (node: BuildNode) => {
|
||||
const ok = await confirm({ title: i18n.t('kubernetes.topology.deleteNode'), description: `确定删除构建节点「${node.name}」?` });
|
||||
const ok = await confirm({ title: i18n.t('kubernetes.topology.deleteNode'), description: i18n.t('deploy.buildNodes.deleteConfirm', { name: node.name }) });
|
||||
if (!ok) return;
|
||||
try {
|
||||
await buildNodeApi.delete(node.id);
|
||||
toast.success(i18n.t('monitor.silenceRules.deleted'));
|
||||
load();
|
||||
} catch {
|
||||
toast.error(i18n.t('加载构建节点失败'));
|
||||
toast.error(i18n.t('deploy.buildNodes.loadFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,7 +137,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
try {
|
||||
const res = await buildNodeApi.test(node.id) as { success?: boolean; message?: string };
|
||||
if (res?.success) {
|
||||
toast.success(`连通成功: ${res.message || ''}`);
|
||||
toast.success(i18n.t('deploy.buildNodes.testSuccess') + (res.message ? `: ${res.message}` : ''));
|
||||
} else {
|
||||
toast.error(res?.message || i18n.t('aiops.llmTestFailed'));
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
const handleSaveMaster = async () => {
|
||||
const n = Number(masterMax);
|
||||
if (!n || n < 1) {
|
||||
toast.error(i18n.t('加载构建节点失败'));
|
||||
toast.error(i18n.t('deploy.buildNodes.loadFailed'));
|
||||
return;
|
||||
}
|
||||
setSavingMaster(true);
|
||||
@@ -172,7 +172,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
max_executors: m?.max_executors ?? n,
|
||||
running_count: m?.running_count ?? prev.running_count,
|
||||
}));
|
||||
toast.success(i18n.t('已删除'));
|
||||
toast.success(i18n.t('deploy.buildNodes.deleted'));
|
||||
} catch {
|
||||
toast.error(i18n.t('kubernetes.deployment.imageUpdateFailed'));
|
||||
} finally {
|
||||
@@ -201,10 +201,10 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Cpu className="h-4 w-4 text-[var(--dg-text-muted)]" />
|
||||
<span className="text-sm font-medium text-[var(--dg-text-primary)]">Master(内置)</span>
|
||||
<span className="text-sm font-medium text-[var(--dg-text-primary)]">{i18n.t('deploy.buildNodes.masterBuiltin')}</span>
|
||||
<Badge variant="outline" className="text-[10px]"> {i18n.t('pipeline.buildnodesmanag.sa419fe03')} </Badge>
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">
|
||||
占用 {master.running_count}/{master.max_executors}
|
||||
{i18n.t('deploy.buildNodes.executorUsage', { running: master.running_count, max: master.max_executors })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -218,19 +218,19 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
className="h-7 w-16 text-xs dg-input"
|
||||
/>
|
||||
<Button size="sm" className="h-7 text-xs" onClick={handleSaveMaster} disabled={savingMaster}>
|
||||
{savingMaster ? i18n.t('保存中') : i18n.t('task.script.save')}
|
||||
{savingMaster ? i18n.t('deploy.buildNodes.saving') : i18n.t('task.script.save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[11px] text-[var(--dg-text-muted)] mt-2">
|
||||
未指定节点时优先在 Master 执行;并发设为 1 时按触发顺序串行排队;满载后自动调度到空闲远程节点
|
||||
{i18n.t('deploy.buildNodes.schedulingHint')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{nodes.length === 0 ? (
|
||||
<PageEmpty
|
||||
title={i18n.t('暂无远程构建节点')}
|
||||
description={i18n.t('关联服务器资产添加 SSH 节点,将重型构建任务调度到高性能机器')}
|
||||
title={i18n.t('deploy.buildNodes.noRemoteNodes')}
|
||||
description={i18n.t('deploy.buildNodes.noRemoteDesc')}
|
||||
action={<Button size="sm" onClick={openCreate}><Plus className="h-3.5 w-3.5 mr-1" /> {i18n.t('kubernetes.topology.addNode')} </Button>}
|
||||
/>
|
||||
) : (
|
||||
@@ -251,14 +251,14 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-1 text-[11px] text-[var(--dg-text-muted)]">
|
||||
<span>执行器 {node.running_count || 0}/{node.max_executors}</span>
|
||||
{node.labels && <span>标签 {node.labels}</span>}
|
||||
<span>{i18n.t('deploy.buildNodes.executorLabel', { running: node.running_count || 0, max: node.max_executors })}</span>
|
||||
{node.labels && <span>{i18n.t('deploy.buildNodes.labelsLabel', { labels: node.labels })}</span>}
|
||||
<span className="truncate">{node.workspace_path}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs" disabled={testingId === node.id} onClick={() => handleTest(node)}>
|
||||
{testingId === node.id ? i18n.t('测试中') : i18n.t('pipeline.testTool')}
|
||||
{testingId === node.id ? i18n.t('deploy.buildNodes.testing') : i18n.t('pipeline.testTool')}
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => handleToggle(node)}>
|
||||
{node.enabled ? i18n.t('system.userInactive') : i18n.t('system.userActive')}
|
||||
@@ -295,7 +295,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs"> {i18n.t('>标签 <span className=')} </Label>
|
||||
<Select value={serverId || '__none'} onValueChange={(v) => setServerId(v === '__none' ? '' : v)}>
|
||||
<SelectTrigger className="h-8 text-xs dg-input"><SelectValue placeholder={i18n.t('选择服务器')} /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs dg-input"><SelectValue placeholder={i18n.t('deploy.buildNodes.selectServer')} /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none"> {i18n.t('k8s.storage.pleaseSelect')} </SelectItem>
|
||||
{servers.map((s) => (
|
||||
@@ -326,7 +326,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="outline" onClick={() => setDialogOpen(false)}> {i18n.t('rbac.jobRole.cancel')} </Button>
|
||||
<Button onClick={handleSubmit} disabled={submitting}>{submitting ? i18n.t('保存中') : i18n.t('task.script.save')}</Button>
|
||||
<Button onClick={handleSubmit} disabled={submitting}>{submitting ? i18n.t('deploy.buildNodes.saving') : i18n.t('task.script.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
@@ -343,7 +343,7 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
|
||||
}
|
||||
|
||||
return (
|
||||
<PageShell title={i18n.t('pipeline.buildNodes')} subtitle={i18n.t('构建节点')}>
|
||||
<PageShell title={i18n.t('pipeline.buildNodes')} subtitle={i18n.t('deploy.pipeline.buildNodes')}>
|
||||
{panel}
|
||||
{dialog}
|
||||
</PageShell>
|
||||
|
||||
@@ -112,7 +112,7 @@ export default function DeployRBAC() {
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!newName.trim() || !newCode.trim() || !newPattern.trim()) {
|
||||
toast.error(i18n.t('请填写名称、标识与正则')); return;
|
||||
toast.error(i18n.t('deploy.rbac.policy.fillFields')); return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
@@ -147,7 +147,7 @@ export default function DeployRBAC() {
|
||||
|
||||
const handleDelete = async (role: ItemRole) => {
|
||||
const ok = await _confirm({
|
||||
title: i18n.t('删除权限'),
|
||||
title: i18n.t('deploy.rbac.policy.deletePolicy'),
|
||||
description: `确定删除「${role.name}」?将影响 ${matchCount[role.id]?.length || 0} 个发布任务`,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -165,7 +165,7 @@ export default function DeployRBAC() {
|
||||
};
|
||||
|
||||
const handleAssign = async () => {
|
||||
if (!selectedUserId || !assignRoleId) { toast.error(i18n.t('请选择用户和权限')); return; }
|
||||
if (!selectedUserId || !assignRoleId) { toast.error(i18n.t('deploy.rbac.policy.selectUserPermission')); return; }
|
||||
setAssignLoading(true);
|
||||
try {
|
||||
await deployApi.assignItemRole(Number(selectedUserId), {
|
||||
@@ -190,8 +190,8 @@ export default function DeployRBAC() {
|
||||
|
||||
const selectedUser = users.find((u) => String(u.id) === selectedUserId);
|
||||
const pageTabs = [
|
||||
{ id: 'roles', label: i18n.t('权限策略'), badge: roles.length || undefined },
|
||||
{ id: 'users', label: i18n.t('用户授权') },
|
||||
{ id: 'roles', label: i18n.t('deploy.rbac.policy.policyStrategy'), badge: roles.length || undefined },
|
||||
{ id: 'users', label: i18n.t('deploy.rbac.policy.userAuth') },
|
||||
{ id: 'groups', label: i18n.t('pipeline.groupLabel') },
|
||||
{ id: 'preview', label: i18n.t('pipeline.matchPreview') },
|
||||
];
|
||||
@@ -200,7 +200,7 @@ export default function DeployRBAC() {
|
||||
<PageShell
|
||||
variant="list"
|
||||
title={i18n.t('deploy.deployNav.jobPermission')}
|
||||
subtitle={i18n.t('按流水线 code / Job 名称正则分配发布权限')}
|
||||
subtitle={i18n.t('deploy.rbac.policy.subtitle')}
|
||||
icon={<Shield className="h-5 w-5" />}
|
||||
loading={loading}
|
||||
tabs={pageTabs}
|
||||
@@ -217,8 +217,8 @@ export default function DeployRBAC() {
|
||||
roles.length === 0 ? (
|
||||
<PageEmpty
|
||||
icon={<Shield className="h-10 w-10" />}
|
||||
title={i18n.t('暂无 Job 权限策略')}
|
||||
description={i18n.t('创建策略后,用户将按正则自动获得匹配流水线的访问权限')}
|
||||
title={i18n.t('deploy.rbac.policy.noPolicy')}
|
||||
description={i18n.t('deploy.rbac.policy.createHint')}
|
||||
action={<Button onClick={() => setCreateOpen(true)}><Plus className="h-4 w-4 mr-1" /> {i18n.t('monitor.logrbactsx.s6058845c')} </Button>}
|
||||
/>
|
||||
) : (
|
||||
@@ -296,7 +296,7 @@ export default function DeployRBAC() {
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1 w-48">
|
||||
<Label className="text-xs"> {i18n.t('权限策略')} </Label>
|
||||
<Label className="text-xs"> {i18n.t('deploy.rbac.policy.policyStrategy')} </Label>
|
||||
<Select value={assignRoleId} onValueChange={setAssignRoleId}>
|
||||
<SelectTrigger className="h-8 text-sm dg-input"><SelectValue placeholder={i18n.t('kubernetes.hpa.selectPolicy')} /></SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -99,8 +99,8 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
|
||||
const listBody = !loading && groups.length === 0 ? (
|
||||
<PageEmpty
|
||||
icon={<Users className="h-10 w-10" />}
|
||||
title={i18n.t('暂无用户组')}
|
||||
description={i18n.t('点击「新建用户组」创建第一个用户组')}
|
||||
title={i18n.t('deploy.userGroup.noGroups')}
|
||||
description={i18n.t('deploy.userGroup.createFirstHint')}
|
||||
action={(
|
||||
<Button size="sm" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}>
|
||||
<Plus className="w-4 h-4 mr-1" />新建用户组
|
||||
@@ -165,7 +165,7 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
|
||||
<DialogContent>
|
||||
<DialogHeader><DialogTitle>{editId ? i18n.t('deploy.rbac.groupEditTitle') : i18n.t('pipeline.newGroup')}</DialogTitle></DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1"><Label> {i18n.t('monitor.groupName')} </Label><Input value={formName} onChange={e => setFormName(e.target.value)} placeholder={i18n.t('例如: 后端开发组')} /></div>
|
||||
<div className="space-y-1"><Label> {i18n.t('monitor.groupName')} </Label><Input value={formName} onChange={e => setFormName(e.target.value)} placeholder={i18n.t('deploy.userGroup.namePlaceholder')} /></div>
|
||||
<div className="space-y-1"><Label> {i18n.t('aiops.llmModelDesc')} </Label><Input value={formDesc} onChange={e => setFormDesc(e.target.value)} placeholder={i18n.t('rbac.jobRole.placeholder.description')} /></div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 mt-4 pt-4 border-t">
|
||||
@@ -190,14 +190,14 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
|
||||
<PageShell
|
||||
variant="list"
|
||||
title={i18n.t('pipeline.releaseTitle')}
|
||||
subtitle={i18n.t('用户组 — 批量授权流水线访问权限')}
|
||||
subtitle={i18n.t('deploy.userGroup.subtitle')}
|
||||
icon={<Users className="h-5 w-5" />}
|
||||
loading={loading}
|
||||
metrics={(
|
||||
<StatStrip
|
||||
items={[
|
||||
{ label: i18n.t('pipeline.groupLabel'), value: groups.length, color: 'blue' },
|
||||
{ label: i18n.t('成员总数'), value: memberCount, color: 'purple' },
|
||||
{ label: i18n.t('deploy.userGroup.memberCount'), value: memberCount, color: 'purple' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -149,8 +149,8 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-1 p-1 rounded-lg bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)]">
|
||||
{([
|
||||
{ key: 'ssh' as const, label: i18n.t('主机发布'), icon: Server },
|
||||
{ key: 'k8s' as const, label: i18n.t('主机发布'), icon: Network },
|
||||
{ key: 'ssh' as const, label: i18n.t('deploy.target.hostDeploy'), icon: Server },
|
||||
{ key: 'k8s' as const, label: i18n.t('deploy.target.hostDeploy'), icon: Network },
|
||||
]).map((item) => {
|
||||
const active = value.deployTarget === item.key;
|
||||
const Icon = item.icon;
|
||||
@@ -200,7 +200,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
|
||||
<Input
|
||||
value={serverSearch}
|
||||
onChange={(e) => setServerSearch(e.target.value)}
|
||||
placeholder={i18n.t('搜索主机名或 IP')}
|
||||
placeholder={i18n.t('deploy.target.searchHost')}
|
||||
className="pl-8 h-8 text-xs dg-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -296,7 +296,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
|
||||
<Input
|
||||
readOnly
|
||||
value={value.k8sDeployment ? `${value.k8sNamespace}/${value.k8sDeployment}` : ''}
|
||||
placeholder={i18n.t('从下方列表选择')}
|
||||
placeholder={i18n.t('deploy.target.selectFromList')}
|
||||
className="h-8 text-xs dg-input font-mono"
|
||||
/>
|
||||
</div>
|
||||
@@ -308,7 +308,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
|
||||
<Input
|
||||
value={depSearch}
|
||||
onChange={(e) => setDepSearch(e.target.value)}
|
||||
placeholder={i18n.t('搜索 Deployment 或命名空间')}
|
||||
placeholder={i18n.t('deploy.target.searchDeployment')}
|
||||
className="pl-8 h-8 text-xs dg-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -353,7 +353,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
|
||||
<Input
|
||||
value={value.k8sContainerName}
|
||||
onChange={(e) => onChange({ k8sContainerName: e.target.value })}
|
||||
placeholder={i18n.t('搜索 Deployment 或命名空间')}
|
||||
placeholder={i18n.t('deploy.target.searchDeployment')}
|
||||
className="h-8 text-xs font-mono dg-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
|
||||
// 流水线阶段定义(固定顺序) // Logan.Li
|
||||
export const STAGE_DEFS = [
|
||||
{ key: 'checkout', label: i18n.t('pipeline.checkout'), icon: GitBranch, color: '#6366F1', order: 1, needs: ['repo'], desc: i18n.t('代码检出') },
|
||||
{ key: 'build', label: i18n.t('pipeline.build'), icon: Box, color: '#F59E0B', order: 2, needs: [], desc: i18n.t('构建') },
|
||||
{ key: 'test', label: i18n.t('pipeline.testTool'), icon: CheckCircle, color: '#10B981', order: 3, needs: [], desc: i18n.t('测试') },
|
||||
{ key: 'image', label: i18n.t('pipeline.imageBuild'), icon: Container, color: '#06B6D4', order: 4, needs: ['registry'], desc: i18n.t('镜像打包') },
|
||||
{ key: 'deploy', label: i18n.t('pipeline.deploy2'), icon: Rocket, color: '#EF4444', order: 5, needs: [], desc: i18n.t('部署') },
|
||||
{ key: 'verify', label: i18n.t('system.credentialVerify'), icon: ShieldCheck, color: '#3B82F6', order: 6, needs: [], desc: i18n.t('验证') },
|
||||
{ key: 'checkout', label: i18n.t('pipeline.checkout'), icon: GitBranch, color: '#6366F1', order: 1, needs: ['repo'], desc: i18n.t('deploy.stage.checkout') },
|
||||
{ key: 'build', label: i18n.t('pipeline.build'), icon: Box, color: '#F59E0B', order: 2, needs: [], desc: i18n.t('deploy.stage.build') },
|
||||
{ key: 'test', label: i18n.t('pipeline.testTool'), icon: CheckCircle, color: '#10B981', order: 3, needs: [], desc: i18n.t('deploy.stage.test') },
|
||||
{ key: 'image', label: i18n.t('pipeline.imageBuild'), icon: Container, color: '#06B6D4', order: 4, needs: ['registry'], desc: i18n.t('deploy.stage.imageBuild') },
|
||||
{ key: 'deploy', label: i18n.t('pipeline.deploy2'), icon: Rocket, color: '#EF4444', order: 5, needs: [], desc: i18n.t('deploy.stage.deploy') },
|
||||
{ key: 'verify', label: i18n.t('system.credentialVerify'), icon: ShieldCheck, color: '#3B82F6', order: 6, needs: [], desc: i18n.t('deploy.stage.verify') },
|
||||
].sort((a, b) => a.order - b.order);
|
||||
|
||||
// 运行日志条目 // Logan.Li
|
||||
|
||||
@@ -207,7 +207,7 @@ export function AlertChannelDialog({
|
||||
{/* 渠道表单 */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-[var(--dg-text-secondary)] text-sm">
|
||||
{editingChannel ? i18n.t('编辑渠道') : i18n.t('新建渠道')}
|
||||
{editingChannel ? i18n.t('monitor.alertChannelDialog2.editChannel') : i18n.t('monitor.alertChannelDialog2.newChannel')}
|
||||
</Label>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="dg-text-secondary text-xs"> {i18n.t('>渠道名称 <span className=')} <span className="dg-text-red">*</span></Label>
|
||||
|
||||
@@ -76,15 +76,15 @@ export function AlertManager() {
|
||||
];
|
||||
|
||||
const pageTabs = [
|
||||
{ id: 'events', label: i18n.t('告警事件') },
|
||||
{ id: 'events', label: i18n.t('monitor.alertMgr2.alertEvents') },
|
||||
{ id: 'rules', label: i18n.t('monitor.alertRules') },
|
||||
];
|
||||
|
||||
return (
|
||||
<PageShell
|
||||
variant="list"
|
||||
title={i18n.t('监控告警')}
|
||||
subtitle={i18n.t('告警规则管理与告警事件处理')}
|
||||
title={i18n.t('monitor.alertMgr2.monitorAlert')}
|
||||
subtitle={i18n.t('monitor.alertMgr2.subtitle')}
|
||||
icon={<Bell className="h-5 w-5" />}
|
||||
tabs={pageTabs}
|
||||
activeTab={activeTab}
|
||||
@@ -94,7 +94,7 @@ export function AlertManager() {
|
||||
items={[
|
||||
{ label: i18n.t('monitor.alertRules'), value: rules.length, color: 'blue' },
|
||||
{ label: i18n.t('monitor.alertHelper.statusFiring'), value: alerts.filter((e) => e.status === 'firing').length, color: 'red' },
|
||||
{ label: i18n.t('今日告警'), value: alerts.length, color: 'amber' },
|
||||
{ label: i18n.t('monitor.alertMgr2.todayAlerts'), value: alerts.length, color: 'amber' },
|
||||
{ label: i18n.t('kubernetes.msg.uncordonSuccess'), value: alerts.filter((e) => e.status === 'resolved').length, color: 'emerald' },
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -128,10 +128,10 @@ export function AlertRuleDialog({
|
||||
<DialogContent className="dg-input max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)]">
|
||||
{editingRule ? i18n.t('编辑告警规则') : i18n.t('新建告警规则')}
|
||||
{editingRule ? i18n.t('monitor.alertRuleDialog2.editRule') : i18n.t('monitor.alertRuleDialog2.newRule')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="dg-text-muted">
|
||||
{editingRule ? i18n.t('编辑告警规则') : i18n.t('新建告警规则')}
|
||||
{editingRule ? i18n.t('monitor.alertRuleDialog2.editRule') : i18n.t('monitor.alertRuleDialog2.newRule')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -141,7 +141,7 @@ export function AlertRuleDialog({
|
||||
<Input
|
||||
value={ruleForm.name}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })}
|
||||
placeholder={i18n.t('如:CPU 使用率告警')}
|
||||
placeholder={i18n.t('monitor.alertRuleDialog2.namePlaceholder')}
|
||||
className="dg-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -151,7 +151,7 @@ export function AlertRuleDialog({
|
||||
<Textarea
|
||||
value={ruleForm.description}
|
||||
onChange={(e) => setRuleForm({ ...ruleForm, description: e.target.value })}
|
||||
placeholder={i18n.t('如:CPU 使用率告警')}
|
||||
placeholder={i18n.t('monitor.alertRuleDialog2.namePlaceholder')}
|
||||
rows={2}
|
||||
className="dg-input resize-none"
|
||||
/>
|
||||
@@ -303,7 +303,7 @@ export function AlertRuleDialog({
|
||||
onClick={handleSaveRule}
|
||||
>
|
||||
{ruleSaving && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
||||
{editingRule ? i18n.t('cloud.saveChanges') : i18n.t('编辑告警规则')}
|
||||
{editingRule ? i18n.t('cloud.saveChanges') : i18n.t('monitor.alertRuleDialog2.editRule')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -54,15 +54,15 @@ export function AlertRulesTab({
|
||||
|
||||
const handleDeleteRule = async ( id: number) => {
|
||||
const ok = await confirm({
|
||||
title: i18n.t('删除告警规则'),
|
||||
description: i18n.t('确定删除此告警规则?'),
|
||||
title: i18n.t('monitor.alertRulesTab.deleteRule'),
|
||||
description: i18n.t('monitor.alertRulesTab.deleteConfirm'),
|
||||
confirmText: i18n.t('kubernetes.storage.delete2'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
await alertApi.deleteRule(id);
|
||||
toast.success(i18n.t('告警规则已删除'));
|
||||
toast.success(i18n.t('monitor.alertRulesTab.deleted'));
|
||||
onReload();
|
||||
} catch (error) {
|
||||
console.error('Failed to delete rule:', error);
|
||||
@@ -74,7 +74,7 @@ export function AlertRulesTab({
|
||||
const handleBatchDelete = async () => {
|
||||
if (selectedIds.length === 0) return;
|
||||
const ok = await confirm({
|
||||
title: i18n.t('删除告警规则'),
|
||||
title: i18n.t('monitor.alertRulesTab.deleteRule'),
|
||||
description: `确定删除选中的 ${selectedIds.length} 条告警规则吗?此操作不可撤销。`,
|
||||
confirmText: i18n.t('system.terminalBatchDelete'),
|
||||
variant: 'destructive',
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function PrometheusDatasourceManager() {
|
||||
const [editingDs, setEditingDs] = useState<PromDS | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isError) toast.error(i18n.t('获取数据源列表失败'));
|
||||
if (isError) toast.error(i18n.t('monitor.promDs2.listFailed'));
|
||||
}, [isError]);
|
||||
|
||||
const openCreate = () => {
|
||||
@@ -67,7 +67,7 @@ export default function PrometheusDatasourceManager() {
|
||||
await setDefaultMutation.mutateAsync(ds.id);
|
||||
toast.success(`已将「${ds.name}」设为默认数据源`);
|
||||
} catch {
|
||||
toast.error(i18n.t('删除失败'));
|
||||
toast.error(i18n.t('monitor.promDs2.deleteFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,8 +95,8 @@ export default function PrometheusDatasourceManager() {
|
||||
{sources.length === 0 ? (
|
||||
<PageEmpty
|
||||
icon={<Database className="h-12 w-12" />}
|
||||
title={i18n.t('暂无 Prometheus 数据源')}
|
||||
description={i18n.t('添加数据源后可用于指标监控与监控大屏')}
|
||||
title={i18n.t('monitor.promDs2.noData')}
|
||||
description={i18n.t('monitor.promDs2.addHint')}
|
||||
action={(
|
||||
<Button onClick={openCreate} size="sm" className="dg-btn-primary">
|
||||
<Plus className="w-4 h-4 mr-1" />添加数据源
|
||||
|
||||
@@ -147,10 +147,10 @@ export function LogDatasourcePage() {
|
||||
rowKey={(ds) => ds.id}
|
||||
loading={loading}
|
||||
searchable
|
||||
searchPlaceholder={i18n.t('搜索数据源名称或地址...')}
|
||||
searchPlaceholder={i18n.t('monitor.logDsPage.searchPlaceholder')}
|
||||
searchKeys={['name', 'url']}
|
||||
sortable
|
||||
emptyText={i18n.t('暂无数据源,点击上方按钮添加')}
|
||||
emptyText={i18n.t('monitor.logDsPage.noData')}
|
||||
toolbarExtra={
|
||||
<>
|
||||
<Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]" onClick={load}>
|
||||
|
||||
@@ -24,8 +24,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const STATUS_LABEL: Record<string, { text: string; color: string }> = {
|
||||
precise: { text: i18n.t('结果精确'), color: '#22c55e' },
|
||||
partial: { text: i18n.t('部分命中'), color: '#f59e0b' },
|
||||
precise: { text: i18n.t('monitor.logQueryToolbar.exactMatch'), color: '#22c55e' },
|
||||
partial: { text: i18n.t('monitor.logQueryToolbar.partialMatch'), color: '#f59e0b' },
|
||||
failed: { text: i18n.t('monitor.logPanel.queryFailed'), color: '#ef4444' },
|
||||
};
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
|
||||
setBreadcrumbs(data.breadcrumbs || []);
|
||||
setCurrentPath(data.path || path);
|
||||
} catch (err) {
|
||||
setError(err?.message || i18n.t('加载目录失败'));
|
||||
setError(err?.message || i18n.t('server.fileBrowser2.loadDirFailed'));
|
||||
setItems([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -97,7 +97,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
|
||||
try {
|
||||
await serverApi.downloadFile(serverId, filePath);
|
||||
} catch (err) {
|
||||
setError(i18n.t('下载失败: ') + ((err as JsonData)?.message || ''));
|
||||
setError(i18n.t('server.fileBrowser2.downloadFailed') + ((err as JsonData)?.message || ''));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -117,7 +117,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
|
||||
};
|
||||
|
||||
const handleDelete = async (filePath: string, isDir: boolean) => {
|
||||
if (!serverId || !confirm(`确认删除 ${isDir ? i18n.t('目录') : i18n.t('k8s.pods.action.file')} "${filePath}"?`)) return;
|
||||
if (!serverId || !confirm(`确认删除 ${isDir ? i18n.t('server.fileBrowser2.directory') : i18n.t('k8s.pods.action.file')} "${filePath}"?`)) return;
|
||||
try {
|
||||
await serverApi.deleteFile(serverId, filePath);
|
||||
await fetchDir(currentPath);
|
||||
|
||||
@@ -148,7 +148,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
|
||||
if (msg.includes('Already Exists') || msg.includes('68')) {
|
||||
toast.error(i18n.t('system.useldapmanageme.ouExists', { name: ouForm.ou_name }));
|
||||
} else {
|
||||
toast.error(i18n.t('创建失败: ') + msg);
|
||||
toast.error(i18n.t('system.ldapMgmt.createFailed') + msg);
|
||||
}
|
||||
} finally { setLdapSaving(false); }
|
||||
};
|
||||
@@ -168,7 +168,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
|
||||
/** 删除 LDAP 条目 // Logan.Li */
|
||||
const handleDeleteLDAPEntry = async (dn: string, silent = false) => {
|
||||
if (!silent) {
|
||||
const ok = await confirm({ title: i18n.t('删除 LDAP 条目'), description: `确定删除 ${dn}?`, confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive' });
|
||||
const ok = await confirm({ title: i18n.t('system.ldapMgmt.deleteEntry'), description: `确定删除 ${dn}?`, confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive' });
|
||||
if (!ok) return;
|
||||
}
|
||||
try {
|
||||
@@ -190,7 +190,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
|
||||
await ldapApi.updateUser({ dn: editLdapUser.dn, ...editLdapForm });
|
||||
setEditLdapUser(null);
|
||||
await loadLdapOUUsers(selectedOUDN);
|
||||
} catch (e) { toast.error(e?.message || i18n.t('修改失败')); }
|
||||
} catch (e) { toast.error(e?.message || i18n.t('system.ldapMgmt.updateFailed')); }
|
||||
};
|
||||
|
||||
/** LDAP 账号验证测试 // Logan.Li */
|
||||
@@ -201,7 +201,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
|
||||
const res = await ldapApi.testConnection({ test_user: ldapTestUser, test_password: ldapTestPass });
|
||||
setLdapUserTestResult(res);
|
||||
} catch (e) {
|
||||
setLdapUserTestResult({ success: false, message: e?.message || i18n.t('验证失败') });
|
||||
setLdapUserTestResult({ success: false, message: e?.message || i18n.t('system.ldapMgmt.verifyFailed') });
|
||||
} finally { setLdapUserTesting(false); }
|
||||
};
|
||||
|
||||
@@ -234,11 +234,11 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
|
||||
} else if (opts?.importAll) {
|
||||
selected = ldapUsers;
|
||||
} else {
|
||||
toast.error(i18n.t('请至少选择一个用户'));
|
||||
toast.error(i18n.t('system.ldapMgmt.selectUsers'));
|
||||
return;
|
||||
}
|
||||
if (selected.length === 0) {
|
||||
toast.error(i18n.t('请至少选择一个用户'));
|
||||
toast.error(i18n.t('system.ldapMgmt.selectUsers'));
|
||||
return;
|
||||
}
|
||||
setImporting(true);
|
||||
@@ -265,7 +265,7 @@ export function useLdapManagement(onUsersChanged: () => Promise<void> | void) {
|
||||
await onUsersChanged();
|
||||
} catch (error: unknown) {
|
||||
const errMsg = error instanceof Error ? error.message : i18n.t('monitor.dashboard.agent.toast.unknownError');
|
||||
const result = { code: 500, message: i18n.t('导入失败: ') + errMsg };
|
||||
const result = { code: 500, message: i18n.t('system.ldapMgmt.importFailed') + errMsg };
|
||||
if (opts?.setResult) {
|
||||
opts.setResult(result);
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user