feat(rdp): 新增 RDP 远程桌面/SMB/账单同步能力 + 清理编译产物
主要变更: - RDP 远程桌面:新增 backend/pkg/rdp(RDP 引擎 + credssp/display/rle/trace)、 backend/controller/rdp*(连接/凭证/文件/会话/实时)、frontend RDP 组件与 API - Windows SMB 文件传输:backend/pkg/winsmb - 云账单同步:backend/pkg/billingsync + billing_sync/aliyun_billing,前端账单详情 - Agent CLI Kubernetes 工具:kubernetes_action/get、pod_execute/logs - 服务器导入助手:server_import_helpers、ImportCredentialSettings - 国际化与组件重构(cloud/deploy/monitor/server/system 等多模块) - 清理编译产物二进制:移除 backend/server、backend/agent/bin/aiops-agent-* - .gitignore:新增 backend/agent/ 与 .cache/(避免 334MB go-build 缓存误入库) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
777d9e4e71
commit
76ec9387f0
202 files changed
+16555
-2543
No files matched your search
@@ -1,6 +1,7 @@
|
||||
// 终端审计 — 在线会话子面板 // Logan.Li
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
@@ -17,7 +18,10 @@ import {
|
||||
import { Search, RefreshCw, XOctagon, Eye, Activity, Timer, TimerOff } from 'lucide-react';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { LiveMonitorDialog } from '@/components/system/LiveMonitorDialog';
|
||||
import { RdpAuditViewer } from '@/components/system/RdpAuditViewer';
|
||||
import { useTerminalSessions, useTerminalSessionMutations } from '@/hooks/api';
|
||||
import { rdpSessionApi } from '@/api';
|
||||
import { unwrapList } from '@/hooks/api/unwrap';
|
||||
import type { TerminalSession } from './types';
|
||||
import { formatDuration, getStatusBadge } from './helpers';
|
||||
|
||||
@@ -105,6 +109,44 @@ function ActiveSessionRow({
|
||||
);
|
||||
}
|
||||
|
||||
function RdpActiveSessionRow({
|
||||
session,
|
||||
onKill,
|
||||
onMonitor,
|
||||
}: {
|
||||
session: TerminalSession;
|
||||
onKill: (id: number, username: string) => void;
|
||||
onMonitor: (id: number) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const elapsed = useElapsedTimer(session.start_time);
|
||||
|
||||
return (
|
||||
<TableRow className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]">
|
||||
<TableCell><span className="text-sm dg-text-secondary">{session.id}</span></TableCell>
|
||||
<TableCell><span className="text-sm text-[var(--dg-text-primary)]">{session.username || '--'}</span></TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm text-[var(--dg-text-primary)]">{session.server_name || '--'}</span>
|
||||
<span className="text-xs dg-text-muted">{session.server_ip}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell><span className="text-sm dg-text-secondary">{session.start_time ? new Date(session.start_time).toLocaleString() : '--'}</span></TableCell>
|
||||
<TableCell><span className="text-sm dg-text-green font-mono">{formatDuration(elapsed)}</span></TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-secondary hover:dg-text-red" onClick={() => onKill(session.id, session.username)} title={t('audit.terminal.killTitle')}>
|
||||
<XOctagon className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-secondary hover:dg-text-blue" onClick={() => onMonitor(session.id)} title="RDP 实时审计">
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export function ActiveSessionsPanel({ userToggledOff, onUserToggledOff }: ActiveSessionsPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
const confirm = useConfirm();
|
||||
@@ -114,12 +156,22 @@ export function ActiveSessionsPanel({ userToggledOff, onUserToggledOff }: Active
|
||||
page_size: 100,
|
||||
status: 'active',
|
||||
});
|
||||
const rdpQuery = useQuery({
|
||||
queryKey: ['rdp-sessions', 'active'],
|
||||
queryFn: async () => {
|
||||
const resp = await rdpSessionApi.list({ page: 1, page_size: 100, status: 'active' });
|
||||
return unwrapList<TerminalSession>(resp);
|
||||
},
|
||||
});
|
||||
const sessions = data?.list ?? [];
|
||||
const rdpSessions = rdpQuery.data ?? [];
|
||||
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [autoRefresh, setAutoRefresh] = useState(() => !userToggledOff);
|
||||
const [monitorOpen, setMonitorOpen] = useState(false);
|
||||
const [monitorSessionId, setMonitorSessionId] = useState<number | null>(null);
|
||||
const [rdpOpen, setRdpOpen] = useState(false);
|
||||
const [rdpSessionId, setRdpSessionId] = useState<number | null>(null);
|
||||
|
||||
// 用户未手动关闭时,回到在线会话自动开启刷新
|
||||
useEffect(() => {
|
||||
@@ -128,9 +180,9 @@ export function ActiveSessionsPanel({ userToggledOff, onUserToggledOff }: Active
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoRefresh) return;
|
||||
const id = setInterval(() => refetch(), REFRESH_SEC * 1000);
|
||||
const id = setInterval(() => { refetch(); rdpQuery.refetch(); }, REFRESH_SEC * 1000);
|
||||
return () => clearInterval(id);
|
||||
}, [autoRefresh, refetch]);
|
||||
}, [autoRefresh, refetch, rdpQuery]);
|
||||
|
||||
const filtered = sessions.filter((s) => {
|
||||
const kw = keyword.toLowerCase();
|
||||
@@ -162,6 +214,23 @@ export function ActiveSessionsPanel({ userToggledOff, onUserToggledOff }: Active
|
||||
setMonitorOpen(true);
|
||||
};
|
||||
|
||||
const openRdpMonitor = (sessionId: number) => {
|
||||
setRdpSessionId(sessionId);
|
||||
setRdpOpen(true);
|
||||
};
|
||||
|
||||
const killRdp = async (id: number, username: string) => {
|
||||
const ok = await confirm({
|
||||
title: t('audit.terminal.killTitle'),
|
||||
description: t('audit.terminal.killConfirm', { user: username }),
|
||||
confirmText: t('audit.terminal.killConfirmBtn'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
if (!ok) return;
|
||||
await rdpSessionApi.kill(id).catch(() => undefined);
|
||||
rdpQuery.refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
@@ -247,7 +316,40 @@ export function ActiveSessionsPanel({ userToggledOff, onUserToggledOff }: Active
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-0">
|
||||
<div className="px-4 py-2 border-b border-[var(--dg-border)] text-xs font-semibold dg-text-secondary">RDP 在线会话</div>
|
||||
{rdpQuery.isLoading ? (
|
||||
<div className="flex items-center justify-center py-10"><div className="w-7 h-7 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" /></div>
|
||||
) : rdpSessions.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-10 dg-text-muted">
|
||||
<Activity className="h-7 w-7 mb-2 opacity-40" />
|
||||
<p className="text-sm">暂无 RDP 在线会话</p>
|
||||
</div>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
|
||||
<TableHead className="dg-text-secondary">{t('audit.terminal.col.id')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('audit.terminal.col.user')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('audit.terminal.col.targetServer')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('audit.terminal.col.startTime')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('audit.terminal.col.duration')}</TableHead>
|
||||
<TableHead className="dg-text-secondary text-right">{t('audit.terminal.col.actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{rdpSessions.map((s) => (
|
||||
<RdpActiveSessionRow key={`rdp-${s.id}`} session={s} onKill={killRdp} onMonitor={openRdpMonitor} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<LiveMonitorDialog open={monitorOpen} onOpenChange={setMonitorOpen} sessionId={monitorSessionId} />
|
||||
<RdpAuditViewer open={rdpOpen} onOpenChange={setRdpOpen} sessionId={rdpSessionId} mode="live" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user