refactor(frontend): api/components/pages 目录架构统一对齐菜单域 + 日志中心修复

1. 目录架构统一(API = components = 菜单域,一域一目录 + barrel)
   - log/ 提为独立顶层域(日志中心在菜单中与监控中心平级,不再塞在 monitor/)
   - task/ 拆分原 automation.ts → cron-job / script / remote-exec
   - aiops/ 合并 ai.ts + agentCli.ts → assistant + agent-cli
   - agent/ approval/ audit/ 提升为域目录
   - auth 拆分:authApi(认证自服务) + system/user.ts(userApi+ldapApi 管理端)
   - components/automation → components/task,菜单"自动化运维"→"作业任务"
   - pages/ 按域分子目录(approval/audit/monitor),删孤儿 AuditPage.tsx
   - 所有 API 符号名不变,组件经 @/api 聚合引用,运行时行为零变化

2. Loki 查询无返回修复:aiops 容器 DNS 解析外网域名失败
   (宿主机 systemd-resolved 转发链路异常),docker-compose 显式配 dns 上游

3. ES 查询框布局:工具栏单行挤 7 控件(ES 多 150px 索引框),拆两行——
   查询输入独占一行(+查询按钮),次级控件换行 flex-wrap

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
attackerandClaude committed 2026-06-28 06:06:48 +00:00
1 parent da01cbb119
commit c0ee8e4ddd
74 files changed
+229 -216

No files matched your search

+2 -2
View File
@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { User, Lock, Palette, Check, Loader2, AlertCircle, Eye, EyeOff, Languages } from 'lucide-react';
import { useUserStore, useLanguageStore } from '@/store';
import { userApi } from '@/api';
import { authApi } from '@/api';
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
import { cn } from '@/lib/utils';
import { SUPPORTED_LANGUAGES, type AppLanguage } from '@/i18n';
@@ -89,7 +89,7 @@ export function Login() {
setError('');
setLoading(true);
try {
const result = await userApi.login(username, password) as { user: any; token: string; permissions?: string[] };
const result = await authApi.login(username, password) as { user: any; token: string; permissions?: string[] };
useUserStore.getState().login(result.user, result.token, result.permissions ?? null);
localStorage.setItem('token', result.token);
navigate('/', { replace: true });
@@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { Loader2 } from 'lucide-react';
import { CodeSnippetBlock } from '@/components/ui/code-snippet-block';
import { userApi } from '@/api';
import { authApi } from '@/api';
import { getPublicApiBase } from '@/utils/request';
import { getSessionApiAccessKey } from '@/utils/storage';
@@ -37,7 +37,7 @@ export function CICDWebhookContent({
const cached = getSessionApiAccessKey();
if (cached) setAccessKey(cached);
try {
const meta = await userApi.getApiAccessKey();
const meta = await authApi.getApiAccessKey();
if (!cancelled) {
setHasKey(!!meta?.has_token);
setKeyExpired(!!meta?.expired);
@@ -5,7 +5,7 @@ import { useThemeStore, useUserStore } from '@/store';
import { Sidebar } from './Sidebar';
import { Header } from './Header';
import { Breadcrumb } from './Breadcrumb';
import { userApi } from '@/api';
import { authApi } from '@/api';
import type { JsonData } from '@/types/common';
/**
@@ -30,7 +30,7 @@ export function MainLayout() {
useEffect(() => {
if (!token || permissions !== null) return;
let cancelled = false;
userApi.getMyPermissions()
authApi.getMyPermissions()
.then((data: JsonData) => {
if (cancelled) return;
const perms: string[] = data?.permissions ?? [];
+1 -1
View File
@@ -136,7 +136,7 @@ const menuItems: MenuItem[] = [
],
},
{
id: 'automation', name: '自动化运维', labelKey: 'menu.automation', icon: PlayCircle, permission: 'auto:cron',
id: 'automation', name: '作业任务', labelKey: 'menu.automation', icon: PlayCircle, permission: 'auto:cron',
children: [
{ id: 'cronjobs', name: '定时任务', labelKey: 'menu.cronJobs', icon: PlayCircle, path: '/cronjobs', permission: 'auto:cron' },
{ id: 'scripts', name: '脚本库', labelKey: 'menu.scripts', icon: FileCode, path: '/scripts', permission: 'auto:script' },
@@ -13,8 +13,7 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { Loader2 } from 'lucide-react';
import { alertApi } from '@/api';
import { logCenterApi, type LogDataSource } from '@/api/monitor/log-center';
import { alertApi, logCenterApi, type LogDataSource } from '@/api';
import { parseChannels, getChannelIcon, getChannelTypeLabel } from './alertShared';
import type { JsonData } from '@/types/common';
@@ -12,7 +12,7 @@ import {
} from '@/components/ui/select';
import { Loader2, Plug, RefreshCw } from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
import { logCenterApi, type LogDataSource, type TestConnectionResult } from '@/api/monitor/log-center';
import { logCenterApi, type LogDataSource, type TestConnectionResult } from '@/api';
import type { LogEngine } from './log-center-types';
export function LogDatasourceDialog({
@@ -8,7 +8,7 @@ import {
} from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { logCenterApi, type LogDataSource, type TestConnectionResult } from '@/api/monitor/log-center';
import { logCenterApi, type LogDataSource, type TestConnectionResult } from '@/api';
import { LogDatasourceDialog } from './LogDatasourceDialog';
import type { LogEngine } from './log-center-types';
@@ -11,7 +11,7 @@ import { LogClusterPanel } from './LogClusterPanel';
import { SaveQueryDialog, SaveAlertDialog } from './SaveQueryDialog';
import { QueryHistoryPanel } from './QueryHistoryPanel';
import { LogAlertFromQueryDialog } from './LogAlertFromQueryDialog';
import { logCenterApi, type LogDataSource } from '@/api/monitor/log-center';
import { logCenterApi, type LogDataSource } from '@/api';
import type { LogEngine, TimeRange, ResultTab, ViewMode, LogEntry, StatTable, LogCluster, QueryHistoryItem } from './types-v3';
import { ENGINE_META, buildStatTableFromEntries, buildClustersFromEntries, extractFields, generateHistogramData, historyStorage } from './types-v3';
import { useToast } from '@/hooks/use-toast';
@@ -280,8 +280,13 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
<span className="text-[11px] font-semibold" style={{ color: badge.dotColor }}>{badge.label}</span>
</div>
<SyntaxHighlightInput value={query} onChange={setQuery} engine={effEngine} placeholder={meta.queryPlaceholder} />
<button onClick={handleRun} disabled={loading || !currentSource} className="h-10 px-4 rounded text-xs font-medium text-white flex items-center gap-1.5 flex-shrink-0 transition-opacity hover:opacity-90" style={{ background: '#22c55e' }}>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Play className="h-4 w-4" />} 查询
</button>
</div>
</div>
{/* 次级控件行(时间 / 数据源 / 索引 / 上限 / 另存为 / 历史)// Logan.Li */}
<div className="flex items-center gap-2 flex-wrap">
<TimeRangePicker value={timeRange} onChange={setTimeRange} />
<select value={dsId} onChange={e => setDsId(e.target.value)} className="h-10 px-2 text-xs rounded flex-shrink-0" style={{ background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)' }}>
@@ -316,10 +321,6 @@ export function UnifiedQueryPanel({ engine, onRuleCreated }: Props) {
/>
</div>
<button onClick={handleRun} disabled={loading || !currentSource} className="h-10 px-4 rounded text-xs font-medium text-white flex items-center gap-1.5 flex-shrink-0 transition-opacity hover:opacity-90" style={{ background: '#22c55e' }}>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Play className="h-4 w-4" />} 查询
</button>
<div className="relative flex-shrink-0">
<button onClick={() => setSaveMenuOpen(!saveMenuOpen)} className="h-10 px-3 rounded text-xs flex items-center gap-1" style={{ border: '1px solid var(--dg-border)', color: 'var(--dg-text-secondary)', background: 'var(--dg-bg-tertiary)' }}>
<Save className="h-3.5 w-3.5" /> 另存为 <ChevronDown className="h-3 w-3" />
@@ -14,7 +14,7 @@ import {
} from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { userApi } from '@/api';
import { authApi } from '@/api';
import { getPublicApiBase } from '@/utils/request';
import { getSessionApiAccessKey, setSessionApiAccessKey } from '@/utils/storage';
import { cn } from '@/lib/utils';
@@ -93,7 +93,7 @@ export function ApiAccessKeySection() {
try {
const cached = getSessionApiAccessKey();
if (cached) setPlainToken(cached);
const meta = await userApi.getApiAccessKey().catch(() => ({ has_token: false }));
const meta = await authApi.getApiAccessKey().catch(() => ({ has_token: false }));
setKeyMeta(meta);
} finally {
setLoading(false);
@@ -130,7 +130,7 @@ export function ApiAccessKeySection() {
if (formMode === 'edit') {
setActionLoading(true);
try {
const res = await userApi.updateApiAccessKey({
const res = await authApi.updateApiAccessKey({
description: formDescription,
expires_at: formExpiresAt,
});
@@ -156,7 +156,7 @@ export function ApiAccessKeySection() {
setActionLoading(true);
try {
const res = await userApi.regenerateApiAccessKey({
const res = await authApi.regenerateApiAccessKey({
description: formDescription,
expires_at: formExpiresAt,
});
@@ -195,7 +195,7 @@ export function ApiAccessKeySection() {
if (!ok) return;
setActionLoading(true);
try {
await userApi.revokeApiAccessKey();
await authApi.revokeApiAccessKey();
setPlainToken('');
setSessionApiAccessKey('');
setKeyMeta({ has_token: false });
+3 -3
View File
@@ -7,7 +7,7 @@ import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { User, Shield, Loader2 } from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
import { userApi } from '@/api';
import { authApi } from '@/api';
import { useUserStore } from '@/store';
import { ApiAccessKeySection } from './ApiAccessKeySection';
@@ -25,7 +25,7 @@ export default function ProfilePage() {
const load = useCallback(async () => {
setLoading(true);
try {
const p = await userApi.getProfile();
const p = await authApi.getProfile();
setProfile(p);
} finally {
setLoading(false);
@@ -41,7 +41,7 @@ export default function ProfilePage() {
if (!oldPwd || newPwd.length < 6) return;
setPwdSaving(true);
try {
await userApi.changePassword({ old_password: oldPwd, new_password: newPwd });
await authApi.changePassword({ old_password: oldPwd, new_password: newPwd });
toast({ title: '密码已修改' });
setOldPwd('');
setNewPwd('');