feat: 新增日志中心、功能优化与代码重构
- 新增 log_datasource.go / log_proxy.go / user_api_token.go 后端控制器 - 新增 log_datasource.go model - 新增 monitor/log-center.ts API 模块 - 新增 LogCenter 前端组件及页面 - 重构 deployment 相关组件 (HPDA/UpdateImageModal/bundle-yaml 等) - 新增 ResetPasswordDialog / code-snippet-block / copy-button 等 UI 组件 - 重构多个 k8s 相关组件 (TopologyView/ResourceVisualEditor/PodFileBrowser 等) - 更新多个 API 和工具函数 (auth.ts/monitor/index.ts/request.ts 等) - 更新静态资源 (index.html + 新 JS/CSS bundle) - 删除废弃静态资源文件
This commit is contained in:
1 parent
5ca0e6d83a
commit
8d46e8f352
197 files changed
+5014
-815
No files matched your search
@@ -0,0 +1,160 @@
|
||||
// LogDatasourceDialog — 添加/编辑 Loki / ES 数据源 // Logan.Li
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
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 { useToast } from '@/hooks/use-toast';
|
||||
import { logCenterApi, type LogDataSource } from '@/api/monitor/log-center';
|
||||
import type { LogEngine } from './log-center-types';
|
||||
|
||||
export function LogDatasourceDialog({
|
||||
open, onOpenChange, engine, editing, onSaved,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (v: boolean) => void;
|
||||
engine: LogEngine;
|
||||
editing?: LogDataSource | null;
|
||||
onSaved: () => void;
|
||||
}) {
|
||||
const { toast } = useToast();
|
||||
const [name, setName] = useState('');
|
||||
const [url, setUrl] = useState('');
|
||||
const [defaultIndex, setDefaultIndex] = useState('logstash-*');
|
||||
const [basicAuth, setBasicAuth] = useState(false);
|
||||
const [user, setUser] = useState('');
|
||||
const [pass, setPass] = useState('');
|
||||
const [skipTLS, setSkipTLS] = useState(false);
|
||||
const [isDefault, setIsDefault] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [testing, setTesting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (editing) {
|
||||
setName(editing.name);
|
||||
setUrl(editing.url);
|
||||
setDefaultIndex(editing.default_index || 'logstash-*');
|
||||
setBasicAuth(!!editing.basic_auth);
|
||||
setUser(editing.basic_auth_user || '');
|
||||
setPass('');
|
||||
setSkipTLS(!!editing.skip_tls_verify);
|
||||
setIsDefault(!!editing.is_default);
|
||||
} else {
|
||||
setName(engine === 'loki' ? 'Loki' : 'Elasticsearch');
|
||||
setUrl(engine === 'loki' ? 'http://localhost:3100' : 'http://localhost:9200');
|
||||
setDefaultIndex('logstash-*');
|
||||
setBasicAuth(false);
|
||||
setUser('');
|
||||
setPass('');
|
||||
setSkipTLS(false);
|
||||
setIsDefault(false);
|
||||
}
|
||||
}, [open, editing, engine]);
|
||||
|
||||
const payload = () => ({
|
||||
name: name.trim(),
|
||||
type: engine,
|
||||
url: url.trim(),
|
||||
basic_auth: basicAuth,
|
||||
basic_auth_user: user,
|
||||
basic_auth_pass: pass,
|
||||
skip_tls_verify: skipTLS,
|
||||
default_index: engine === 'elasticsearch' ? defaultIndex : undefined,
|
||||
is_default: isDefault,
|
||||
});
|
||||
|
||||
const handleTest = async () => {
|
||||
setTesting(true);
|
||||
try {
|
||||
await logCenterApi.testDatasource(payload());
|
||||
toast({ title: '连接成功' });
|
||||
} catch (e: any) {
|
||||
toast({ title: '连接失败', description: e?.message || String(e), variant: 'destructive' });
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!name.trim() || !url.trim()) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
if (editing) {
|
||||
await logCenterApi.updateDatasource(editing.id, payload());
|
||||
} else {
|
||||
await logCenterApi.createDatasource(payload());
|
||||
}
|
||||
toast({ title: '已保存' });
|
||||
onOpenChange(false);
|
||||
onSaved();
|
||||
} catch (e: any) {
|
||||
toast({ title: '保存失败', description: e?.message || String(e), variant: 'destructive' });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] max-w-md max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)]">
|
||||
{editing ? '编辑' : '添加'}{engine === 'loki' ? ' Loki' : ' Elasticsearch'} 数据源
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-[var(--dg-text-muted)]">
|
||||
仅保存连接信息,日志本体存储在您的 Loki / ES 集群
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3 py-1">
|
||||
<div>
|
||||
<Label className="text-xs text-[var(--dg-text-muted)]">名称</Label>
|
||||
<Input value={name} onChange={e => setName(e.target.value)} className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs text-[var(--dg-text-muted)]">地址</Label>
|
||||
<Input value={url} onChange={e => setUrl(e.target.value)} placeholder="http://host:port" className="mt-1 h-9 font-mono text-xs bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
|
||||
</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>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs text-[var(--dg-text-muted)]">基础认证</Label>
|
||||
<Switch checked={basicAuth} onCheckedChange={setBasicAuth} />
|
||||
</div>
|
||||
{basicAuth && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Input value={user} onChange={e => setUser(e.target.value)} placeholder="用户名" className="h-9 text-xs bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
|
||||
<Input type="password" value={pass} onChange={e => setPass(e.target.value)} placeholder="密码" className="h-9 text-xs bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs text-[var(--dg-text-muted)]">跳过 TLS 校验</Label>
|
||||
<Switch checked={skipTLS} onCheckedChange={setSkipTLS} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs text-[var(--dg-text-muted)]">设为默认</Label>
|
||||
<Switch checked={isDefault} onCheckedChange={setIsDefault} />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" size="sm" onClick={handleTest} disabled={testing}>
|
||||
{testing ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : <Plug className="h-4 w-4 mr-1" />}
|
||||
测试
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave} disabled={saving}>
|
||||
{saving && <Loader2 className="h-4 w-4 animate-spin mr-1" />}
|
||||
保存
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user