diff --git a/.cspell.json b/.cspell.json index 2391993bdb..b74cb62ce4 100644 --- a/.cspell.json +++ b/.cspell.json @@ -10,6 +10,7 @@ "FGPU", "filebrowser", "Frgmt", + "Frgmts", "Gaudi", "keypair", "Lablup", @@ -19,17 +20,14 @@ "RNGD", "shmem", "superadmin", + "textbox", + "vaadin", "vfolder", "vfolders", "Warboy", "webcomponent", "webui", - "wsproxy", - "vfolders", - "vfolder", - "filebrowser", - "vaadin", - "textbox" + "wsproxy" ], "flagWords": [ "데이터레이크", diff --git a/react/data/schema.graphql b/react/data/schema.graphql index 18912ef865..1ef2b25498 100644 --- a/react/data/schema.graphql +++ b/react/data/schema.graphql @@ -21,7 +21,18 @@ type Queries { group_node(id: String!): GroupNode """Added in 24.03.0.""" - group_nodes(filter: String, order: String, offset: Int, before: String, after: String, first: Int, last: Int): GroupConnection + group_nodes( + """Added in 24.09.0.""" + filter: String + + """Added in 24.09.0.""" + order: String + offset: Int + before: String + after: String + first: Int + last: Int + ): GroupConnection group( id: UUID! domain_name: String @@ -1258,9 +1269,7 @@ type ContainerRegistryNode implements Node { """The ID of the object""" id: ID! - """ - Added in 24.09.0. The undecoded UUID type id of DB container_registries row. - """ + """Added in 24.09.0. The UUID type id of DB container_registries row.""" row_id: UUID name: String diff --git a/react/relay.config.js b/react/relay.config.js index c2d899a18f..a3e7218c08 100644 --- a/react/relay.config.js +++ b/react/relay.config.js @@ -31,5 +31,6 @@ module.exports = { // https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#number-string-boolean-symbol-and-object DateTime: 'string', UUID: 'string', + JSONString: 'string', }, }; diff --git a/react/src/App.tsx b/react/src/App.tsx index 7b445f9b43..3e2da8daf0 100644 --- a/react/src/App.tsx +++ b/react/src/App.tsx @@ -57,6 +57,10 @@ const InteractiveLoginPage = React.lazy( ); const ImportAndRunPage = React.lazy(() => import('./pages/ImportAndRunPage')); +const ComputeSessionList = React.lazy( + () => import('./components/ComputeSessionList'), +); + const RedirectToSummary = () => { useSuspendedBackendaiClient(); const pathName = '/summary'; @@ -139,6 +143,11 @@ const router = createBrowserRouter([ { path: '/job', handle: { labelKey: 'webui.menu.Sessions' }, + element: ( + + + + ), }, { path: '/serving', diff --git a/react/src/components/BAIModal.tsx b/react/src/components/BAIModal.tsx index 7e715b7e4b..e162dbc3e6 100644 --- a/react/src/components/BAIModal.tsx +++ b/react/src/components/BAIModal.tsx @@ -9,9 +9,7 @@ import Draggable from 'react-draggable'; export const DEFAULT_BAI_MODAL_Z_INDEX = 1001; export interface BAIModalProps extends ModalProps { - okText?: string; // customize text of ok button with adequate content draggable?: boolean; // modal can be draggle - className?: string; } const BAIModal: React.FC = ({ className, diff --git a/react/src/components/ComputeSessionList.tsx b/react/src/components/ComputeSessionList.tsx new file mode 100644 index 0000000000..046a13f5f1 --- /dev/null +++ b/react/src/components/ComputeSessionList.tsx @@ -0,0 +1,18 @@ +import SessionDetailDrawer from './SessionDetailDrawer'; +import React from 'react'; +import { StringParam, useQueryParam } from 'use-query-params'; + +const ComputeSessionList = () => { + const [sessionId, setSessionId] = useQueryParam('sessionDetail', StringParam); + return ( + { + setSessionId(null, 'replaceIn'); + }} + /> + ); +}; + +export default ComputeSessionList; diff --git a/react/src/components/ComputeSessionNodeItems/EditableSessionName.tsx b/react/src/components/ComputeSessionNodeItems/EditableSessionName.tsx new file mode 100644 index 0000000000..5d7d8755c4 --- /dev/null +++ b/react/src/components/ComputeSessionNodeItems/EditableSessionName.tsx @@ -0,0 +1,84 @@ +import { EditableSessionNameFragment$key } from './__generated__/EditableSessionNameFragment.graphql'; +import { EditableSessionNameMutation } from './__generated__/EditableSessionNameMutation.graphql'; +import { theme } from 'antd'; +import Text, { TextProps } from 'antd/es/typography/Text'; +import Title, { TitleProps } from 'antd/es/typography/Title'; +import graphql from 'babel-plugin-relay/macro'; +import React, { useState } from 'react'; +import { useFragment, useMutation } from 'react-relay'; + +type EditableSessionNameProps = { + sessionFrgmt: EditableSessionNameFragment$key; +} & ( + | ({ component?: typeof Text } & Omit) + | ({ component: typeof Title } & Omit) +); + +const EditableSessionName: React.FC = ({ + component: Component = Text, + sessionFrgmt, + style, + ...otherProps +}) => { + const session = useFragment( + graphql` + fragment EditableSessionNameFragment on ComputeSessionNode { + id + name + priority + } + `, + sessionFrgmt, + ); + const [optimisticName, setOptimisticName] = useState(session.name); + const { token } = theme.useToken(); + const [commitEditMutation, isPendingEditMutation] = + useMutation(graphql` + mutation EditableSessionNameMutation($input: ModifyComputeSessionInput!) { + modify_compute_session(input: $input) { + item { + id + name + } + } + } + `); + return ( + session && ( + { + setOptimisticName(newName); + commitEditMutation({ + variables: { + input: { + id: session.id, + name: newName, + // TODO: Setting the priority is not needed here. However, due to an API bug, we will keep it. + priority: session.priority, + }, + }, + onCompleted(response, errors) {}, + onError(error) {}, + }); + }, + triggerType: ['icon', 'text'], + } + } + copyable + style={{ + ...style, + color: isPendingEditMutation ? token.colorTextTertiary : style?.color, + }} + {...otherProps} + > + {isPendingEditMutation ? optimisticName : session.name} + + ) + ); +}; + +export default EditableSessionName; diff --git a/react/src/components/ComputeSessionNodeItems/SessionActionButtons.tsx b/react/src/components/ComputeSessionNodeItems/SessionActionButtons.tsx new file mode 100644 index 0000000000..3bb7c8ed36 --- /dev/null +++ b/react/src/components/ComputeSessionNodeItems/SessionActionButtons.tsx @@ -0,0 +1,122 @@ +import { useBackendAIAppLauncher } from '../../hooks/useBackendAIAppLauncher'; +import TerminateSessionModal from './TerminateSessionModal'; +import { + SessionActionButtonsFragment$data, + SessionActionButtonsFragment$key, +} from './__generated__/SessionActionButtonsFragment.graphql'; +import { Tooltip, Button, theme } from 'antd'; +import graphql from 'babel-plugin-relay/macro'; +import { TerminalIcon, PowerOffIcon } from 'lucide-react'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useFragment } from 'react-relay'; + +interface SessionActionButtonsProps { + sessionFrgmt: SessionActionButtonsFragment$key | null; +} +// const isRunning = (session:SessionActionButtonsFragment$data) => { +// return [ +// 'batch', +// 'interactive', +// 'inference', +// 'system', +// 'running', +// 'others', +// ].includes(session); +// } + +const isActive = (session: SessionActionButtonsFragment$data) => { + return !['TERMINATED', 'CANCELLED'].includes(session?.status || ''); +}; +// const isTransitional = (session: SessionActionButtonsFragment$data) => { +// return [ +// 'RESTARTING', +// 'TERMINATING', +// 'PENDING', +// 'PREPARING', +// 'PULLING', +// ].includes(session?.status || ''); +// }; + +const SessionActionButtons: React.FC = ({ + sessionFrgmt, +}) => { + const { token } = theme.useToken(); + const appLauncher = useBackendAIAppLauncher(); + + const { t } = useTranslation(); + + const session = useFragment( + graphql` + fragment SessionActionButtonsFragment on ComputeSessionNode { + id + row_id @required(action: NONE) + status + access_key + service_ports + commit_status + + ...TerminateSessionModalFragment + } + `, + sessionFrgmt, + ); + const [openTerminateModal, setOpenTerminateModal] = useState(false); + + // const isDisabledTermination = !['PENDING'].includes(session?.status || '') && session?.commit_status === 'ongoing' + // ${(this._isRunning && !this._isPreparing(rowData.item.status)) || + // this._isError(rowData.item.status) + return ( + session && ( + <> + {/* +