fix(log): ES 7.2.0 time format .SSSZ + index dropdown + demo always visible

- ES query 502 fix: remove +00:00 conversion, use native .SSSZ format
  (ES 7.2.0 date_time = strict_date_optional_time only, rejects +00:00/epoch)
- parseLogTime: use time.RFC3339Nano for fractional seconds
- new API GET /log-datasources/indices to list ES indices via proxy
- LogDatasourceDialog: add '获取索引' button + Select dropdown for ES
- LogDatasourcePage: always show demo sources alongside real ones
This commit is contained in:
attacker committed 2026-06-27 09:45:21 +00:00
1 parent 2e179ef7f4
commit a04d815efe
7 files changed
+308 -15

No files matched your search

+3
View File
@@ -34,6 +34,9 @@ export const logCenterApi = {
deleteDatasource: (id: number | string) => http.delete(`/log-datasources/${id}`),
setDefaultDatasource: (id: number | string) => http.post(`/log-datasources/${id}/default`),
testDatasource: (data: Record<string, unknown>) => http.post('/log-datasources/test', data),
/** 通过代理查询 ES 集群索引列表 // Logan.Li */
listIndices: (params: Record<string, string>) =>
http.get<string[]>('/log-datasources/indices', params),
queryLoki: (data: {
datasource_id: number;
query: string;
@@ -7,7 +7,10 @@ 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 { Loader2, Plug } from 'lucide-react';
import {
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
} from '@/components/ui/select';
import { Loader2, Plug, RefreshCw } from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
import { logCenterApi, type LogDataSource } from '@/api/monitor/log-center';
import type { LogEngine } from './log-center-types';
@@ -32,6 +35,9 @@ export function LogDatasourceDialog({
const [isDefault, setIsDefault] = useState(false);
const [saving, setSaving] = useState(false);
const [testing, setTesting] = useState(false);
// 索引下拉: ES 索引列表 + 加载状态 // Logan.Li
const [indices, setIndices] = useState<string[]>([]);
const [loadingIndices, setLoadingIndices] = useState(false);
useEffect(() => {
if (!open) return;
@@ -44,6 +50,10 @@ export function LogDatasourceDialog({
setPass('');
setSkipTLS(!!editing.skip_tls_verify);
setIsDefault(!!editing.is_default);
// 编辑 ES 源时预加载索引列表
if (editing.type === 'elasticsearch' && editing.url) {
fetchIndices(editing.url, !!editing.basic_auth, editing.basic_auth_user || '', '');
}
} else {
setName(engine === 'loki' ? 'Loki' : 'Elasticsearch');
setUrl(engine === 'loki' ? 'http://localhost:3100' : 'http://localhost:9200');
@@ -53,9 +63,30 @@ export function LogDatasourceDialog({
setPass('');
setSkipTLS(false);
setIsDefault(false);
setIndices([]);
}
}, [open, editing, engine]);
const fetchIndices = async (esUrl: string, auth: boolean, authUser: string, authPass: string) => {
if (!esUrl) return;
setLoadingIndices(true);
try {
const result = await logCenterApi.listIndices({
url: esUrl,
basic_auth: auth ? 'true' : 'false',
basic_auth_user: authUser,
basic_auth_pass: authPass,
skip_tls_verify: skipTLS ? 'true' : 'false',
});
setIndices(Array.isArray(result) ? result : []);
} catch {
// 静默失败,仍可手动输入
setIndices([]);
} finally {
setLoadingIndices(false);
}
};
const payload = () => ({
name: name.trim(),
type: engine,
@@ -121,8 +152,39 @@ export function LogDatasourceDialog({
</div>
{engine === 'elasticsearch' && (
<div>
<Label className="text-xs text-[var(--dg-text-muted)]">默认索引</Label>
<Input value={defaultIndex} onChange={e => setDefaultIndex(e.target.value)} className="mt-1 h-9 font-mono text-xs bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
<div className="flex items-center justify-between">
<Label className="text-xs text-[var(--dg-text-muted)]">默认索引</Label>
<Button
variant="ghost"
size="sm"
className="h-6 px-1.5 text-[10px] text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)]"
onClick={() => fetchIndices(url, basicAuth, user, pass)}
disabled={!url || loadingIndices}
>
{loadingIndices ? (
<Loader2 className="h-3 w-3 animate-spin mr-0.5" />
) : (
<RefreshCw className="h-3 w-3 mr-0.5" />
)}
获取索引
</Button>
</div>
{indices.length > 0 ? (
<Select value={defaultIndex} onValueChange={setDefaultIndex}>
<SelectTrigger className="mt-1 h-9 w-full font-mono text-xs bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] max-h-48">
{indices.map((idx) => (
<SelectItem key={idx} value={idx} className="font-mono text-xs">
{idx}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Input value={defaultIndex} onChange={e => setDefaultIndex(e.target.value)} placeholder="索引名或通配符,如 logstash-*" className="mt-1 h-9 font-mono text-xs bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
)}
</div>
)}
<div className="flex items-center justify-between">
@@ -157,4 +219,4 @@ export function LogDatasourceDialog({
</DialogContent>
</Dialog>
);
}
}
@@ -32,8 +32,8 @@ export function LogDatasourcePage() {
try {
const res = await logCenterApi.listDatasources();
const apiList: LogDataSource[] = Array.isArray(res) ? res : [];
// API 有数据 → 展示真实数据源;无数据 → demo 兜底 // Logan.Li
setSources(apiList.length > 0 ? apiList : DEMO_FALLBACK);
// 真实数据源在前 + demo 兜底在后 // Logan.Li
setSources([...apiList, ...DEMO_FALLBACK]);
} catch {
setSources(DEMO_FALLBACK);
} finally {