Files
aiops/frontend/src/components/monitor/datasource/PrometheusDatasourceManager.tsx
T
attacker c6e0d6ca57 refactor(i18n): U42 残余硬编码中文彻底清零 — 33文件54处→0, zh/en 7847→7898对齐
全量 JSX children + toast/confirm + sr-only 描述硬编码中文清零:
- Kubernetes模块: UpdateStrategyCard maxSurge/maxUnavailable说明 + IngressCreateForm host域名/Service端口 + ServiceCreateForm 端口配置/selector + DeploymentEditService 无selector/个关联/暂无Service/Ingress + ClusterOverviewPage 核 + KubernetesClusterDialog 已配置 + KubectlTerminal/ResourceVisualEditor/CreateHPADialog/DeploymentEditContainers/Storage + PodInstancesTable/UpdateStrategySelectorCards
- AI模块: PredictiveCapacityTab AI深度解读/紧急/警告/7天预测/预计N天 + PredictiveCostTab AI深度解读 + event-handler.ts 高危操作确认toast
- Deploy模块: DeployCredentials 启用/禁用 + DeployRBAC 删除角色confirm + WorkspaceTermDialog + CredFormDialog Windows用户
- Monitor模块: AlertRulesTab 删除confirm/分钟 + PrometheusDatasourceManager/LogDatasourcePage 删除confirm + QueryHistoryPanel 条
- System模块: ServerFileBrowser/TerminalDialog/RdpAuditViewer sr-only + SystemManager/CredentialManager/TerminalSessionsTab 删除confirm + SecuritySettingsTab 30分钟/2小时
- Task模块: CronJobViewDialog 次

新增 61 翻译键 (k8s.editAdvanced 6 + k8s.editService 11 + k8s.cluster 2 + k8s.misc 9 + aiops.capacity 9 + aiops.cost 1 + aiops.cli 2 + deploy 4 + monitor 5 + system 10 + task 1 + common 2)
tsc -b=0, npm build✓ (17.71s), ESLint=0/43/any=0, go build=0, 17/17 测试通过
2026-07-14 00:51:36 +08:00

210 lines
8.0 KiB
TypeScript

// Prometheus 数据源管理 — 列表编排器 // Logan.Li
import { useState, useEffect } from 'react';
import {
Database,
Plus,
Edit,
Trash2,
Star,
Key,
Plug,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { toast } from 'sonner';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { usePromDatasources, usePromDatasourceMutations } from '@/hooks/api';
import type { PromDS } from './prometheus-ds/types';
import { handleTestQuick } from './prometheus-ds/helpers';
import PromDsFormDialog from './prometheus-ds/PromDsFormDialog';
import { PageLoading } from '@/components/layout/PageLoading';
import { PageEmpty } from '@/components/layout/PageEmpty';
import { ContentPanel } from '@/components/layout/ContentPanel';
import i18n from '@/i18n';
export default function PrometheusDatasourceManager() {
const _confirm = useConfirm();
const { data: sources = [], isLoading, isError } = usePromDatasources();
const { deleteMutation, setDefaultMutation } = usePromDatasourceMutations();
const [dialogOpen, setDialogOpen] = useState(false);
const [editingDs, setEditingDs] = useState<PromDS | null>(null);
useEffect(() => {
if (isError) toast.error(i18n.t('monitor.promDs2.listFailed'));
}, [isError]);
const openCreate = () => {
setEditingDs(null);
setDialogOpen(true);
};
const openEdit = (ds: PromDS) => {
setEditingDs(ds);
setDialogOpen(true);
};
const handleDelete = async (ds: PromDS) => {
const ok = await _confirm({
title: i18n.t('monitor.deleteDatasource'),
description: i18n.t("monitor.deleteDatasourceConfirmDesc", { name: ds.name }),
variant: 'destructive',
});
if (!ok) return;
try {
await deleteMutation.mutateAsync(ds.id);
toast.success(i18n.t('monitor.silenceRules.deleted'));
} catch {
toast.error(i18n.t('kubernetes.msg.deleteFailed2'));
}
};
const handleSetDefault = async (ds: PromDS) => {
try {
await setDefaultMutation.mutateAsync(ds.id);
toast.success(i18n.t('monitor.setDefaultDsSuccess', { name: ds.name }));
} catch {
toast.error(i18n.t('monitor.promDs2.deleteFailed'));
}
};
if (isLoading) {
return <PageLoading />;
}
const toolbar = (
<div className="flex items-center justify-between w-full">
<span className="text-sm dg-text-secondary flex items-center gap-2">
<Database className="w-4 h-4 dg-text-blue" />
Prometheus 数据源
<Badge variant="secondary" className="dg-badge-muted">{sources.length}</Badge>
</span>
<Button onClick={openCreate} size="sm" className="dg-btn-primary">
<Plus className="w-4 h-4 mr-1" />
添加数据源
</Button>
</div>
);
return (
<div className="space-y-4">
<ContentPanel toolbar={toolbar} noPadding>
{sources.length === 0 ? (
<PageEmpty
icon={<Database className="h-12 w-12" />}
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" />{i18n.t("common.addDatasource")}
</Button>
)}
/>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead className="border-b border-[var(--border-default)]">
<tr className="text-xs dg-text-secondary">
<th className="text-left py-3 px-4 font-medium"> {i18n.t('kubernetes.secret.secretName')} </th>
<th className="text-left py-3 px-4 font-medium">URL</th>
<th className="text-left py-3 px-4 font-medium w-20"> {i18n.t('k8s.storage.default')} </th>
<th className="text-left py-3 px-4 font-medium w-16"> {i18n.t('system.terminalStatus')} </th>
<th className="text-right py-3 px-4 font-medium w-36"> {i18n.t('system.auditAction')} </th>
</tr>
</thead>
<tbody>
{sources.map(ds => (
<tr
key={ds.id}
className="dg-table-row border-b border-[var(--border-subtle)]"
>
<td className="py-3 px-4">
<div className="flex items-center gap-2">
<img
src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/prometheus.png"
alt="Prometheus"
className="w-5 h-5 rounded"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/>
<div>
<span className="text-sm font-medium text-[var(--text-primary)]">{ds.name}</span>
{ds.basic_auth && <Key className="w-3 h-3 inline ml-1.5 dg-text-yellow" />}
</div>
</div>
</td>
<td className="py-3 px-4">
<code className="text-xs dg-text-blue bg-[var(--surface-inset)] px-2 py-0.5 rounded max-w-[320px] block truncate"
title={ds.url}>
{ds.url}
</code>
</td>
<td className="py-3 px-4">
{ds.is_default ? (
<span className="dg-badge-yellow"><Star className="w-3 h-3 mr-1 inline" /> {i18n.t('k8s.storage.default')} </span>
) : (
<span className="text-xs dg-text-muted">--</span>
)}
</td>
<td className="py-3 px-4">
{ds.status === 1 ? (
<span className="dg-status-running"> {i18n.t('system.userActive')} </span>
) : (
<span className="dg-badge-muted"> {i18n.t('system.userInactive')} </span>
)}
</td>
<td className="py-3 px-4">
<div className="flex items-center justify-end gap-1">
<Button
variant="ghost" size="sm"
className="h-8 px-2"
onClick={() => handleTestQuick(ds)}
title={i18n.t('aiops.llmTest')}
>
<Plug className="w-3.5 h-3.5" />
</Button>
{!ds.is_default && (
<Button
variant="ghost" size="sm"
className="h-8 px-2 dg-text-yellow"
onClick={() => handleSetDefault(ds)}
title={i18n.t('aiops.llmSetDefault')}
>
<Star className="w-3.5 h-3.5" />
</Button>
)}
<Button
variant="ghost" size="sm"
className="h-8 px-2"
onClick={() => openEdit(ds)}
>
<Edit className="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost" size="sm"
className="h-8 px-2 dg-text-red hover:dg-text-red"
onClick={() => handleDelete(ds)}
>
<Trash2 className="w-3.5 h-3.5" />
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</ContentPanel>
<PromDsFormDialog
open={dialogOpen}
onOpenChange={setDialogOpen}
editingDs={editingDs}
sources={sources}
/>
</div>
);
}