Files
aiops/frontend/src/components/cloud/BillingRuleDialog.tsx
T
attacker ecec73989b feat(i18n): 剩余模块全量国际化 — 51% 覆盖率 (231 文件接入 t())
批量转换 163 个文件,3204 处中文替换为 t() 调用:
- Monitor: 日志中心/告警中心/数据源/大屏/站点拨测/SNMP/中间件 全部转换
- Kubernetes: ResourceVisualEditor/NamespaceResourcePage/拓扑/事件 全部转换
- Deploy: 流水线/发布/凭证/RBAC 全部转换
- Server/Cloud/Agent/Task/Layout/UI: 全部转换
- 翻译词典扩展至 6474 行 (22 namespaces, 5000+ keys)
- tsc 编译零错误
2026-07-03 01:51:57 +08:00

153 lines
6.3 KiB
TypeScript

// 云账单预警规则弹窗(新建/编辑)— 自包含状态,从 CloudBilling 提取 // Logan.Li
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Loader2 } from 'lucide-react';
import { toast } from 'sonner';
import { billingApi } from '@/api';
import { providerLabels } from './CloudBillingHelpers';
import type { JsonData } from '@/types/common';
import { useTranslation } from 'react-i18next';
const emptyRule = { name: '', provider: '', metric: 'daily', threshold: '', operator: '>', enabled: true };
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
editingRule: JsonData | null;
onSuccess: () => void;
}
export function BillingRuleDialog({ open, onOpenChange, editingRule, onSuccess }: Props) {
const { t } = useTranslation();
const [form, setForm] = useState<JsonData>(emptyRule);
const [submitting, setSubmitting] = useState(false);
// 弹窗打开时初始化表单 // Logan.Li
useEffect(() => {
if (!open) return;
if (editingRule) {
setForm({
name: editingRule.name,
provider: editingRule.provider || '',
metric: editingRule.metric || 'daily',
threshold: editingRule.threshold,
operator: editingRule.operator || '>',
enabled: editingRule.enabled,
});
} else {
setForm({ ...emptyRule });
}
}, [open, editingRule]);
const handleSave = async () => {
if (!form.name || !form.threshold) {
toast.error(t('cloud.billingruledialog.sd36855'));
return;
}
setSubmitting(true);
try {
const data = { ...form, threshold: parseFloat(form.threshold) };
if (editingRule) {
await billingApi.updateAlertRule(editingRule.id, data);
} else {
await billingApi.createAlertRule(data);
}
onOpenChange(false);
onSuccess();
toast(t('common.savedSuccessfully'));
} catch (e) {
toast.error(t('common.saveFailed'), { description: (e as JsonData).message });
} finally {
setSubmitting(false);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>{editingRule ? t('cloud.billingruledialog.sb8cfa0') : t('cloud.billingruledialog.sd58c54')}</DialogTitle>
<DialogDescription>{t('cloud.billingruledialog.s75227f')}</DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]">{t('cloud.billingruledialog.s34fe90')}</Label>
<Input className="mt-1" placeholder=t('cloud.billingruledialog.sc147ac') value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]">{t('common.vendor')}</Label>
<Select value={form.provider || 'all'} onValueChange={(v) => setForm({ ...form, provider: v === 'all' ? '' : v })}>
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="all">{t('cloud.billingruledialog.sc356f1')}</SelectItem>
{Object.entries(providerLabels).map(([k, v]) => (
<SelectItem key={k} value={k}>{v}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]">{t('cloud.colMetric')}</Label>
<Select value={form.metric} onValueChange={(v) => setForm({ ...form, metric: v })}>
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="daily">{t('cloud.dailyCost')}</SelectItem>
<SelectItem value="monthly">{t('cloud.monthlyAccumulated')}</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]">{t('cloud.billingruledialog.sd7650e')}</Label>
<Select value={form.operator} onValueChange={(v) => setForm({ ...form, operator: v })}>
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value=">">{t('cloud.billingruledialog.s3dba97')}</SelectItem>
<SelectItem value=">=">{t('cloud.billingruledialog.s8e1e26')}</SelectItem>
<SelectItem value="<">{t('cloud.billingruledialog.s0196c1')}</SelectItem>
<SelectItem value="<=">{t('cloud.billingruledialog.s45c301')}</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]">{t('cloud.billingruledialog.s11f9fb')}</Label>
<Input type="number" step="0.01" className="mt-1" placeholder="500.00" value={form.threshold} onChange={(e) => setForm({ ...form, threshold: e.target.value })} />
</div>
</div>
<div className="flex items-center gap-2">
<Switch checked={form.enabled} onCheckedChange={(v) => setForm({ ...form, enabled: v })} />
<Label className="text-sm text-[var(--dg-text-secondary)]">{t('common.enable')}</Label>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>{t('common.cancel')}</Button>
<Button onClick={handleSave} disabled={submitting}>
{submitting && <Loader2 className="h-4 w-4 mr-1 animate-spin" />}
保存
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}