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:
LoganandClaude committed 2026-07-04 01:50:36 +08:00
1 parent 777d9e4e71
commit 76ec9387f0
202 files changed
+16555 -2543

No files matched your search

@@ -1,5 +1,4 @@
import { useRef, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Dialog,
DialogContent,
@@ -11,8 +10,10 @@ import { FitAddon } from 'xterm-addon-fit';
import 'xterm/css/xterm.css';
import { Maximize2, Minimize2, X, FolderOpen } from 'lucide-react';
import { cn } from '@/lib/utils';
import { attachTerminalResize, mountXterm } from '@/lib/xterm-utils';
import { useUserStore } from '@/store';
import { pipelineApi } from '@/api';
import { TerminalLoadingOverlay } from '@/components/common/TerminalLoadingOverlay';
import type { JsonData } from '@/types/common';
interface WorkspaceTermDialogProps {
@@ -31,7 +32,6 @@ export function WorkspaceTermDialog({
runId,
buildNumber,
}: WorkspaceTermDialogProps) {
const { t } = useTranslation();
const containerRef = useRef<HTMLDivElement>(null);
const termRef = useRef<XTerm | null>(null);
const wsRef = useRef<WebSocket | null>(null);
@@ -39,10 +39,12 @@ export function WorkspaceTermDialog({
const [connected, setConnected] = useState(false);
const [fullscreen, setFullscreen] = useState(false);
const [workspace, setWorkspace] = useState('');
const [hasOutput, setHasOutput] = useState(false);
// 获取 workspace 路径
useEffect(() => {
if (!open || !runId) return;
setHasOutput(false);
pipelineApi.getWorkspace(runId)
.then((data: JsonData) => setWorkspace(data?.workspace_path || ''))
.catch(() => {});
@@ -85,30 +87,29 @@ export function WorkspaceTermDialog({
try {
const msg = JSON.parse(event.data);
if (msg.status === 0 && typeof msg.data === 'string') {
setHasOutput(true);
term.write(msg.data);
}
} catch {
setHasOutput(true);
term.write(typeof event.data === 'string' ? event.data : '');
}
};
ws.onopen = () => {
setConnected(true);
if (containerRef.current) {
term.open(containerRef.current);
requestAnimationFrame(() => {
try { fit.fit(); term.focus(); } catch { /* noop */ }
});
}
mountXterm(term, fit, () => containerRef.current, ws);
};
ws.onclose = () => {
setConnected(false);
term.write(`\r\n\x1b[31m${t('deploy.runDetail.connectionClosed')}\x1b[0m\r\n`);
setHasOutput(true);
term.write('\r\n\x1b[31m[连接已关闭]\x1b[0m\r\n');
};
ws.onerror = () => {
term.write(`\r\n\x1b[31m${t('deploy.runDetail.connectionError')}\x1b[0m\r\n`);
setHasOutput(true);
term.write('\r\n\x1b[31m[连接发生错误]\x1b[0m\r\n');
};
return () => {
@@ -119,31 +120,24 @@ export function WorkspaceTermDialog({
};
}, [open, runId]);
// ResizeObserver for fit
// 容器尺寸变化时 fit 并同步远端 PTY,避免缩放后黑边或铺不满。 // Logan.Li
useEffect(() => {
if (!open) return;
const container = containerRef.current;
if (!container) return;
let timer: ReturnType<typeof setTimeout>;
const ro = new ResizeObserver(() => {
clearTimeout(timer);
timer = setTimeout(() => {
if (fitRef.current && termRef.current) {
try {
fitRef.current.fit();
termRef.current.focus();
if (wsRef.current?.readyState === WebSocket.OPEN) {
const { cols, rows } = termRef.current;
wsRef.current.send(JSON.stringify({ status: 1, cols, rows }));
}
} catch { /* noop */ }
}
}, 60);
});
ro.observe(container);
return () => { clearTimeout(timer); ro.disconnect(); };
}, [open]);
let cleanup: (() => void) | undefined;
let retry = 0;
const attach = () => {
const container = containerRef.current;
const fit = fitRef.current;
const term = termRef.current;
if (!container || !fit || !term) {
if (retry++ < 30) requestAnimationFrame(attach);
return;
}
cleanup = attachTerminalResize(container, fit, term, wsRef.current);
};
attach();
return () => cleanup?.();
}, [open, fullscreen]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -160,14 +154,14 @@ export function WorkspaceTermDialog({
)}
style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '900px', width: '90vw' }}
>
<DialogTitle className="sr-only">{t('deploy.pipeline.workspaceTerminal', { buildNumber })}</DialogTitle>
<DialogDescription className="sr-only">{t('deploy.pipeline.buildDir')}</DialogDescription>
<DialogTitle className="sr-only">Workspace 终端 #{buildNumber}</DialogTitle>
<DialogDescription className="sr-only">流水线构建目录终端</DialogDescription>
<div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]">
<div className="flex items-center gap-2 text-sm min-w-0">
<div className={cn('w-2 h-2 rounded-full shrink-0', connected ? 'bg-emerald-400' : 'bg-red-400')} />
<FolderOpen className="h-4 w-4 text-[var(--dg-text-secondary)] shrink-0" />
<span className="text-[var(--dg-text-primary)] font-medium truncate">
#{buildNumber} {t('deploy.runDetail.buildDir')}
#{buildNumber} 构建目录
</span>
{workspace && (
<span className="text-[var(--dg-text-muted)] text-xs truncate hidden sm:inline">
@@ -190,11 +184,10 @@ export function WorkspaceTermDialog({
</button>
</div>
</div>
<div
ref={containerRef}
className="bg-[#0a0e14]"
style={{ height: fullscreen ? 'calc(100vh - 49px)' : '480px' }}
/>
<div className="relative bg-[#0a0e14]" style={{ height: fullscreen ? 'calc(100vh - 49px)' : '480px' }}>
<div ref={containerRef} className="xterm-terminal-host h-full" />
<TerminalLoadingOverlay show={!hasOutput} label="正在进入工作区..." />
</div>
</DialogContent>
</Dialog>
);