From 7509a1e53065d99382701f542beeb4372984f8c0 Mon Sep 17 00:00:00 2001 From: SungChul Hong Date: Mon, 17 Feb 2025 18:06:52 +0900 Subject: [PATCH] feat: Introduce session status info modal in session detail panel --- .../SessionStatusDetailModal.tsx | 219 ++++++++++++++++++ .../SessionStatusTag.tsx | 68 ++++-- react/src/components/DoubleTag.tsx | 12 +- react/src/components/SessionDetailContent.tsx | 9 +- resources/i18n/de.json | 7 +- resources/i18n/el.json | 7 +- resources/i18n/en.json | 7 +- resources/i18n/es.json | 7 +- resources/i18n/fi.json | 7 +- resources/i18n/fr.json | 7 +- resources/i18n/id.json | 7 +- resources/i18n/it.json | 7 +- resources/i18n/ja.json | 7 +- resources/i18n/ko.json | 7 +- resources/i18n/mn.json | 7 +- resources/i18n/ms.json | 7 +- resources/i18n/pl.json | 7 +- resources/i18n/pt-BR.json | 7 +- resources/i18n/pt.json | 7 +- resources/i18n/ru.json | 7 +- resources/i18n/th.json | 7 +- resources/i18n/tr.json | 7 +- resources/i18n/vi.json | 7 +- resources/i18n/zh-CN.json | 7 +- resources/i18n/zh-TW.json | 7 +- 25 files changed, 403 insertions(+), 52 deletions(-) create mode 100644 react/src/components/ComputeSessionNodeItems/SessionStatusDetailModal.tsx diff --git a/react/src/components/ComputeSessionNodeItems/SessionStatusDetailModal.tsx b/react/src/components/ComputeSessionNodeItems/SessionStatusDetailModal.tsx new file mode 100644 index 0000000000..db4a331993 --- /dev/null +++ b/react/src/components/ComputeSessionNodeItems/SessionStatusDetailModal.tsx @@ -0,0 +1,219 @@ +import { useSuspendedBackendaiClient } from '../../hooks'; +import { useCurrentUserRole } from '../../hooks/backendai'; +import BAIModal from '../BAIModal'; +import DoubleTag from '../DoubleTag'; +import Flex from '../Flex'; +import { statusTagColor } from './SessionStatusTag'; +import { SessionStatusDetailModalFragment$key } from './__generated__/SessionStatusDetailModalFragment.graphql'; +import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'; +import { + Descriptions, + ModalProps, + Tag, + Timeline, + Typography, + theme, +} from 'antd'; +import graphql from 'babel-plugin-relay/macro'; +import dayjs from 'dayjs'; +import _ from 'lodash'; +import { Fragment } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useFragment } from 'react-relay'; + +export const statusInfoTagColor = { + 'idle-timeout': 'green', + 'user-requested': 'green', + scheduled: 'green', + 'self-terminated': 'green', + 'failed-to-start': 'red', + 'creation-failed': 'red', + 'no-available-instances': 'red', +}; + +type Predicates = { + name: string; + msg: string; +}; + +type ErrorCollection = { + name: string; + repr: string; + src: string; + agent_id?: string; + traceback?: string; +}; + +type StatusData = { + kernel?: { + exit_code: number | string; + }; + session?: { + status: string; + }; + scheduler?: { + failed_predicates: Array; + passed_predicates: Array; + retries: number; + last_try: string; + msg?: string; + }; + error?: { + name: string; + repr: string; + src: string; + collection: Array; + }; +}; + +interface SessionStatusDetailModalProps extends ModalProps { + sessionFrgmt: SessionStatusDetailModalFragment$key; +} + +const SessionStatusDetailModal: React.FC = ({ + sessionFrgmt, + ...modalProps +}) => { + const { t } = useTranslation(); + const { token } = theme.useToken(); + const userRole = useCurrentUserRole(); + const baiClient = useSuspendedBackendaiClient(); + + const session = useFragment( + graphql` + fragment SessionStatusDetailModalFragment on ComputeSessionNode { + id + status + status_info + status_data + starts_at + } + `, + sessionFrgmt, + ); + console.log(session); + const statusData: StatusData = JSON.parse(session.status_data || '{}'); + console.log('#', statusData); + + return ( + + {t('session.StatusInfo')} + + + } + footer={null} + width={450} + {...modalProps} + > + + {statusData?.kernel ? ( + + {statusData.kernel.exit_code} + + ) : null} + {statusData?.session ? ( + + {statusData.session?.status} + + ) : null} + {statusData?.scheduler ? ( + <> + + {statusData.scheduler?.retries} + + + {dayjs(statusData.scheduler?.last_try).format('lll')} + + + {statusData.scheduler?.msg} + + + + + { + return { + dot: , + color: token.colorError, + children: ( + + {p.name} + + {p.msg} + + + ), + }; + }), + _.map(statusData.scheduler?.passed_predicates, (p) => { + return { + dot: , + color: token.colorSuccess, + children: {p.name}, + }; + }), + )} + /> + + + + + ) : null} + {statusData?.error + ? _.map(statusData?.error?.collection ?? statusData, (collection) => { + return ( + + + {collection.name} + + {(userRole === 'superadmin' || + !baiClient._config.hideAgents) && + collection?.agent_id && ( + + {collection?.agent_id} + + )} + + {collection.repr} + + {collection?.traceback && ( + +
{collection?.traceback}
+
+ )} +
+ ); + }) + : null} +
+
+ ); +}; + +export default SessionStatusDetailModal; diff --git a/react/src/components/ComputeSessionNodeItems/SessionStatusTag.tsx b/react/src/components/ComputeSessionNodeItems/SessionStatusTag.tsx index 9c6aed1673..50a635461a 100644 --- a/react/src/components/ComputeSessionNodeItems/SessionStatusTag.tsx +++ b/react/src/components/ComputeSessionNodeItems/SessionStatusTag.tsx @@ -1,20 +1,24 @@ import Flex from '../Flex'; +import SessionStatusDetailModal, { + statusInfoTagColor, +} from './SessionStatusDetailModal'; import { SessionStatusTagFragment$data, SessionStatusTagFragment$key, } from './__generated__/SessionStatusTagFragment.graphql'; -import { LoadingOutlined } from '@ant-design/icons'; -import { Tag, theme } from 'antd'; +import { LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons'; +import { Tag, Tooltip, theme } from 'antd'; import graphql from 'babel-plugin-relay/macro'; import _ from 'lodash'; -import React from 'react'; +import React, { Suspense, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useFragment } from 'react-relay'; interface SessionStatusTagProps { sessionFrgmt?: SessionStatusTagFragment$key | null; showInfo?: boolean; } -const statusTagColor = { +export const statusTagColor = { //prepare RESTARTING: 'blue', PREPARING: 'blue', @@ -41,31 +45,30 @@ const isTransitional = (session: SessionStatusTagFragment$data) => { ].includes(session?.status || ''); }; -const statusInfoTagColor = { - // 'idle-timeout': undefined, - // 'user-requested': undefined, - // scheduled: undefined, - // 'self-terminated': undefined, - 'no-available-instances': 'red', - 'failed-to-start': 'red', - 'creation-failed': 'red', -}; const SessionStatusTag: React.FC = ({ sessionFrgmt, showInfo, }) => { + const { t } = useTranslation(); + const { token } = theme.useToken(); + const [openStatusDetailModal, setOpenStatusDetailModal] = + useState(false); + const session = useFragment( graphql` fragment SessionStatusTagFragment on ComputeSessionNode { id - name status status_info + status_data + + ...SessionStatusDetailModalFragment } `, sessionFrgmt, ); - const { token } = theme.useToken(); + + const hasDetail = session?.status_data && session?.status_data !== '{}'; return session ? ( _.isEmpty(session.status_info) || !showInfo ? ( @@ -85,14 +88,28 @@ const SessionStatusTag: React.FC = ({ ) : ( - - {session.status} - + : undefined} + style={{ + margin: 0, + zIndex: 1, + cursor: hasDetail ? 'pointer' : 'auto', + }} + onClick={() => { + if (hasDetail) { + setOpenStatusDetailModal(true); + } + }} + > + {session.status} + + = ({ > {session.status_info} + + setOpenStatusDetailModal(false)} + /> + ) ) : null; diff --git a/react/src/components/DoubleTag.tsx b/react/src/components/DoubleTag.tsx index 1ce882141f..66147c060b 100644 --- a/react/src/components/DoubleTag.tsx +++ b/react/src/components/DoubleTag.tsx @@ -7,6 +7,7 @@ import React from 'react'; export type DoubleTagObjectValue = { label: string; color?: string; + style?: React.CSSProperties; // style 속성 추가 }; const DoubleTag: React.FC<{ @@ -36,11 +37,12 @@ const DoubleTag: React.FC<{ !_.isEmpty(objValue.label) ? ( {!_.isUndefined(highlightKeyword) ? ( diff --git a/react/src/components/SessionDetailContent.tsx b/react/src/components/SessionDetailContent.tsx index 6ca90d56c2..1ec409f7be 100644 --- a/react/src/components/SessionDetailContent.tsx +++ b/react/src/components/SessionDetailContent.tsx @@ -20,8 +20,7 @@ import ImageMetaIcon from './ImageMetaIcon'; import SessionUsageMonitor from './SessionUsageMonitor'; import { SessionDetailContentLegacyQuery } from './__generated__/SessionDetailContentLegacyQuery.graphql'; import { SessionDetailContentQuery } from './__generated__/SessionDetailContentQuery.graphql'; -import { FolderOutlined } from '@ant-design/icons'; -import { InfoCircleOutlined } from '@ant-design/icons'; +import { FolderOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { Alert, Button, @@ -108,6 +107,9 @@ const SessionDetailContent: React.FC<{ ...SessionUsageMonitorFragment ...ContainerCommitModalFragment ...SessionIdleChecksNodeFragment + # fix: This fragment is not used in this component, but it is required by the SessionStatusDetailModal. + # It might be a bug in relay + ...SessionStatusDetailModalFragment } legacy_session: compute_session(id: $uuid) { image @@ -202,7 +204,6 @@ const SessionDetailContent: React.FC<{ contentStyle={{ display: 'flex', gap: token.marginSM }} > - {/*