diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddf56e4..0692513 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,37 +1,45 @@ -# Flexmodel CI/CD Pipeline -# Runs tests for all modules, then builds and pushes Docker images. -# Deploys to production on push to main. +# Flexmodel UI CI +# 可手动触发选择:只跑测试、只构建镜像、或两者都跑。 +# push / PR 默认只跑测试(lint + 类型检查)。 +# 镜像版本号取自 VERSION 文件:${VERSION}、${VERSION}.${run_number} 等 -name: Run Tests +name: Flexmodel UI on: push: branches: [ "main" ] pull_request: branches: [ "main" ] + workflow_dispatch: + inputs: + run_tests: + description: "执行测试(lint + 类型检查)" + type: boolean + default: true + run_build: + description: "执行构建并推送 Docker 镜像" + type: boolean + default: false jobs: - - push-ui-image: - name: Push UI docker image + ui-test: + name: UI tests (lint + type-check) runs-on: ubuntu-latest + if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.run_tests == 'true' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: 'recursive' token: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build and push UI image - uses: docker/build-push-action@v6 + - name: Set up Node + uses: actions/setup-node@v5 with: - context: . - push: true - tags: cjbi/flexmodel-ui:latest - cache-from: type=gha - cache-to: type=gha,mode=max + node-version: '22' + cache: 'npm' + cache-dependency-path: 'package-lock.json' + - name: Install dependencies + run: npm ci + - name: Lint + run: npm run lint + - name: Type check + run: npx tsc --noEmit diff --git a/eslint.config.js b/eslint.config.js index 278df1a..1d1a2d8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -23,7 +23,12 @@ export default tseslint.config( 'warn', { allowConstantExport: true }, ], - "@typescript-eslint/no-explicit-any": ["off"] + "@typescript-eslint/no-explicit-any": ["off"], + "@typescript-eslint/no-unused-vars": ["error", { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "ignoreRestSiblings": true + }] }, }, ) diff --git a/src/components/console/Console.tsx b/src/components/console/Console.tsx index 0bee328..4d1b902 100644 --- a/src/components/console/Console.tsx +++ b/src/components/console/Console.tsx @@ -31,7 +31,7 @@ const Console: React.FC = ({ onToggle, projectId }) => { const [searchKeyword, setSearchKeyword] = useState(''); const [debouncedSearchKeyword, setDebouncedSearchKeyword] = useState(''); const [fontSize, setFontSize] = useState(12); - const [displayLimit, setDisplayLimit] = useState(100); // 默认显示100条 + const [displayLimit, setDisplayLimit] = useState(100); // 默认显示100? // 使用WebSocket Hook const { @@ -44,35 +44,35 @@ const Console: React.FC = ({ onToggle, projectId }) => { logsEndRef, setAutoScrollEnabled } = useConsoleLogs({ - maxLogs: 1000, // 减少到1000条以提高性能 + maxLogs: 1000, // 减少?000条以提高性能 autoScroll: true, projectId, }); - // 始终置底开关:选中后不管是否滚动,始终保持在底部 + // 始终置底开关:选中后不管是否滚动,始终保持在底? const [stayAtBottom, setStayAtBottom] = useState(false); - // 选中“始终置底”时,确保自动滚动始终开启 + // 选中“始终置底”时,确保自动滚动始终开? useEffect(() => { if (stayAtBottom) { setAutoScrollEnabled(true); } }, [stayAtBottom, setAutoScrollEnabled]); - // 根据滚动位置更新自动滚动开关 + // 根据滚动位置更新自动滚动开? const handleContentScroll = useCallback((e: React.UIEvent) => { if (stayAtBottom) { - // 强制保持在底部 + // 强制保持在底? setAutoScrollEnabled(true); if (logsEndRef.current) { - // 使用同步滚动,避免视觉抖动 + // 使用同步滚动,避免视觉抖? logsEndRef.current.scrollIntoView({ behavior: 'auto' }); } return; } const target = e.currentTarget; - const threshold = 16; // 离底部阈值 + const threshold = 16; // 离底部阈? const distanceToBottom = target.scrollHeight - target.scrollTop - target.clientHeight; const isNearBottom = distanceToBottom <= threshold; setAutoScrollEnabled(isNearBottom); @@ -92,7 +92,7 @@ const Console: React.FC = ({ onToggle, projectId }) => { } }, [isConnected, reconnect, t]); - // 手动滚动到底部 + // 手动滚动到底? const scrollToBottom = useCallback(() => { if (logsEndRef.current) { logsEndRef.current.scrollIntoView({ behavior: 'smooth' }); @@ -108,7 +108,7 @@ const Console: React.FC = ({ onToggle, projectId }) => { return () => clearTimeout(timer); }, [searchKeyword]); - // 过滤日志 - 只按关键词过滤 + // 过滤日志 - 只按关键词过? const filteredLogs = useMemo(() => { if (!debouncedSearchKeyword) { if (displayLimit === -1 || logs.length <= displayLimit) return logs; @@ -116,7 +116,7 @@ const Console: React.FC = ({ onToggle, projectId }) => { } const keyword = debouncedSearchKeyword.toLowerCase(); - let result = logs.filter(log => + const result = logs.filter(log => log.message.toLowerCase().includes(keyword) ); @@ -129,17 +129,17 @@ const Console: React.FC = ({ onToggle, projectId }) => { useEffect(() => { if (import.meta.env.DEV) { console.log('Console组件 - 当前日志数量:', logs.length); - console.log('Console组件 - 连接状态:', isConnected); - console.log('Console组件 - 过滤后日志数量:', filteredLogs.length); + console.log('Console组件 - 连接状?', isConnected); + console.log('Console组件 - 过滤后日志数?', filteredLogs.length); // 性能警告 if (logs.length > 400) { - console.warn(`Console日志数量较多 (${logs.length}条),可能影响性能`); + console.warn(`Console日志数量较多 (${logs.length}?,可能影响性能`); } } }, [logs, isConnected, filteredLogs]); - // 当过滤条件或显示限制变化时,滚动到底部 + // 当过滤条件或显示限制变化时,滚动到底? useEffect(() => { if (filteredLogs.length > 0 && logsEndRef.current) { // 使用setTimeout确保DOM更新后再滚动 @@ -151,7 +151,7 @@ const Console: React.FC = ({ onToggle, projectId }) => { } }, [filteredLogs.length, displayLimit, debouncedSearchKeyword, logsEndRef]); - // 打开时滚动到底部(交由父组件控制可见性,这里仅在有日志时尝试) + // 打开时滚动到底部(交由父组件控制可见性,这里仅在有日志时尝试? useEffect(() => { if (filteredLogs.length > 0) { setTimeout(() => { @@ -169,7 +169,7 @@ const Console: React.FC = ({ onToggle, projectId }) => { borderTop: `1px solid ${token.colorBorder}`, background: token.colorBgContainer }}> - {/* 控制栏 */} + {/* 控制?*/}
{ @@ -12,6 +13,8 @@ const PlatformSidebar: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const { isSidebarCollapsed, toggleSidebar } = useSidebar(); + const {config} = useConfig(); + const version = config.version || ''; const [openKeys, setOpenKeys] = useState(() => { const pathname = location.pathname.replace(/\/$/, ''); const initialOpenKeys: string[] = []; @@ -128,9 +131,15 @@ const PlatformSidebar: React.FC = () => {
+ {!isSidebarCollapsed && ( + + {version} + + )}