diff --git a/spectre-frontend/package.json b/spectre-frontend/package.json index 93812ad..114f7b2 100644 --- a/spectre-frontend/package.json +++ b/spectre-frontend/package.json @@ -19,6 +19,7 @@ "echarts": "^6.0.0", "framer-motion": "^12.23.9", "i18next": "^25.7.3", + "idb": "^8.0.3", "react": "~19.1.2", "react-dom": "~19.1.2", "react-hook-form": "^7.61.1", diff --git a/spectre-frontend/pnpm-lock.yaml b/spectre-frontend/pnpm-lock.yaml index 1a46b5f..f3443d6 100644 --- a/spectre-frontend/pnpm-lock.yaml +++ b/spectre-frontend/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: i18next: specifier: ^25.7.3 version: 25.7.3(typescript@5.9.3) + idb: + specifier: ^8.0.3 + version: 8.0.3 react: specifier: ~19.1.2 version: 19.1.2 @@ -3220,6 +3223,9 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + idb@8.0.3: + resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -8478,6 +8484,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + idb@8.0.3: {} + ieee754@1.2.1: {} ignore@5.3.2: {} diff --git a/spectre-frontend/src/api/impl/arthas.ts b/spectre-frontend/src/api/impl/arthas.ts index 4c2afde..89ec3aa 100644 --- a/spectre-frontend/src/api/impl/arthas.ts +++ b/spectre-frontend/src/api/impl/arthas.ts @@ -43,42 +43,19 @@ export const joinChannel = ( export type InputStatusResponse = { type: 'input_status' - fid: number inputStatus: 'ALLOW_INPUT' | 'DISABLED' | 'ALLOW_INTERRUPT' jobId: number } -type ArthasResponse = { +export type PureArthasResponse = { type: string jobId: number } -export type ArthasResponseWithId = ArthasResponse & { - /** - * arthas 实际没用这个值,这个是给前端识别用的 - */ - fid: number -} -let fid = Date.now() - -function applyIdForArthasResponse(r: unknown): Promise { - const resp = r as ArthasResponse[] - const result: ArthasResponseWithId[] = [] - for (const arthasRespons of resp) { - result.push({ - ...arthasRespons, - fid: fid++, - }) - } - return Promise.resolve(result) -} - export const pullResults = async ( channelId: string, -): Promise => - axios - .get(`arthas/channel/${channelId}/pull-result`) - .then(applyIdForArthasResponse) +): Promise => + axios.get(`arthas/channel/${channelId}/pull-result`) export const executeArthasCommand = (channelId: string, command: string) => { return axios.post(`arthas/channel/${channelId}/execute`, { @@ -89,12 +66,10 @@ export const executeArthasCommand = (channelId: string, command: string) => { export const executeArthasCommandSync = ( channelId: string, command: string, -): Promise => { - return axios - .post(`arthas/channel/${channelId}/execute-sync`, { - command, - }) - .then(applyIdForArthasResponse) +): Promise => { + return axios.post(`arthas/channel/${channelId}/execute-sync`, { + command, + }) } export const disconnectSession = (channelId: string) => { diff --git a/spectre-frontend/src/components/icon/icon.ts b/spectre-frontend/src/components/icon/icon.ts index 7f3b275..5d27fee 100644 --- a/spectre-frontend/src/components/icon/icon.ts +++ b/spectre-frontend/src/components/icon/icon.ts @@ -28,6 +28,7 @@ const Icon = { RIGHT_ARROW: 'fc-angle-right', BOOKMARK: 'fc-bookmark', LOCK: 'fc-lock', + EXTERNAL: 'fc-arrow-up-right-from-square', } as const export type Icons = (typeof Icon)[keyof typeof Icon] diff --git a/spectre-frontend/src/components/icon/svg-symbols.tsx b/spectre-frontend/src/components/icon/svg-symbols.tsx index 4997af1..714a86e 100644 --- a/spectre-frontend/src/components/icon/svg-symbols.tsx +++ b/spectre-frontend/src/components/icon/svg-symbols.tsx @@ -337,6 +337,17 @@ const SvgSymbols: React.FC = () => { d="M256 160L256 224L384 224L384 160C384 124.7 355.3 96 320 96C284.7 96 256 124.7 256 160zM192 224L192 160C192 89.3 249.3 32 320 32C390.7 32 448 89.3 448 160L448 224C483.3 224 512 252.7 512 288L512 512C512 547.3 483.3 576 448 576L192 576C156.7 576 128 547.3 128 512L128 288C128 252.7 156.7 224 192 224z" /> + + {/**/} + + , document.body, diff --git a/spectre-frontend/src/pages/channel/[channelId]/ChannelLayout.tsx b/spectre-frontend/src/pages/channel/[channelId]/ChannelLayout.tsx index a089e5d..a04cd48 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/ChannelLayout.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/ChannelLayout.tsx @@ -10,6 +10,7 @@ import ChannelContext, { } from '@/pages/channel/[channelId]/context.ts' import type { QuickCommandRef } from '@/pages/channel/[channelId]/_component/QuickCommand' import useArthasMessageBus from '@/pages/channel/[channelId]/useArthasMessageBus.tsx' +import './_message_view/init.ts' interface ChannelLayoutProps { channelId: string @@ -17,10 +18,13 @@ interface ChannelLayoutProps { } const ChannelLayout: React.FC = (props) => { - const bus = useArthasMessageBus() + const bus = useArthasMessageBus(props.channelId) const tabsController = useRef(null) const quickCommandRef = useRef(null) - const contextValue = useMemo(() => { + const contextValue = useMemo(() => { + if (!bus) { + return null + } return { messageBus: bus, getTabsController() { @@ -32,6 +36,9 @@ const ChannelLayout: React.FC = (props) => { } }, [bus]) + if (!contextValue) { + return + } return ( <> diff --git a/spectre-frontend/src/pages/channel/[channelId]/_channel_icons/ChannelIcon.ts b/spectre-frontend/src/pages/channel/[channelId]/_channel_icons/ChannelIcon.ts index a128273..fd8a0ee 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_channel_icons/ChannelIcon.ts +++ b/spectre-frontend/src/pages/channel/[channelId]/_channel_icons/ChannelIcon.ts @@ -6,6 +6,7 @@ const ChannelIcon = { DASHBOARD: 'fc-chart-line', FIELD: 'jb-field', ARRAY: 'jb-db-array', + ALIGN_LEFT: 'fc-align-left', } export default ChannelIcon diff --git a/spectre-frontend/src/pages/channel/[channelId]/_channel_icons/svg-symbols.tsx b/spectre-frontend/src/pages/channel/[channelId]/_channel_icons/svg-symbols.tsx index efb461f..b60097c 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_channel_icons/svg-symbols.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_channel_icons/svg-symbols.tsx @@ -147,6 +147,14 @@ const ChannelSvgSymbols: React.FC = () => { fill="#6C707E" /> + + {/**/} + + , document.body, diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/ArthasResponseDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/ArthasResponseDetail.tsx similarity index 71% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/ArthasResponseDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/ArthasResponseDetail.tsx index 3cae535..1c846f0 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/ArthasResponseDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/ArthasResponseDetail.tsx @@ -1,19 +1,22 @@ -import type { ArthasResponseWithId } from '@/api/impl/arthas.ts' +import type { PureArthasResponse } from '@/api/impl/arthas.ts' import React, { useCallback, useMemo, useRef, useState } from 'react' import { type DetailComponentProps, getArthasMessageView } from './factory.ts' +import type { ArthasMessage } from '@/pages/channel/[channelId]/db.ts' interface ArthasResponseDetailProps { - message: ArthasResponseWithId + message: ArthasMessage } const ArthasResponseDetail: React.FC = (props) => { const Component = useMemo(() => { - return getArthasMessageView(props.message.type)?.detailComponent - }, [props.message.type]) + return getArthasMessageView(props.message.value.type)?.detailComponent + }, [props.message.value.type]) as React.FC< + DetailComponentProps + > const componentCache = useRef(new Map()) - const [dirtyIds, setDirtyIds] = useState(new Set()) + const [dirtyIds, setDirtyIds] = useState(new Set()) const handleOnDirty = useCallback( - (id: number) => { + (id: string) => { if (!dirtyIds.has(id)) { setDirtyIds((prev) => new Set(prev).add(id)) } @@ -23,8 +26,8 @@ const ArthasResponseDetail: React.FC = (props) => { const renderDetail = useCallback( ( - id: number, - Component: React.FC>, + id: string, + Component: React.FC>, ) => { // 1. 如果已经在缓存池里,直接返回缓存的实例 if (componentCache.current.has(id)) { @@ -34,8 +37,8 @@ const ArthasResponseDetail: React.FC = (props) => { // 2. 如果不在缓存中,创建一个新的实例 const newComponent = ( handleOnDirty(id)} /> ) @@ -54,15 +57,15 @@ const ArthasResponseDetail: React.FC = (props) => { ) } if ( - dirtyIds.has(props.message.fid) && - !componentCache.current.has(props.message.fid) + dirtyIds.has(props.message.id) && + !componentCache.current.has(props.message.id) ) { componentCache.current.set( - props.message.fid, - renderDetail(props.message.fid, Component), + props.message.id, + renderDetail(props.message.id, Component), ) } - const currentId = props.message.fid + const currentId = props.message.id return ( <> {/* 策略:对于脏组件,我们全部渲染但在 CSS 上隐藏;对于非脏组件,动态切换 */} diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/ArthasResponsePreview.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/ArthasResponsePreview.tsx similarity index 83% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/ArthasResponsePreview.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/ArthasResponsePreview.tsx index 195c58e..526815c 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/ArthasResponsePreview.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/ArthasResponsePreview.tsx @@ -1,19 +1,21 @@ -import type { ArthasResponseWithId } from '@/api/impl/arthas.ts' import React, { useMemo } from 'react' import { getArthasMessageView, type PreviewInfo } from './factory.ts' import clsx from 'clsx' import { Tooltip } from '@heroui/react' +import type { ArthasMessage } from '@/pages/channel/[channelId]/db.ts' interface ArthasResponsePreviewProps { - message: ArthasResponseWithId + message: ArthasMessage } const ArthasResponsePreview: React.FC = (props) => { const state: PreviewInfo = useMemo(() => { return ( - getArthasMessageView(props.message.type)?.display(props.message) ?? { + getArthasMessageView(props.message.value.type)?.display( + props.message.value, + ) ?? { name: '', color: 'default', - tag: props.message.type, + tag: props.message.value.type, } ) }, [props.message]) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/ClassloaderMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/ClassloaderMessageDetail.tsx similarity index 94% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/ClassloaderMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/ClassloaderMessageDetail.tsx index 6b41f4a..fa01f4d 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/ClassloaderMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/ClassloaderMessageDetail.tsx @@ -1,5 +1,5 @@ import type React from 'react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' import { Table, TableBody, @@ -19,7 +19,6 @@ type Classloader = { export type ClassLoaderMessage = { type: 'classloader' jobId: number - fid: number classLoaderStats?: Record< string, { diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/CommandMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/CommandMessageDetail.tsx similarity index 84% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/CommandMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/CommandMessageDetail.tsx index 73cf245..1e14612 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/CommandMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/CommandMessageDetail.tsx @@ -1,6 +1,6 @@ import type React from 'react' import { Code } from '@heroui/react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' export type CommandMessage = { type: 'command' @@ -8,7 +8,6 @@ export type CommandMessage = { state: string command: string message?: string - fid: number } const CommandMessageDetail: React.FC> = ( diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/DashboardMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/DashboardMessageDetail.tsx similarity index 99% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/DashboardMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/DashboardMessageDetail.tsx index 9889f83..148b85e 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/DashboardMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/DashboardMessageDetail.tsx @@ -50,7 +50,6 @@ export type DashboardMessage = { runtimeInfo: RuntimeInfo threads: Thread[] type: 'dashboard' - fid: number } export const DashboardMessageDetail: React.FC = () => { diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/EnhancerMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/EnhancerMessageDetail.tsx similarity index 85% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/EnhancerMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/EnhancerMessageDetail.tsx index abdb71d..3eb61f5 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/EnhancerMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/EnhancerMessageDetail.tsx @@ -1,5 +1,5 @@ import { Code } from '@heroui/react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' type EnhancerMessage = { effect: { @@ -10,7 +10,6 @@ type EnhancerMessage = { } jobId: number success: boolean - fid: number type: 'enhancer' } diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/JadMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/JadMessageDetail.tsx similarity index 90% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/JadMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/JadMessageDetail.tsx index 43ab472..c237a57 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/JadMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/JadMessageDetail.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useContext } from 'react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' import Code from '@/components/Code.tsx' import { Link } from '@heroui/react' import ChannelContext from '@/pages/channel/[channelId]/context.ts' @@ -15,7 +15,6 @@ export type JadMessage = { mappings: Record source: string type: 'jad' - fid: number } const JadMessageDetail: React.FC> = ( diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/MemoryMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/MemoryMessageDetail.tsx similarity index 91% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/MemoryMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/MemoryMessageDetail.tsx index d318d33..2858a10 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/MemoryMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/MemoryMessageDetail.tsx @@ -7,7 +7,7 @@ import { TableRow, } from '@heroui/react' import React, { useMemo } from 'react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' import PercentageData from '@/pages/channel/[channelId]/_tabs/_dashboard/PercentageData.tsx' type MemoryInfo = { @@ -21,19 +21,17 @@ type MemoryMessage = { jobId: number memoryInfo: Record type: 'memory' - fid: number } const MemoryMessageDetail: React.FC> = ({ msg, }) => { const infos = useMemo(() => { const infos: MemoryInfo[] = [] - console.log(msg.memoryInfo) for (const entry of Object.entries(msg.memoryInfo)) { infos.push(...entry[1]) } return infos - }, [msg.fid]) + }, [msg.memoryInfo]) return ( diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/MessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/MessageDetail.tsx similarity index 76% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/MessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/MessageDetail.tsx index 652c3fa..e8d6d68 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/MessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/MessageDetail.tsx @@ -1,12 +1,11 @@ import type React from 'react' import { Code } from '@heroui/react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' type MessageResponse = { type: 'message' jobId: number message: string - fid: number } const MessageDetail: React.FC> = ( diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/OgnlCommonMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/OgnlCommonMessageDetail.tsx similarity index 64% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/OgnlCommonMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/OgnlCommonMessageDetail.tsx index ea43b2a..3a8ff85 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/OgnlCommonMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/OgnlCommonMessageDetail.tsx @@ -1,5 +1,5 @@ import type React from 'react' -import OgnlMessageView from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/_ognl_result/OgnlMessageView.tsx' +import OgnlMessageView from '../_component/_ognl_result/OgnlMessageView.tsx' const OgnlCommonMessageDetail: React.FC<{ raw: string }> = ({ raw }) => { return ( diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/ScMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/ScMessageDetail.tsx similarity index 97% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/ScMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/ScMessageDetail.tsx index 9086c59..86df8ad 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/ScMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/ScMessageDetail.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' import { Card, CardBody, @@ -49,7 +49,6 @@ type ClassInfo = { synthetic: boolean } type ScMessage = { - fid: number withField: boolean type: 'sc' segment: number diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/StackMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/StackMessageDetail.tsx similarity index 99% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/StackMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/StackMessageDetail.tsx index cae7264..398d765 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/StackMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/StackMessageDetail.tsx @@ -27,7 +27,6 @@ type Trace = { } type StackMessage = { - fid: number type: 'stack' threadId: number threadName: string diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/StatusMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/StatusMessageDetail.tsx similarity index 84% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/StatusMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/StatusMessageDetail.tsx index 88f636b..e98e402 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/StatusMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/StatusMessageDetail.tsx @@ -1,13 +1,12 @@ import type React from 'react' import { Code } from '@heroui/react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' type StatusMessage = { type: 'status' statusCode: number jobId: number message?: string - fid: number } const StatusMessageDetail: React.FC> = ({ diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/TraceMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/TraceMessageDetail.tsx similarity index 98% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/TraceMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/TraceMessageDetail.tsx index 9338e55..fa5ebba 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/TraceMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/TraceMessageDetail.tsx @@ -2,7 +2,7 @@ import React, { useContext, useMemo, useRef } from 'react' import clsx from 'clsx' import { formatTime } from '@/common/util.ts' import { ListboxItem, Tooltip } from '@heroui/react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' import RightClickMenu from '@/components/RightClickMenu/RightClickMenu.tsx' import useRightClickMenu from '@/components/RightClickMenu/useRightClickMenu.ts' import ChannelContext from '@/pages/channel/[channelId]/context.ts' @@ -36,7 +36,6 @@ type TraceMessage = { jobId: number nodeCount: number root: Root - fid: number } function toKey(invoke: Invoke): string { diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/WelcomeMessageDetail.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/WelcomeMessageDetail.tsx similarity index 88% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/WelcomeMessageDetail.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/WelcomeMessageDetail.tsx index 9a54372..ff6c5e5 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/WelcomeMessageDetail.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/WelcomeMessageDetail.tsx @@ -1,6 +1,6 @@ import { Code, Link } from '@heroui/react' import type React from 'react' -import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import type { DetailComponentProps } from '../factory.ts' type WelcomeMessage = { type: 'welcome' @@ -11,7 +11,6 @@ type WelcomeMessage = { tutorials: string version: string wiki: string - fid: number } const WelcomeMessageDetail: React.FC> = ( diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/_ognl_result/OgnlMessageView.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/_ognl_result/OgnlMessageView.tsx similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/_ognl_result/OgnlMessageView.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/_ognl_result/OgnlMessageView.tsx diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/_ognl_result/parser.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/_ognl_result/parser.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_component/_ognl_result/parser.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_component/_ognl_result/parser.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/classloader.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/classloader.ts similarity index 72% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/classloader.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/classloader.ts index 9fcea4e..3f9f870 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/classloader.ts +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/classloader.ts @@ -1,4 +1,4 @@ -import { registerMessageView } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' +import { registerMessageView } from '../factory.ts' import ClassloaderMessageDetail from '../_component/ClassloaderMessageDetail' registerMessageView({ diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/command.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/command.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/command.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/command.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/dashboard.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/dashboard.ts new file mode 100644 index 0000000..71265dd --- /dev/null +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/dashboard.ts @@ -0,0 +1,12 @@ +import { registerMessageView } from '../factory.ts' +import DashboardMessageDetail from '../_component/DashboardMessageDetail.tsx' + +registerMessageView({ + detailComponent: DashboardMessageDetail, + type: 'dashboard', + display: (_) => ({ + name: '服务状态', + color: 'default', + tag: 'Dashboard', + }), +}) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/enhancer.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/enhancer.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/enhancer.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/enhancer.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/jad.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/jad.ts new file mode 100644 index 0000000..12ae06c --- /dev/null +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/jad.ts @@ -0,0 +1,12 @@ +import { registerMessageView } from '../factory.ts' +import JadMessageDetail from '../_component/JadMessageDetail.tsx' + +registerMessageView({ + detailComponent: JadMessageDetail, + type: 'jad', + display: (message) => ({ + name: message.classInfo.name, + tag: 'jad', + color: 'default', + }), +}) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/jvm.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/jvm.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/jvm.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/jvm.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/memory.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/memory.ts new file mode 100644 index 0000000..f433eba --- /dev/null +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/memory.ts @@ -0,0 +1,12 @@ +import { registerMessageView } from '../factory.ts' +import MemoryMessageDetail from '../_component/MemoryMessageDetail.tsx' + +registerMessageView({ + detailComponent: MemoryMessageDetail, + display: (_) => ({ + color: 'default', + name: '查看内存信息', + tag: 'memory', + }), + type: 'memory', +}) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/message.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/message.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/message.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/message.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/ognl-common.tsx b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/ognl-common.tsx similarity index 92% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/ognl-common.tsx rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/ognl-common.tsx index de9fcab..cde06ad 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/ognl-common.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/ognl-common.tsx @@ -1,9 +1,9 @@ import { type DetailComponentProps, registerMessageView } from '../factory.ts' import OgnlCommonMessageDetail from '../_component/OgnlCommonMessageDetail.tsx' -import type { ArthasResponseWithId } from '@/api/impl/arthas.ts' import React from 'react' +import type { PureArthasResponse } from '@/api/impl/arthas.ts' -function createComponent( +function createComponent( ognlResultGetter: (r: T) => string, ): React.FC> { const Component: React.FC> = (props) => { diff --git a/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/sc.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/sc.ts new file mode 100644 index 0000000..222797d --- /dev/null +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/sc.ts @@ -0,0 +1,12 @@ +import { registerMessageView } from '../factory.ts' +import ScMessageDetail from '../_component/ScMessageDetail.tsx' + +registerMessageView({ + detailComponent: ScMessageDetail, + type: 'sc', + display: (_) => ({ + tag: 'sc', + name: '查找类', + color: 'default', + }), +}) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/stack.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/stack.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/stack.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/stack.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/status.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/status.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/status.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/status.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/trace.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/trace.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/trace.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/trace.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/uncomplete.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/uncomplete.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/uncomplete.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/uncomplete.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/welcome.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/welcome.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/welcome.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/_register/welcome.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/factory.ts similarity index 52% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/factory.ts index a1160f1..6653ed1 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts +++ b/spectre-frontend/src/pages/channel/[channelId]/_message_view/factory.ts @@ -1,5 +1,5 @@ import type { ChipProps } from '@heroui/react' -import type { ArthasResponseWithId } from '@/api/impl/arthas.ts' +import type { PureArthasResponse } from '@/api/impl/arthas.ts' import type React from 'react' export type PreviewInfo = { @@ -8,7 +8,7 @@ export type PreviewInfo = { tag: string } -export interface DetailComponentProps { +export interface DetailComponentProps { msg: T /** * 表示当前视图已经“脏”了,需要进行持久化,以免用户下次进入时丢失相关数据 @@ -16,25 +16,22 @@ export interface DetailComponentProps { onDirty?: () => void } -export type RegisterConfiguration = { +export type RegisterConfiguration = { type: string detailComponent?: React.FC> display: (message: T) => PreviewInfo } -const configMap: Record< - string, - RegisterConfiguration -> = {} +const configMap: Record> = {} -export function registerMessageView( +export function registerMessageView( conf: RegisterConfiguration, ) { - configMap[conf.type] = conf as RegisterConfiguration + configMap[conf.type] = conf as RegisterConfiguration } export function getArthasMessageView( type: string, -): RegisterConfiguration | undefined { +): RegisterConfiguration | undefined { return configMap[type] } diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/init.ts b/spectre-frontend/src/pages/channel/[channelId]/_message_view/init.ts similarity index 100% rename from spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/init.ts rename to spectre-frontend/src/pages/channel/[channelId]/_message_view/init.ts diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/TabsController.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/TabsController.tsx index 5f2d73e..aedf3ce 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/TabsController.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/TabsController.tsx @@ -20,8 +20,7 @@ import ChannelIcon from '@/pages/channel/[channelId]/_channel_icons/ChannelIcon. type TabInfo = { node: React.ReactNode - icon?: string - id: number | string + id: React.Key } & TabOptions type OpenTabFuncArgs = TabArgs[K] extends undefined @@ -43,7 +42,7 @@ interface TabsControllerProps { } const TabsController: React.FC = (props) => { - const [activeTabId, setActiveTabId] = useState(0) + const [activeTabId, setActiveTabId] = useState(0) const { menuProps, onContextMenu } = useRightClickMenu() const currentHoverTab = useRef(undefined) const [tabs, setTabs] = useState(() => { @@ -84,7 +83,7 @@ const TabsController: React.FC = (props) => { { ...options, node, - icon: holder.icon, + icon: holder.icon ?? options.icon, id, }, ] @@ -93,12 +92,12 @@ const TabsController: React.FC = (props) => { }, })) - const switchTab = (id: number | string) => { + const switchTab = (id: React.Key) => { setActiveTabId(id) } const closeCurrentTab = ( - id: number | string, + id: React.Key, e?: React.MouseEvent, ) => { e?.stopPropagation() diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/ArthasResponseDetailTab.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/ArthasResponseDetailTab.tsx index f5203be..e57dee9 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/ArthasResponseDetailTab.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/ArthasResponseDetailTab.tsx @@ -1,15 +1,20 @@ -import React from 'react' -import type { ArthasResponseWithId } from '@/api/impl/arthas.ts' -import { Tab, Tabs } from '@heroui/react' -import ArthasResponseDetail from './_message_view/ArthasResponseDetail.tsx' +import React, { useContext } from 'react' +import { Tab, Tabs, Tooltip } from '@heroui/react' +import ArthasResponseDetail from '@/pages/channel/[channelId]/_message_view/ArthasResponseDetail.tsx' +import SvgIcon from '@/components/icon/SvgIcon.tsx' +import Icon from '@/components/icon/icon.ts' +import ChannelContext from '@/pages/channel/[channelId]/context.ts' +import type { ArthasMessage } from '@/pages/channel/[channelId]/db.ts' +import ChannelIcon from '@/pages/channel/[channelId]/_channel_icons/ChannelIcon.ts' interface ArthasResponseDetailProps { - entity?: ArthasResponseWithId + entity?: ArthasMessage } const ArthasResponseDetailTab: React.FC = ({ entity, }) => { + const context = useContext(ChannelContext) if (!entity) { return (
@@ -20,13 +25,28 @@ const ArthasResponseDetailTab: React.FC = ({
) } + const openInNewTab = () => { + context + .getTabsController() + .openTab( + 'MESSAGE_DETAIL', + { name: entity.context.command, icon: ChannelIcon.ALIGN_LEFT }, + { msg: entity }, + ) + } return ( -
+
@@ -37,6 +57,15 @@ const ArthasResponseDetailTab: React.FC = ({
+
+ + + +
) } diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/ArthasResponseListTab.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/ArthasResponseListTab.tsx index d6c810c..8549004 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/ArthasResponseListTab.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/ArthasResponseListTab.tsx @@ -5,30 +5,27 @@ import React, { useRef, useState, } from 'react' -import type { ArthasResponseWithId } from '@/api/impl/arthas.ts' import { useSelector } from 'react-redux' import { type RootState, store } from '@/store' import ArthasResponseItem, { type ResponseGroupItem, } from '@/pages/channel/[channelId]/_tabs/_console/_component/ArthasResponseItem.tsx' import ChannelContext from '@/pages/channel/[channelId]/context.ts' +import type { ArthasMessage } from '@/pages/channel/[channelId]/db.ts' +import type { ArthasMessageBus } from '@/pages/channel/[channelId]/useArthasMessageBus.tsx' interface ArthasResponseListProps { - onEntitySelect: (e: ArthasResponseWithId) => void + onEntitySelect: (e: ArthasMessage) => void } const IGNORED_TYPES = new Set(['input_status']) -function buildArray0() { +function buildArray0(bus: ArthasMessageBus) { const channelSlice = store.getState().channel const isDebugMode = channelSlice.context.isDebugMode - return buildArray( - channelSlice.messages[channelSlice.context.channelId] ?? [], - undefined, - isDebugMode, - ) + return buildArray(bus.messages, undefined, isDebugMode) } function buildArray( - messages: ArthasResponseWithId[], + messages: ArthasMessage[], previousMessage?: ResponseGroupItem, isDebugMode?: boolean, ): ResponseGroupItem[] { @@ -39,19 +36,22 @@ function buildArray( })) } else { result = [] - let lastJobId = previousMessage?.entity.jobId ?? -1 + let lastJobId = previousMessage?.entity.value.jobId ?? -1 let groupColorFlag = previousMessage?.groupInfo?.colorFlag ?? -1 - let lastMsgType = previousMessage ? previousMessage.entity.type : '' + let lastMsgType = previousMessage ? previousMessage.entity.value.type : '' for (const entity of messages) { - if (IGNORED_TYPES.has(entity.type)) { + if (IGNORED_TYPES.has(entity.value.type)) { continue } // dashboard 仅显示第一条 - if ('dashboard' === entity.type && lastMsgType === entity.type) { + if ( + 'dashboard' === entity.value.type && + lastMsgType === entity.value.type + ) { continue } - lastMsgType = entity.type - if (entity.jobId === lastJobId) { + lastMsgType = entity.value.type + if (entity.value.jobId === lastJobId) { result.push({ entity, groupInfo: { @@ -70,22 +70,22 @@ function buildArray( }, }) } - lastJobId = entity.jobId + lastJobId = entity.value.jobId } } return result } - const ArthasResponseListTab: React.FC = (props) => { const isDebugMode = useSelector( (state) => state.channel.context.isDebugMode, ) ?? false - const [filteredResponses, setFilteredResponse] = - useState(buildArray0) + const context = useContext(ChannelContext) + const [filteredResponses, setFilteredResponse] = useState< + ResponseGroupItem[] + >(() => buildArray0(context.messageBus)) const scrollRef = useRef(null) const [selectedEntityIndex, setSelectedEntityIndex] = useState(-1) - const context = useContext(ChannelContext) useEffect(() => { const id = context.messageBus.addListener({ @@ -110,7 +110,7 @@ const ArthasResponseListTab: React.FC = (props) => { }, [context.messageBus]) useEffect(() => { - setFilteredResponse(buildArray0()) + setFilteredResponse(buildArray0(context.messageBus)) }, [isDebugMode]) useLayoutEffect(() => { @@ -119,7 +119,6 @@ const ArthasResponseListTab: React.FC = (props) => { const isAtBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 100 - console.log(isAtBottom) if (isAtBottom) { container.scrollTo({ top: container.scrollHeight }) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_component/ArthasResponseItem.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_component/ArthasResponseItem.tsx index 456e283..ec8e75e 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_component/ArthasResponseItem.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_component/ArthasResponseItem.tsx @@ -1,11 +1,11 @@ -import type { ArthasResponseWithId } from '@/api/impl/arthas.ts' import clsx from 'clsx' import React, { useMemo } from 'react' import './listStyle.css' -import ArthasResponsePreview from '@/pages/channel/[channelId]/_tabs/_console/_message_view/ArthasResponsePreview.tsx' +import ArthasResponsePreview from '@/pages/channel/[channelId]/_message_view/ArthasResponsePreview.tsx' +import type { ArthasMessage } from '@/pages/channel/[channelId]/db.ts' export type ResponseGroupItem = { - entity: ArthasResponseWithId + entity: ArthasMessage groupInfo?: { colorFlag: number } @@ -59,7 +59,7 @@ const ArthasResponseItem: React.FC = ({ return (
({ - name: '服务状态', - color: 'default', - tag: 'Dashboard', - }), -}) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/jad.ts b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/jad.ts deleted file mode 100644 index 1b9c6d4..0000000 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/jad.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { registerMessageView } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' -import JadMessageDetail from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/JadMessageDetail.tsx' - -registerMessageView({ - detailComponent: JadMessageDetail, - type: 'jad', - display: (message) => ({ - name: message.classInfo.name, - tag: 'jad', - color: 'default', - }), -}) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/memory.ts b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/memory.ts deleted file mode 100644 index 256d2a9..0000000 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/memory.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { registerMessageView } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' -import MemoryMessageDetail from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/MemoryMessageDetail.tsx' - -registerMessageView({ - detailComponent: MemoryMessageDetail, - display: (_) => ({ - color: 'default', - name: '查看内存信息', - tag: 'memory', - }), - type: 'memory', -}) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/sc.ts b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/sc.ts deleted file mode 100644 index d7e0fce..0000000 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/_message_view/_register/sc.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { registerMessageView } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts' -import ScMessageDetail from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/ScMessageDetail.tsx' - -registerMessageView({ - detailComponent: ScMessageDetail, - type: 'sc', - display: (_) => ({ - tag: 'sc', - name: '查找类', - color: 'default', - }), -}) diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/index.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/index.tsx index 8a33ab2..4193ee4 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/index.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_console/index.tsx @@ -1,15 +1,13 @@ import React, { useState } from 'react' -import { type ArthasResponseWithId } from '@/api/impl/arthas.ts' import { Divider } from '@heroui/react' import ArthasResponseDetailTab from './ArthasResponseDetailTab.tsx' import CommandExecuteBlock from './CommandExecuteBlock.tsx' import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels' import ArthasResponseListTab from './ArthasResponseListTab.tsx' - -import './_message_view/init.ts' +import type { ArthasMessage } from '@/pages/channel/[channelId]/db.ts' const ConsoleTab: React.FC = () => { - const [selectedEntity, setSelectedEntity] = useState() + const [selectedEntity, setSelectedEntity] = useState() return (
diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/MemoryChart.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/MemoryChart.tsx index c01e09b..01fe6da 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/MemoryChart.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/MemoryChart.tsx @@ -16,15 +16,16 @@ import { import { LineChart, type LineSeriesOption } from 'echarts/charts' import { CanvasRenderer } from 'echarts/renderers' import { UniversalTransition } from 'echarts/features' -import type { - DashboardMessage, - MemoryInfo, -} from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/DashboardMessageDetail.tsx' + import { formatTime } from '@/common/util.ts' import { Card, CardBody } from '@heroui/react' import KVGird from '@/components/KVGird' import KVGridItem from '@/components/KVGird/KVGridItem.tsx' import PercentageData from '@/pages/channel/[channelId]/_tabs/_dashboard/PercentageData.tsx' +import type { + DashboardMessage, + MemoryInfo, +} from '@/pages/channel/[channelId]/_message_view/_component/DashboardMessageDetail.tsx' type EChartsOption = echarts.ComposeOption< | TitleComponentOption diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/ThreadTable.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/ThreadTable.tsx index 18ed1b0..d7251e2 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/ThreadTable.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/ThreadTable.tsx @@ -1,4 +1,3 @@ -import type { DashboardMessage } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/DashboardMessageDetail.tsx' import { Table, TableBody, @@ -9,6 +8,7 @@ import { } from '@heroui/react' import PercentageData from '@/pages/channel/[channelId]/_tabs/_dashboard/PercentageData.tsx' import React from 'react' +import type { DashboardMessage } from '@/pages/channel/[channelId]/_message_view/_component/DashboardMessageDetail.tsx' interface ThreadTableProps { lastMessage: DashboardMessage diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/index.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/index.tsx index a4bd3b4..e57952e 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/index.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_dashboard/index.tsx @@ -1,14 +1,14 @@ import React, { useContext, useEffect, useState } from 'react' import ChannelContext from '@/pages/channel/[channelId]/context.ts' -import type { DashboardMessage } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/DashboardMessageDetail.tsx' import { Button, Card, CardBody } from '@heroui/react' import KVGird from '@/components/KVGird' import KVGridItem from '@/components/KVGird/KVGridItem.tsx' -import type { CommandMessage } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/CommandMessageDetail.tsx' import MemoryChart from '@/pages/channel/[channelId]/_tabs/_dashboard/MemoryChart.tsx' import ThreadTable from '@/pages/channel/[channelId]/_tabs/_dashboard/ThreadTable.tsx' import PercentageData from '@/pages/channel/[channelId]/_tabs/_dashboard/PercentageData.tsx' import clsx from 'clsx' +import type { DashboardMessage } from '@/pages/channel/[channelId]/_message_view/_component/DashboardMessageDetail.tsx' +import type { CommandMessage } from '../../_message_view/_component/CommandMessageDetail' const DashBoardTab: React.FC = () => { const context = useContext(ChannelContext) @@ -19,18 +19,18 @@ const DashBoardTab: React.FC = () => { useEffect(() => { const id = context.messageBus.addListener({ - onMessage(msg) { + onMessage: function (msg) { const current = msg[msg.length - 1] if ( - current.type === 'command' && - (current as CommandMessage).command === 'dashboard' + current.value.type === 'command' && + (current.value as CommandMessage).command === 'dashboard' ) { return - } else if (current.type !== 'dashboard') { + } else if (current.value.type !== 'dashboard') { setStopped(true) return } else { - const dashboardMessage = current as DashboardMessage + const dashboardMessage = current.value as DashboardMessage setState(dashboardMessage) return } diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_jad/index.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_jad/index.tsx index 484561a..17024f8 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_jad/index.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_jad/index.tsx @@ -3,7 +3,7 @@ import { executeArthasCommandSync } from '@/api/impl/arthas.ts' import { store } from '@/store' import { addToast } from '@heroui/react' import Code from '@/components/Code.tsx' -import type { JadMessage } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/JadMessageDetail.tsx' +import type { JadMessage } from '../../_message_view/_component/JadMessageDetail' export interface JadPageProps { /** diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/_message_detail/index.tsx b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_message_detail/index.tsx new file mode 100644 index 0000000..5e23e62 --- /dev/null +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/_message_detail/index.tsx @@ -0,0 +1,16 @@ +import ArthasResponseDetail from '@/pages/channel/[channelId]/_message_view/ArthasResponseDetail.tsx' +import type { ArthasMessage } from '@/pages/channel/[channelId]/db.ts' + +export interface MessageDetailPageProps { + msg: ArthasMessage +} + +const MessageDetailPage: React.FC = (props) => { + return ( +
+ +
+ ) +} + +export default MessageDetailPage diff --git a/spectre-frontend/src/pages/channel/[channelId]/_tabs/tab-constant.ts b/spectre-frontend/src/pages/channel/[channelId]/_tabs/tab-constant.ts index 9a85045..baec4bc 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/_tabs/tab-constant.ts +++ b/spectre-frontend/src/pages/channel/[channelId]/_tabs/tab-constant.ts @@ -5,10 +5,14 @@ import type React from 'react' import ChannelIcon from '@/pages/channel/[channelId]/_channel_icons/ChannelIcon.ts' import type { TabOptions } from '@/pages/channel/[channelId]/context.ts' import DashBoardTab from '@/pages/channel/[channelId]/_tabs/_dashboard' +import MessageDetailPage, { + type MessageDetailPageProps, +} from '@/pages/channel/[channelId]/_tabs/_message_detail' export interface TabArgs { JAD: JadPageProps DASHBOARD: undefined + MESSAGE_DETAIL: MessageDetailPageProps } type ComponentHolder = { @@ -38,8 +42,16 @@ const DASHBOARD: ComponentHolder = { }), } +const MESSAGE_DETAIL: ComponentHolder = { + Component: MessageDetailPage, + defaultPropsFactory: (props) => ({ + uniqueId: props.msg.id, + }), +} + // eslint-disable-next-line @typescript-eslint/no-explicit-any export const TabComponents: Record> = { JAD, DASHBOARD, + MESSAGE_DETAIL, } diff --git a/spectre-frontend/src/pages/channel/[channelId]/context.ts b/spectre-frontend/src/pages/channel/[channelId]/context.ts index b891a3f..a8481f1 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/context.ts +++ b/spectre-frontend/src/pages/channel/[channelId]/context.ts @@ -6,10 +6,11 @@ import type { ArthasMessageBus } from '@/pages/channel/[channelId]/useArthasMess export type TabOptions = { name?: string isLocked?: boolean + icon?: string /** * 如果该标签页是唯一的,则需要提供该值,当用户重复打开时,将会跳转而不是开启一个新的 */ - uniqueId?: string + uniqueId?: React.Key hoverMessage?: string } diff --git a/spectre-frontend/src/pages/channel/[channelId]/db.ts b/spectre-frontend/src/pages/channel/[channelId]/db.ts new file mode 100644 index 0000000..503f8ff --- /dev/null +++ b/spectre-frontend/src/pages/channel/[channelId]/db.ts @@ -0,0 +1,255 @@ +import { type DBSchema, type IDBPDatabase, openDB } from 'idb' +import type { PureArthasResponse } from '@/api/impl/arthas.ts' + +const VERSION = 1 + +type ContextValue = { + command?: string + channelId: string +} +type ContextValueWithPK = ContextValue & { id: string } + +type MessageValue = { + channelId: string + contextId: string + /** + * Arthas 消息,JSON格式 + */ + value: PureArthasResponse +} + +type MessageValueWithId = MessageValue & { id: string } + +type ChannelInfoValue = { + lastAccess: number +} + +interface ArthasMessageDB extends DBSchema { + messages: { + key: string + value: MessageValue + indexes: { 'by-channel-id': string } + } + context: { + key: string + value: ContextValue + indexes: { 'by-channel-id': string } + } + channelInfo: { + /** + * channel id + */ + key: string + value: ChannelInfoValue + } +} + +export type ArthasMessage = { + id: string + context: ContextValue + value: T +} +/** + * 1h + */ +const MAX_ALIVE = 1000 * 60 * 60 + +async function setupDB() { + let contextId = Date.now() + let messageId = Date.now() + const db = await openDB('ArthasMessage', VERSION, { + upgrade(database: IDBPDatabase) { + const messagesStore = database.createObjectStore('messages') + messagesStore.createIndex('by-channel-id', 'channelId') + const contextStore = database.createObjectStore('context') + contextStore.createIndex('by-channel-id', 'channelId') + database.createObjectStore('channelInfo') + }, + }) + + async function findAllContextByIdIn( + ids: string[], + ): Promise> { + const scopes = await Promise.all(ids.map((id) => db.get('context', id))) + const result: Record = {} + + for (let i = 0; i < scopes.length; i++) { + const context = scopes[i] + if (!context) { + continue + } + result[ids[i]] = { + ...context, + id: ids[i], + } + } + return result + } + + async function doClear(channelId: string) { + console.log(`Cleaning channel messages of "${channelId}"`) + + const messageTx = db.transaction('messages', 'readwrite') + const messageIndex = messageTx + .objectStore('messages') + .index('by-channel-id') + + const contextIds = new Set() + for ( + let cursor = await messageIndex.openCursor( + IDBKeyRange.only(channelId), + 'prev', + ); + cursor; + cursor = await cursor.continue() + ) { + contextIds.add(cursor.value.contextId) + cursor.delete() + } + const contextTx = db.transaction('context', 'readwrite') + const contextStore = contextTx.objectStore('context') + for (const contextId1 of contextIds) { + await contextStore.delete(contextId1) + } + + await db.delete('channelInfo', channelId) + await contextTx.done + await messageTx.done + } + + return { + async clearUnusedMessages() { + const tx = db.transaction('channelInfo') + const store = tx.objectStore('channelInfo') + + for ( + let cursor = await store.openCursor(); + cursor; + cursor = await cursor.continue() + ) { + if (Date.now() - cursor.value.lastAccess >= MAX_ALIVE) { + await doClear(cursor.primaryKey) + } + } + }, + /** + * 找到对应类型的最后一条消息 + */ + async findLastMessage( + channelId: string, + messageType: string, + ): Promise { + const tx = db.transaction('messages') + const store = tx.objectStore('messages') + const index = store.index('by-channel-id') + + for ( + let cursor = await index.openCursor( + IDBKeyRange.only(channelId), + 'prev', + ); + cursor; + cursor = await cursor.continue() + ) { + if (cursor.value.value.type === messageType) { + return { + id: cursor.primaryKey, + value: cursor.value.value, + context: (await findAllContextByIdIn([cursor.value.contextId]))[0], + } + } + } + }, + async listAllMessages( + channelId: string, + size: number, + ): Promise { + const tx = db.transaction('messages') + const store = tx.objectStore('messages') + const index = store.index('by-channel-id') + const messages: MessageValueWithId[] = [] + const scopeIds = new Set() + for ( + let cursor = await index.openCursor(IDBKeyRange.only(channelId)); + cursor; + cursor = await cursor.continue() + ) { + if (cursor.value.channelId !== channelId) { + continue + } + if (messages.length > size) { + break + } + messages.push({ + id: cursor.primaryKey, + ...cursor.value, + }) + scopeIds.add(cursor.value.contextId) + } + const contextMap = await findAllContextByIdIn([...scopeIds]) + const result: ArthasMessage[] = messages.map( + (msg) => ({ + id: msg.id, + context: contextMap[msg.contextId], + value: msg.value, + }), + ) + await db.put( + 'channelInfo', + { + lastAccess: Date.now(), + }, + channelId, + ) + await tx.done + return result + }, + async createNewContext(context: ContextValue): Promise { + const myId = (++contextId).toString() + await db.put('context', context, myId) + return myId + }, + async findLastContextId(channelId: string) { + const tx = db.transaction('context') + const store = tx.objectStore('context') + const index = store.index('by-channel-id') + + const cursor = await index.openCursor(IDBKeyRange.only(channelId), 'prev') + return cursor?.primaryKey + }, + async insertAllMessages( + messages: MessageValue[], + ): Promise { + const contextIds = new Set() + const ids: string[] = [] + const updateAccessIds = new Set() + for (const message of messages) { + const myId = (++messageId).toString() + db.put('messages', message, myId) + ids.push(myId) + contextIds.add(message.contextId) + updateAccessIds.add(message.channelId) + } + for (const updateAccessId of updateAccessIds) { + await db.put( + 'channelInfo', + { + lastAccess: Date.now(), + }, + updateAccessId, + ) + } + const contextMap = await findAllContextByIdIn([...contextIds]) + return messages.map((message, index) => ({ + id: ids[index], + context: contextMap[message.contextId], + value: message.value, + })) + }, + close() { + db.close() + }, + } +} + +export default setupDB diff --git a/spectre-frontend/src/pages/channel/[channelId]/index.tsx b/spectre-frontend/src/pages/channel/[channelId]/index.tsx index 084f05c..1fb0a53 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/index.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/index.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useState } from 'react' import { useParams } from 'react-router' import { type ChannelSessionDTO, joinChannel } from '@/api/impl/arthas.ts' import { useDispatch } from 'react-redux' -import { setupChannelContext } from '@/store/channelSlice.ts' import ChannelLayout from '@/pages/channel/[channelId]/ChannelLayout.tsx' const ChannelPage: React.FC = () => { @@ -19,11 +18,6 @@ const ChannelPage: React.FC = () => { .then((session) => { if (session) { setSession(session) - dispatch( - setupChannelContext({ - channelId, - }), - ) setLoading(false) } else { setTimeout(() => { diff --git a/spectre-frontend/src/pages/channel/[channelId]/useArthasMessageBus.tsx b/spectre-frontend/src/pages/channel/[channelId]/useArthasMessageBus.tsx index 9ec0f51..981ba6b 100644 --- a/spectre-frontend/src/pages/channel/[channelId]/useArthasMessageBus.tsx +++ b/spectre-frontend/src/pages/channel/[channelId]/useArthasMessageBus.tsx @@ -1,22 +1,19 @@ import { - type ArthasResponseWithId, executeArthasCommand, type InputStatusResponse, interruptCommand, pullResults, } from '@/api/impl/arthas.ts' -import { useEffect, useMemo } from 'react' -import { - appendMessages, - clearExpiredMessages, - updateInputStatus, -} from '@/store/channelSlice.ts' +import { useEffect, useState } from 'react' +import { setupChannelContext, updateInputStatus } from '@/store/channelSlice.ts' import { store } from '@/store' import { useDispatch } from 'react-redux' import type { Dispatch } from '@reduxjs/toolkit' +import setupDB, { type ArthasMessage } from '@/pages/channel/[channelId]/db.ts' +import type { CommandMessage } from '@/pages/channel/[channelId]/_message_view/_component/CommandMessageDetail.tsx' interface Listener { - onMessage?: (messages: ArthasResponseWithId[]) => void + onMessage?: (messages: ArthasMessage[]) => void afterExecute?: (command: string, fail: boolean) => void } @@ -37,6 +34,10 @@ export type ArthasMessageBus = { * @param interruptCurrent 是否中止当前正在执行的命令 */ execute(command: string, interruptCurrent?: boolean): Promise + /** + * 当前 channel 上的消息. 不一定是全部消息 + */ + messages: ArthasMessage[] } let globalId = Date.now() @@ -51,40 +52,92 @@ type PollState = { type ArthasMessageBusInternal = { launchPullResultTask: () => void pullNow: () => void - state: PollState + close: () => void } & ArthasMessageBus const classloaderHashRegx = /-c +[\da-zA-Z]{8}/ +const INPUT_STATUS = 'input_status' +const MAX_BUS_MESSAGE_SIZE = 100 +/** + * 达到最大值后,清理至这么多消息 + */ +const BUS_MESSAGE_THRESHOLD = 90 -const createArthasMessageBusInternal = ( +const createArthasMessageBusInternal = async ( + channelId: string, dispatch: Dispatch, -): ArthasMessageBusInternal => { +): Promise => { const listenerMap = new Map() + const db = await setupDB() + let currentContextId = + (await db.findLastContextId(channelId)) ?? + (await db.createNewContext({ + channelId, + })) + const messages = await setupMessages() const state: PollState = { taskDelay: 0, isFetching: false, isExcited: false, } + async function setupMessages() { + const messages = await db.listAllMessages(channelId, MAX_BUS_MESSAGE_SIZE) + if (messages.length === 0) { + dispatch( + setupChannelContext({ + channelId, + inputStatus: 'DISABLED', + }), + ) + return messages + } + const status = await db.findLastMessage(channelId, INPUT_STATUS) + dispatch( + setupChannelContext({ + channelId, + inputStatus: status + ? (status.value as InputStatusResponse).inputStatus + : 'ALLOW_INPUT', + }), + ) + return messages + } + const doPullResults = async (): Promise => { const channelId = store.getState().channel.context.channelId const r = await pullResults(channelId) for (const resp of r) { - if (resp.type === 'input_status') { - const status = (resp as InputStatusResponse).inputStatus - dispatch(updateInputStatus(status)) + switch (resp.type) { + case INPUT_STATUS: { + const status = (resp as InputStatusResponse).inputStatus + dispatch(updateInputStatus(status)) + break + } + case 'command': { + const command = resp as CommandMessage + currentContextId = await db.createNewContext({ + command: command.command, + channelId, + }) + } } } if (r.length > 0) { - for (const entry of listenerMap.entries()) { - entry[1].onMessage?.(r) - } - dispatch( - appendMessages({ - messages: r, + const dbMsg = await db.insertAllMessages( + r.map((msg) => ({ + value: msg, channelId, - }), + contextId: currentContextId, + })), ) + messages.push(...dbMsg) + if (messages.length > MAX_BUS_MESSAGE_SIZE) { + messages.splice(0, messages.length - BUS_MESSAGE_THRESHOLD) + } + for (const entry of listenerMap.entries()) { + entry[1].onMessage?.(dbMsg) + } } return r.length } @@ -161,37 +214,57 @@ const createArthasMessageBusInternal = ( } } + function close() { + state.isExcited = true + if (state.pullResultsTaskId) { + clearTimeout(state.pullResultsTaskId) + } + db.close() + } + return { launchPullResultTask, pullNow, addListener, removeListener, execute, - state, + messages, + close, } } -const useArthasMessageBus = (): ArthasMessageBus => { +const useArthasMessageBus = ( + channelId: string, +): ArthasMessageBus | undefined => { const dispatch = useDispatch() - const internalBus = useMemo( - () => createArthasMessageBusInternal(dispatch), - [dispatch], - ) + const [internalBus, setInternalBus] = useState< + ArthasMessageBusInternal | undefined + >() useEffect(() => { - const state = internalBus.state - state.isExcited = false - internalBus.launchPullResultTask() - dispatch(clearExpiredMessages()) + let isDestroyed = false + let myBus: ArthasMessageBusInternal | undefined + createArthasMessageBusInternal(channelId, dispatch) + .then((r) => { + if (isDestroyed) { + myBus = r + r.close() + } else { + setInternalBus(r) + r.launchPullResultTask() + } + }) + .catch((e) => { + console.error(e) + }) return () => { - state.isExcited = true - if (state.pullResultsTaskId) { - clearTimeout(state.pullResultsTaskId) + isDestroyed = true + if (myBus) { + myBus.close() } } - }, [dispatch, internalBus]) + }, [channelId, dispatch]) - // 偷个懒 return internalBus } diff --git a/spectre-frontend/src/store/channelSlice.ts b/spectre-frontend/src/store/channelSlice.ts index eead197..beffccc 100644 --- a/spectre-frontend/src/store/channelSlice.ts +++ b/spectre-frontend/src/store/channelSlice.ts @@ -1,7 +1,4 @@ -import type { - ArthasResponseWithId, - InputStatusResponse, -} from '@/api/impl/arthas.ts' +import type { InputStatusResponse } from '@/api/impl/arthas.ts' import { createSlice, type PayloadAction } from '@reduxjs/toolkit' type ChannelContext = { @@ -12,14 +9,6 @@ type ChannelContext = { } interface ChannelState { - /** - * channel id -> 缓存的消息 - */ - messages: Record - /** - * 记录上次更新时间,并删除长时间不更新的记录 - */ - updates: Record /** * 保存当前界面频道上下文. 不会被持久化 */ @@ -27,69 +16,18 @@ interface ChannelState { } const initialState: ChannelState = { - messages: {}, - updates: {}, context: { channelId: '-1', inputStatus: 'DISABLED', }, } -type AppendMessagePayload = { - channelId: string - messages: ArthasResponseWithId[] -} - export const channelSlice = createSlice({ name: 'channel', initialState, reducers: { - /** - * 追加消息 - */ - appendMessages(state, action: PayloadAction) { - const payload = action.payload - const messagesTarget = state.messages[payload.channelId] - if (messagesTarget) { - messagesTarget.push(...payload.messages) - } else { - state.messages[payload.channelId] = payload.messages - } - const len = state.messages[payload.channelId].length - if (len > 64) { - // TODO 优化代码,避免频繁清除 - state.messages[payload.channelId] = state.messages[ - payload.channelId - ].slice(len - 64) - } - state.updates[payload.channelId] = Date.now() - }, - clearExpiredMessages(state) { - const newMessage: Record = {} - const newUpdates: Record = {} - Object.entries(state.updates).forEach(([k, v]) => { - if (Date.now() - v < 1000 * 60 * 30) { - newMessage[k] = state.messages[k] - newUpdates[k] = v - } - }) - state.messages = newMessage - state.updates = newUpdates - }, - setupChannelContext(state, action: PayloadAction<{ channelId: string }>) { - const messages = state.messages[action.payload.channelId] ?? [] - let inputStatus: InputStatusResponse['inputStatus'] = 'DISABLED' - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - if (msg.type === 'input_status') { - inputStatus = (msg as InputStatusResponse).inputStatus - break - } - } - state.context = { - channelId: action.payload.channelId, - inputStatus, - } + setupChannelContext(state, action: PayloadAction) { + state.context = action.payload }, updateChannelContext( state, @@ -109,11 +47,6 @@ export const channelSlice = createSlice({ }, }) -export const { - clearExpiredMessages, - appendMessages, - setupChannelContext, - updateChannelContext, - updateInputStatus, -} = channelSlice.actions +export const { setupChannelContext, updateChannelContext, updateInputStatus } = + channelSlice.actions export default channelSlice.reducer