refactor(quality): Q31 前端巨型组件拆分 — PromDsFormDialog 528L→294L (-44%), 提取 prometheus-ds/ 6 个文件 (CollapsibleSection 42L 通用折叠包装器 DRY 3 个重复折叠模式 + AuthSection 107L 认证折叠 + HeadersSection 64L 自定义Headers + AlertingSection 58L 告警折叠 + MiscSection 47L 杂项折叠 + TestResultBanner 32L 测试结果横幅), Auth/Alerting/Misc 三个折叠区块统一使用 CollapsibleSection 消除 ~30 行重复折叠 JSX, updateForm 泛型化 <K extends keyof> 类型安全提升, 零逻辑变更, ESLint errors=0/warnings=65/any=0, tsc -b=0, npm build✓ (17.65s), go build=0, 17/17 测试通过
This commit is contained in:
1 parent
45f7342d1e
commit
195b6945b4
9 files changed
+405
-250
No files matched your search
@@ -0,0 +1,58 @@
|
||||
// Prometheus 数据源表单 — Alerting 告警设置区块
|
||||
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { PromDS, PromDsForm } from './types';
|
||||
import CollapsibleSection from './CollapsibleSection';
|
||||
|
||||
interface AlertingSectionProps {
|
||||
form: PromDsForm;
|
||||
updateForm: <K extends keyof PromDsForm>(field: K, value: PromDsForm[K]) => void;
|
||||
sources: PromDS[];
|
||||
editingId: number | null;
|
||||
open: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
/** Alerting 折叠区块(启用告警 + Alertmanager 数据源选择) */
|
||||
export default function AlertingSection({
|
||||
form, updateForm, sources, editingId, open, onToggle,
|
||||
}: AlertingSectionProps) {
|
||||
const { t } = useTranslation(['monitor', 'common']);
|
||||
return (
|
||||
<CollapsibleSection icon={AlertTriangle} title="Alerting" open={open} onToggle={onToggle}>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-enable-alerting"
|
||||
checked={form.enable_alerting}
|
||||
onCheckedChange={v => updateForm('enable_alerting', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-enable-alerting" className="text-sm cursor-pointer">
|
||||
Manage alerts via Alerting UI
|
||||
</Label>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm text-[var(--dg-text-secondary)]">
|
||||
Alertmanager data source
|
||||
</Label>
|
||||
<Select value={form.alertmanager_ds} onValueChange={v => updateForm('alertmanager_ds', v)}>
|
||||
<SelectTrigger className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm">
|
||||
<SelectValue placeholder="Choose" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none">{t('common:none')}</SelectItem>
|
||||
{sources.filter(s => s.id !== editingId).map(s => (
|
||||
<SelectItem key={s.id} value={String(s.id)}>{s.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Prometheus 数据源表单 — Auth 认证设置区块
|
||||
|
||||
import { Shield } from 'lucide-react';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { PromDsForm } from './types';
|
||||
import CollapsibleSection from './CollapsibleSection';
|
||||
|
||||
interface AuthSectionProps {
|
||||
form: PromDsForm;
|
||||
updateForm: <K extends keyof PromDsForm>(field: K, value: PromDsForm[K]) => void;
|
||||
open: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
/** Auth 认证折叠区块(Basic auth / With Credentials / TLS Client Auth / Skip TLS Verify) */
|
||||
export default function AuthSection({ form, updateForm, open, onToggle }: AuthSectionProps) {
|
||||
const { t } = useTranslation(['monitor']);
|
||||
return (
|
||||
<CollapsibleSection icon={Shield} title="Auth" open={open} onToggle={onToggle}>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-basic-auth"
|
||||
checked={form.basic_auth}
|
||||
onCheckedChange={v => updateForm('basic_auth', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-basic-auth" className="text-sm cursor-pointer">Basic auth</Label>
|
||||
</div>
|
||||
{form.basic_auth && (
|
||||
<div className="grid grid-cols-2 gap-3 ml-9">
|
||||
<Input
|
||||
placeholder={t('monitor:promDs.username')}
|
||||
value={form.basic_auth_user}
|
||||
onChange={e => updateForm('basic_auth_user', e.target.value)}
|
||||
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm"
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={t('monitor:promDs.password')}
|
||||
value={form.basic_auth_pass}
|
||||
onChange={e => updateForm('basic_auth_pass', e.target.value)}
|
||||
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-with-creds"
|
||||
checked={form.with_credentials}
|
||||
onCheckedChange={v => updateForm('with_credentials', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-with-creds" className="text-sm cursor-pointer">With Credentials</Label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-tls-auth"
|
||||
checked={form.tls_client_auth}
|
||||
onCheckedChange={v => updateForm('tls_client_auth', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-tls-auth" className="text-sm cursor-pointer">TLS Client Auth</Label>
|
||||
</div>
|
||||
{form.tls_client_auth && (
|
||||
<div className="ml-9 space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-ca-cert"
|
||||
checked={form.with_ca_cert}
|
||||
onCheckedChange={v => updateForm('with_ca_cert', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-ca-cert" className="text-sm cursor-pointer">With CA Cert</Label>
|
||||
</div>
|
||||
{form.with_ca_cert && (
|
||||
<textarea
|
||||
placeholder={t('monitor:promDs.caCertPlaceholder')}
|
||||
value={form.ca_cert}
|
||||
onChange={e => updateForm('ca_cert', e.target.value)}
|
||||
rows={4}
|
||||
className="w-full bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-md p-3 text-xs font-mono text-[var(--dg-text-primary)] resize-y"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-skip-tls"
|
||||
checked={form.skip_tls_verify}
|
||||
onCheckedChange={v => updateForm('skip_tls_verify', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-skip-tls" className="text-sm cursor-pointer">Skip TLS Verify</Label>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-[var(--dg-text-secondary)] italic">
|
||||
Forward OAuth Identity
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Prometheus 数据源表单 — 可折叠区块通用包装器
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
interface CollapsibleSectionProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
open: boolean;
|
||||
onToggle: () => void;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/** 可折叠区块(Grafana 风格 — 按钮 header + 展开内容) */
|
||||
export default function CollapsibleSection({
|
||||
icon: Icon,
|
||||
title,
|
||||
open,
|
||||
onToggle,
|
||||
children,
|
||||
}: CollapsibleSectionProps) {
|
||||
return (
|
||||
<div className="border border-[var(--dg-border)]/50 rounded-lg overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center justify-between px-4 py-3 bg-[var(--dg-bg-tertiary)]/30 hover:bg-[var(--dg-bg-tertiary)]/60 transition-colors"
|
||||
onClick={onToggle}
|
||||
>
|
||||
<span className="text-sm font-medium flex items-center gap-2">
|
||||
<Icon className="w-4 h-4" /> {title}
|
||||
</span>
|
||||
{open ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||
</button>
|
||||
{open && (
|
||||
<div className="p-4 space-y-4 border-t border-[var(--dg-border)]/50">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Prometheus 数据源表单 — Custom HTTP Headers 区块
|
||||
|
||||
import { Plus, Wrench, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { HeaderEntry } from './types';
|
||||
|
||||
interface HeadersSectionProps {
|
||||
headers: HeaderEntry[];
|
||||
onAdd: () => void;
|
||||
onRemove: (idx: number) => void;
|
||||
onUpdate: (idx: number, field: 'header' | 'value', val: string) => void;
|
||||
}
|
||||
|
||||
/** Custom HTTP Headers 区块(键值对增删改) */
|
||||
export default function HeadersSection({
|
||||
headers, onAdd, onRemove, onUpdate,
|
||||
}: HeadersSectionProps) {
|
||||
const { t } = useTranslation(['monitor']);
|
||||
return (
|
||||
<div className="space-y-3 bg-[var(--dg-bg-tertiary)]/30 rounded-lg p-4 border border-[var(--dg-border)]/50">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-sm font-medium flex items-center gap-2 text-[var(--dg-text-primary)]">
|
||||
<Wrench className="w-4 h-4" /> Custom HTTP Headers
|
||||
</h4>
|
||||
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={onAdd}>
|
||||
<Plus className="w-3 h-3 mr-1" /> Add header
|
||||
</Button>
|
||||
</div>
|
||||
{headers.length === 0 ? (
|
||||
<p className="text-xs text-[var(--dg-text-secondary)] italic">
|
||||
{t('monitor:promDs.noHeaders')}
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{headers.map((h, idx) => (
|
||||
<div key={idx} className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder="Header"
|
||||
value={h.header}
|
||||
onChange={e => onUpdate(idx, 'header', e.target.value)}
|
||||
className="flex-1 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm h-8"
|
||||
/>
|
||||
<Input
|
||||
placeholder="Value"
|
||||
value={h.value}
|
||||
onChange={e => onUpdate(idx, 'value', e.target.value)}
|
||||
className="flex-1 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm h-8"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost" size="sm" className="h-8 w-8 p-0 dg-text-red hover:dg-text-red"
|
||||
onClick={() => onRemove(idx)}
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Prometheus 数据源表单 — Misc 杂项设置区块
|
||||
|
||||
import { Settings } from 'lucide-react';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { PromDsForm } from './types';
|
||||
import CollapsibleSection from './CollapsibleSection';
|
||||
|
||||
interface MiscSectionProps {
|
||||
form: PromDsForm;
|
||||
updateForm: <K extends keyof PromDsForm>(field: K, value: PromDsForm[K]) => void;
|
||||
open: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
/** Misc 折叠区块(禁用 metrics 查找 + 自定义查询参数) */
|
||||
export default function MiscSection({ form, updateForm, open, onToggle }: MiscSectionProps) {
|
||||
const { t } = useTranslation(['monitor']);
|
||||
return (
|
||||
<CollapsibleSection icon={Settings} title="Misc" open={open} onToggle={onToggle}>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-disable-lookup"
|
||||
checked={form.disable_metrics_lookup}
|
||||
onCheckedChange={v => updateForm('disable_metrics_lookup', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-disable-lookup" className="text-sm cursor-pointer">
|
||||
Disable metrics lookup
|
||||
</Label>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm text-[var(--dg-text-secondary)]">
|
||||
Custom query parameters
|
||||
</Label>
|
||||
<Input
|
||||
placeholder={t('monitor:promDs.queryParamsPlaceholder')}
|
||||
value={form.custom_query_params}
|
||||
onChange={e => updateForm('custom_query_params', e.target.value)}
|
||||
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm font-mono"
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
);
|
||||
}
|
||||
@@ -2,27 +2,10 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Database,
|
||||
Plus,
|
||||
RefreshCw,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Shield,
|
||||
Globe,
|
||||
Clock,
|
||||
Wrench,
|
||||
Settings,
|
||||
X,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Plug,
|
||||
AlertTriangle,
|
||||
} from 'lucide-react';
|
||||
import { Database, Globe, Clock, RefreshCw, Plug } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
@@ -37,6 +20,11 @@ import { usePromDatasourceMutations } from '@/hooks/api';
|
||||
import type { PromDS, HeaderEntry, PromDsForm } from './types';
|
||||
import { defaultForm } from './types';
|
||||
import { dsToForm, parseHeaders, buildPayload, testPromConnection, type TestConnectionResult } from './helpers';
|
||||
import AuthSection from './AuthSection';
|
||||
import HeadersSection from './HeadersSection';
|
||||
import AlertingSection from './AlertingSection';
|
||||
import MiscSection from './MiscSection';
|
||||
import TestResultBanner from './TestResultBanner';
|
||||
|
||||
interface PromDsFormDialogProps {
|
||||
open: boolean;
|
||||
@@ -81,7 +69,7 @@ export default function PromDsFormDialog({
|
||||
setShowMisc(false);
|
||||
}, [open, editingDs]);
|
||||
|
||||
const updateForm = (field: keyof PromDsForm, value: PromDsForm[keyof PromDsForm]) => {
|
||||
const updateForm = <K extends keyof PromDsForm>(field: K, value: PromDsForm[K]) => {
|
||||
setForm(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
@@ -215,185 +203,13 @@ export default function PromDsFormDialog({
|
||||
</div>
|
||||
|
||||
{/* === Auth 折叠 === */}
|
||||
<div className="border border-[var(--dg-border)]/50 rounded-lg overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center justify-between px-4 py-3 bg-[var(--dg-bg-tertiary)]/30 hover:bg-[var(--dg-bg-tertiary)]/60 transition-colors"
|
||||
onClick={() => setShowAuth(!showAuth)}
|
||||
>
|
||||
<span className="text-sm font-medium flex items-center gap-2">
|
||||
<Shield className="w-4 h-4" /> Auth
|
||||
</span>
|
||||
{showAuth ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||
</button>
|
||||
{showAuth && (
|
||||
<div className="p-4 space-y-4 border-t border-[var(--dg-border)]/50">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-basic-auth"
|
||||
checked={form.basic_auth}
|
||||
onCheckedChange={v => updateForm('basic_auth', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-basic-auth" className="text-sm cursor-pointer">Basic auth</Label>
|
||||
</div>
|
||||
{form.basic_auth && (
|
||||
<div className="grid grid-cols-2 gap-3 ml-9">
|
||||
<Input
|
||||
placeholder={t('monitor:promDs.username')}
|
||||
value={form.basic_auth_user}
|
||||
onChange={e => updateForm('basic_auth_user', e.target.value)}
|
||||
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm"
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={t('monitor:promDs.password')}
|
||||
value={form.basic_auth_pass}
|
||||
onChange={e => updateForm('basic_auth_pass', e.target.value)}
|
||||
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-with-creds"
|
||||
checked={form.with_credentials}
|
||||
onCheckedChange={v => updateForm('with_credentials', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-with-creds" className="text-sm cursor-pointer">With Credentials</Label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-tls-auth"
|
||||
checked={form.tls_client_auth}
|
||||
onCheckedChange={v => updateForm('tls_client_auth', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-tls-auth" className="text-sm cursor-pointer">TLS Client Auth</Label>
|
||||
</div>
|
||||
{form.tls_client_auth && (
|
||||
<div className="ml-9 space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-ca-cert"
|
||||
checked={form.with_ca_cert}
|
||||
onCheckedChange={v => updateForm('with_ca_cert', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-ca-cert" className="text-sm cursor-pointer">With CA Cert</Label>
|
||||
</div>
|
||||
{form.with_ca_cert && (
|
||||
<textarea
|
||||
placeholder={t('monitor:promDs.caCertPlaceholder')}
|
||||
value={form.ca_cert}
|
||||
onChange={e => updateForm('ca_cert', e.target.value)}
|
||||
rows={4}
|
||||
className="w-full bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-md p-3 text-xs font-mono text-[var(--dg-text-primary)] resize-y"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-skip-tls"
|
||||
checked={form.skip_tls_verify}
|
||||
onCheckedChange={v => updateForm('skip_tls_verify', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-skip-tls" className="text-sm cursor-pointer">Skip TLS Verify</Label>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-[var(--dg-text-secondary)] italic">
|
||||
Forward OAuth Identity
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<AuthSection form={form} updateForm={updateForm} open={showAuth} onToggle={() => setShowAuth(!showAuth)} />
|
||||
|
||||
{/* === Custom HTTP Headers === */}
|
||||
<div className="space-y-3 bg-[var(--dg-bg-tertiary)]/30 rounded-lg p-4 border border-[var(--dg-border)]/50">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-sm font-medium flex items-center gap-2 text-[var(--dg-text-primary)]">
|
||||
<Wrench className="w-4 h-4" /> Custom HTTP Headers
|
||||
</h4>
|
||||
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={addHeader}>
|
||||
<Plus className="w-3 h-3 mr-1" /> Add header
|
||||
</Button>
|
||||
</div>
|
||||
{headers.length === 0 ? (
|
||||
<p className="text-xs text-[var(--dg-text-secondary)] italic">{t('monitor:promDs.noHeaders')}</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{headers.map((h, idx) => (
|
||||
<div key={idx} className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder="Header"
|
||||
value={h.header}
|
||||
onChange={e => updateHeader(idx, 'header', e.target.value)}
|
||||
className="flex-1 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm h-8"
|
||||
/>
|
||||
<Input
|
||||
placeholder="Value"
|
||||
value={h.value}
|
||||
onChange={e => updateHeader(idx, 'value', e.target.value)}
|
||||
className="flex-1 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm h-8"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost" size="sm" className="h-8 w-8 p-0 dg-text-red hover:dg-text-red"
|
||||
onClick={() => removeHeader(idx)}
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<HeadersSection headers={headers} onAdd={addHeader} onRemove={removeHeader} onUpdate={updateHeader} />
|
||||
|
||||
{/* === Alerting 折叠 === */}
|
||||
<div className="border border-[var(--dg-border)]/50 rounded-lg overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center justify-between px-4 py-3 bg-[var(--dg-bg-tertiary)]/30 hover:bg-[var(--dg-bg-tertiary)]/60 transition-colors"
|
||||
onClick={() => setShowAlerting(!showAlerting)}
|
||||
>
|
||||
<span className="text-sm font-medium flex items-center gap-2">
|
||||
<AlertTriangle className="w-4 h-4" /> Alerting
|
||||
</span>
|
||||
{showAlerting ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||
</button>
|
||||
{showAlerting && (
|
||||
<div className="p-4 space-y-4 border-t border-[var(--dg-border)]/50">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-enable-alerting"
|
||||
checked={form.enable_alerting}
|
||||
onCheckedChange={v => updateForm('enable_alerting', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-enable-alerting" className="text-sm cursor-pointer">
|
||||
Manage alerts via Alerting UI
|
||||
</Label>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm text-[var(--dg-text-secondary)]">Alertmanager data source</Label>
|
||||
<Select value={form.alertmanager_ds} onValueChange={v => updateForm('alertmanager_ds', v)}>
|
||||
<SelectTrigger className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm">
|
||||
<SelectValue placeholder="Choose" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none">{t('common:none')}</SelectItem>
|
||||
{sources.filter(s => s.id !== editingId).map(s => (
|
||||
<SelectItem key={s.id} value={String(s.id)}>{s.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<AlertingSection form={form} updateForm={updateForm} sources={sources} editingId={editingId} open={showAlerting} onToggle={() => setShowAlerting(!showAlerting)} />
|
||||
|
||||
{/* === 采集与查询 === */}
|
||||
<div className="grid grid-cols-2 gap-4 bg-[var(--dg-bg-tertiary)]/30 rounded-lg p-4 border border-[var(--dg-border)]/50">
|
||||
@@ -444,61 +260,10 @@ export default function PromDsFormDialog({
|
||||
</div>
|
||||
|
||||
{/* === Misc 折叠 === */}
|
||||
<div className="border border-[var(--dg-border)]/50 rounded-lg overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center justify-between px-4 py-3 bg-[var(--dg-bg-tertiary)]/30 hover:bg-[var(--dg-bg-tertiary)]/60 transition-colors"
|
||||
onClick={() => setShowMisc(!showMisc)}
|
||||
>
|
||||
<span className="text-sm font-medium flex items-center gap-2">
|
||||
<Settings className="w-4 h-4" /> Misc
|
||||
</span>
|
||||
{showMisc ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||
</button>
|
||||
{showMisc && (
|
||||
<div className="p-4 space-y-4 border-t border-[var(--dg-border)]/50">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="ds-disable-lookup"
|
||||
checked={form.disable_metrics_lookup}
|
||||
onCheckedChange={v => updateForm('disable_metrics_lookup', v)}
|
||||
/>
|
||||
<Label htmlFor="ds-disable-lookup" className="text-sm cursor-pointer">
|
||||
Disable metrics lookup
|
||||
</Label>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm text-[var(--dg-text-secondary)]">Custom query parameters</Label>
|
||||
<Input
|
||||
placeholder={t('monitor:promDs.queryParamsPlaceholder')}
|
||||
value={form.custom_query_params}
|
||||
onChange={e => updateForm('custom_query_params', e.target.value)}
|
||||
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-sm font-mono"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<MiscSection form={form} updateForm={updateForm} open={showMisc} onToggle={() => setShowMisc(!showMisc)} />
|
||||
|
||||
{/* 连接测试结果 */}
|
||||
{testResult && (
|
||||
<div className={`rounded-lg p-3 flex items-center gap-2 text-sm ${
|
||||
testResult.success
|
||||
? 'bg-emerald-500/10 border border-emerald-500/30 dg-text-green'
|
||||
: 'bg-red-500/10 border border-red-500/30 dg-text-red'
|
||||
}`}>
|
||||
{testResult.success
|
||||
? <CheckCircle className="w-4 h-4 shrink-0" />
|
||||
: <XCircle className="w-4 h-4 shrink-0" />
|
||||
}
|
||||
<span>{testResult.message}</span>
|
||||
{testResult.target_count !== undefined && (
|
||||
<Badge variant="secondary" className="ml-auto text-xs">
|
||||
{testResult.target_count} targets
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<TestResultBanner result={testResult} />
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex items-center gap-2 pt-2 border-t border-[var(--dg-border)]/50">
|
||||
@@ -526,4 +291,4 @@ export default function PromDsFormDialog({
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Prometheus 数据源表单 — 连接测试结果横幅
|
||||
|
||||
import { CheckCircle, XCircle } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import type { TestConnectionResult } from './helpers';
|
||||
|
||||
interface TestResultBannerProps {
|
||||
result: TestConnectionResult | null;
|
||||
}
|
||||
|
||||
/** 连接测试结果横幅(成功绿色 / 失败红色 + target 计数徽章) */
|
||||
export default function TestResultBanner({ result }: TestResultBannerProps) {
|
||||
if (!result) return null;
|
||||
return (
|
||||
<div className={`rounded-lg p-3 flex items-center gap-2 text-sm ${
|
||||
result.success
|
||||
? 'bg-emerald-500/10 border border-emerald-500/30 dg-text-green'
|
||||
: 'bg-red-500/10 border border-red-500/30 dg-text-red'
|
||||
}`}>
|
||||
{result.success
|
||||
? <CheckCircle className="w-4 h-4 shrink-0" />
|
||||
: <XCircle className="w-4 h-4 shrink-0" />
|
||||
}
|
||||
<span>{result.message}</span>
|
||||
{result.target_count !== undefined && (
|
||||
<Badge variant="secondary" className="ml-auto text-xs">
|
||||
{result.target_count} targets
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user