From fe075980396110e9fd8d236d431393555785e1b8 Mon Sep 17 00:00:00 2001 From: "Logan.Li" Date: Wed, 5 Aug 2026 20:18:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=95=B4=E5=90=88?= =?UTF-8?q?=E5=90=8E=E7=9A=84=E5=89=8D=E7=AB=AF=E7=B1=BB=E5=9E=8B=E5=A5=91?= =?UTF-8?q?=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补齐账单币种与公共组件类型导出 - 修复 LDAP 和主机监控拆分组件类型 - 删除无后端接口且未引用的旧凭证弹窗 --- frontend/src/api/cloud/billing.ts | 2 +- .../components/cloud/CloudBillingHelpers.tsx | 17 +- .../deploy/CredentialFormDialog.tsx | 150 ------------------ .../components/system/SystemSettingsTab.tsx | 2 +- .../system-settings/LdapSettingsTab.tsx | 4 +- frontend/src/components/ui/confirm-dialog.tsx | 2 +- .../src/pages/monitor/host-monitor/types.ts | 9 +- 7 files changed, 27 insertions(+), 159 deletions(-) delete mode 100644 frontend/src/components/deploy/CredentialFormDialog.tsx diff --git a/frontend/src/api/cloud/billing.ts b/frontend/src/api/cloud/billing.ts index a948e5d..e4c24ad 100644 --- a/frontend/src/api/cloud/billing.ts +++ b/frontend/src/api/cloud/billing.ts @@ -16,7 +16,7 @@ export const billingApi = { getCostAnalysis: async (params?: { start_month?: string; end_month?: string; dimension?: string; currency?: string; provider?: string; cloud_account_id?: number }) => { return http.get('/cloud-billings/cost-analysis', params); }, - list: async (params?: { provider?: string; cloud_account_id?: number; start_date?: string; end_date?: string; page?: number; page_size?: number }) => { + list: async (params?: { provider?: string; cloud_account_id?: number; currency?: string; start_date?: string; end_date?: string; page?: number; page_size?: number }) => { return http.get('/cloud-billings', params); }, create: async (data: Record) => { diff --git a/frontend/src/components/cloud/CloudBillingHelpers.tsx b/frontend/src/components/cloud/CloudBillingHelpers.tsx index 1db593a..97b200e 100644 --- a/frontend/src/components/cloud/CloudBillingHelpers.tsx +++ b/frontend/src/components/cloud/CloudBillingHelpers.tsx @@ -42,6 +42,21 @@ export function formatCurrency(n: number) { return `¥${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } +// 按账单原币种展示金额,未知币种回退为币种代码前缀。 // Logan.Li +export function formatCurrencyByCode(n: number, currency = 'CNY') { + const normalizedCurrency = currency.trim().toUpperCase() || 'CNY'; + try { + return new Intl.NumberFormat('zh-CN', { + style: 'currency', + currency: normalizedCurrency, + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(n); + } catch { + return `${normalizedCurrency} ${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; + } +} + export function StatCard({ icon, label, value, trend, trendLabel, alert }: { icon: ReactNode; label: string; @@ -106,4 +121,4 @@ export function TrendChart({ data }: { data: { date: string; cost: number }[] }) ); -} \ No newline at end of file +} diff --git a/frontend/src/components/deploy/CredentialFormDialog.tsx b/frontend/src/components/deploy/CredentialFormDialog.tsx deleted file mode 100644 index ffbc9d4..0000000 --- a/frontend/src/components/deploy/CredentialFormDialog.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { useState, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { - Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Plus, Key, Globe, Container } from 'lucide-react'; -import { toast } from 'sonner'; -import { credentialApi, deployApi } from '@/api'; -import type { JsonData } from '@/types/common'; - -interface Repo { - id: number; name: string; url: string; credential_id: number | null; -} -interface Registry { - id: number; name: string; registry_url: string; namespace: string; credential_id: number | null; -} -interface Credential { - id: number; name: string; secret_type: string; summary: string; -} - -interface Props { - open: boolean; - onOpenChange: (v: boolean) => void; - type: 'repo' | 'registry'; - editId: number | null; - repos: Repo[]; - registries: Registry[]; - onSave: () => void; -} - -export default function CredentialFormDialog({ open, onOpenChange, type, editId, repos, registries, onSave }: Props) { - const { t } = useTranslation(); - const isRepo = type === 'repo'; - const [name, setName] = useState(''); - const [desc, setDesc] = useState(''); - const [url, setUrl] = useState(''); - const [namespace, setNamespace] = useState(''); - const [credentialId, setCredentialId] = useState(''); - const [credentials, setCredentials] = useState([]); - const [saving, setSaving] = useState(false); - - // Docker 仓库 → docker 类型;Git 仓库 → api 或 docker 类型 - const filteredCredentials = credentials.filter(c => - isRepo ? true : c.secret_type === 'docker' - ); - - const loadCredentials = async () => { - try { - const res: JsonData = await credentialApi.list({ page_size: 100 }); - setCredentials(res?.list || []); - } catch { /* silent */ } - }; - - useEffect(() => { - loadCredentials(); - if (editId) { - if (isRepo) { - const r = repos.find(x => x.id === editId); - if (r) { setName(r.name); setDesc(''); setUrl(r.url); setCredentialId(r.credential_id || ''); } - } else { - const r = registries.find(x => x.id === editId); - if (r) { setName(r.name); setDesc(''); setUrl(r.registry_url); setNamespace(r.namespace || ''); setCredentialId(r.credential_id || ''); } - } - } else { - setName(''); setDesc(''); setUrl(''); setNamespace(''); setCredentialId(''); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [editId, open, isRepo]); - - const handleSave = async () => { - if (!name.trim() || !url.trim()) { toast.error(t('deploy.form.nameAddrRequired')); return; } - setSaving(true); - try { - const cid = credentialId ? Number(credentialId) : null; - const payload = isRepo - ? { name: name.trim(), description: desc.trim(), url: url.trim(), credential_id: cid } - : { name: name.trim(), description: desc.trim(), registry_url: url.trim(), namespace: namespace.trim(), credential_id: cid }; - if (editId) { - if (isRepo) await deployApi.updateRepo(editId, payload); - else await deployApi.updateRegistry(editId, payload); - toast.success(t('deploy.toast.updated')); - } else { - if (isRepo) await deployApi.createRepo(payload); - else await deployApi.createRegistry(payload); - toast.success(t('deploy.toast.created')); - } - onOpenChange(false); - onSave(); - } catch { toast.error(t('deploy.toast.saveFailed')); } - finally { setSaving(false); } - }; - - const CredIcon = isRepo ? Globe : Container; - - return ( - - - - - - {editId ? t('common.edit') : t('common.add')} {isRepo ? t('deploy.credForm.gitRepo') : t('deploy.credForm.imageRegistry')} - - -
-
- setName(e.target.value)} placeholder={isRepo ? t('deploy.credForm.repoNamePlaceholder') : t('deploy.credForm.registryNamePlaceholder')} className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
-
- setUrl(e.target.value)} placeholder={isRepo ? 'http://10.10.10.72:9000/group/repo.git' : 'http://10.10.10.72'} className="font-mono text-sm bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
- {!isRepo &&
- setNamespace(e.target.value)} placeholder={t('deploy.credForm.namespacePlaceholder')} className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
} - - {/* 关联凭证 */} -
- - - {filteredCredentials.length === 0 && ( -

- - {t('deploy.credForm.noCredHint', { type: isRepo ? '' : t('deploy.credForm.dockerPwd') })} -

- )} -
-
- - - - -
-
- ); -} diff --git a/frontend/src/components/system/SystemSettingsTab.tsx b/frontend/src/components/system/SystemSettingsTab.tsx index c95760c..9d6d562 100644 --- a/frontend/src/components/system/SystemSettingsTab.tsx +++ b/frontend/src/components/system/SystemSettingsTab.tsx @@ -51,7 +51,7 @@ export interface LdapFormData { search_filter: string; } -interface SystemSettingsTabProps { +export interface SystemSettingsTabProps { // 审计策略 // Logan.Li auditRetentionDays: string; setAuditRetentionDays: (v: string) => void; diff --git a/frontend/src/components/system/system-settings/LdapSettingsTab.tsx b/frontend/src/components/system/system-settings/LdapSettingsTab.tsx index 00b245e..de4b2dc 100644 --- a/frontend/src/components/system/system-settings/LdapSettingsTab.tsx +++ b/frontend/src/components/system/system-settings/LdapSettingsTab.tsx @@ -17,7 +17,6 @@ type LdapSettingsTabProps = Pick< | "ldapSaving" | "ldapTesting" | "ldapTestResult" - | "ldapTestSuccess" | "handleLdapSave" | "handleLdapTest" > & @@ -76,7 +75,6 @@ export function LdapSettingsTab(props: LdapSettingsTabProps) { ldapSaving, ldapTesting, ldapTestResult, - ldapTestSuccess, handleLdapSave, handleLdapTest, } = props; @@ -206,7 +204,7 @@ export function LdapSettingsTab(props: LdapSettingsTabProps) {
{ldapTestResult && ( {ldapTestResult} diff --git a/frontend/src/components/ui/confirm-dialog.tsx b/frontend/src/components/ui/confirm-dialog.tsx index f4cbf20..e2c715f 100644 --- a/frontend/src/components/ui/confirm-dialog.tsx +++ b/frontend/src/components/ui/confirm-dialog.tsx @@ -7,7 +7,7 @@ import { import { Button } from '@/components/ui/button'; import i18n from '@/i18n'; -interface ConfirmOptions { +export interface ConfirmOptions { title?: string; description?: string; confirmText?: string; diff --git a/frontend/src/pages/monitor/host-monitor/types.ts b/frontend/src/pages/monitor/host-monitor/types.ts index e1a4b8b..3dd1c4a 100644 --- a/frontend/src/pages/monitor/host-monitor/types.ts +++ b/frontend/src/pages/monitor/host-monitor/types.ts @@ -42,7 +42,12 @@ export const TIME_RANGES = TIME_RANGE_PRESETS.map((p) => ({ export type AggType = 'avg' | 'max' | 'min'; - +export interface RefLine { + value: number; + label?: string; + color?: string; +} + export type HostRecord = Record; export interface BucketPoint { @@ -84,4 +89,4 @@ export const DEFAULT_PROBE_FORM = (hostname = ''): ProbeForm => ({ timeout: 10, interval: 60, description: '', -}); \ No newline at end of file +});