diff --git a/react/package.json b/react/package.json index f4921deb03..f6f87cb828 100644 --- a/react/package.json +++ b/react/package.json @@ -9,6 +9,7 @@ "@ant-design/icons": "^5.4.0", "@cloudscape-design/board-components": "3.0.60", "@codemirror/language": "^6.10.2", + "@melloware/react-logviewer": "^5.2.4", "@storybook/test": "^8.2.8", "@tanstack/react-query": "^5.51.15", "@testing-library/jest-dom": "^6.4.8", diff --git a/react/pnpm-lock.yaml b/react/pnpm-lock.yaml index 0d9e95a21e..acb99c74f8 100644 --- a/react/pnpm-lock.yaml +++ b/react/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@codemirror/language': specifier: ^6.10.2 version: 6.10.2 + '@melloware/react-logviewer': + specifier: ^5.2.4 + version: 5.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/test': specifier: ^8.2.8 version: 8.2.8(@types/jest@29.5.12)(jest@27.5.1(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)))(storybook@8.2.8(@babel/preset-env@7.25.3(@babel/core@7.25.2))) @@ -1847,6 +1850,12 @@ packages: '@types/react': '>=16' react: '>=16' + '@melloware/react-logviewer@5.2.4': + resolution: {integrity: sha512-9BvE6v2rSsAuvsA4FY3nul91MbBfcAECEH3/4gmTUE5NBptkANFNBaEDD0fZpAOUNnaYK0qHUtG5A/EfJ2eZQQ==} + peerDependencies: + react: ^17.1.1 || ^18.0.0 || ^19.0.0 + react-dom: ^17.1.1 || ^18.0.0 || ^19.0.0 + '@nextjournal/lang-clojure@1.0.0': resolution: {integrity: sha512-gOCV71XrYD0DhwGoPMWZmZ0r92/lIHsqQu9QWdpZYYBwiChNwMO4sbVMP7eTuAqffFB2BTtCSC+1skSH9d3bNg==} @@ -4746,6 +4755,9 @@ packages: resolution: {integrity: sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==} engines: {node: '>= 6.0.0'} + hotkeys-js@3.13.7: + resolution: {integrity: sha512-ygFIdTqqwG4fFP7kkiYlvayZppeIQX2aPpirsngkv1xM1lP0piDY5QEh68nQnIKvz64hfocxhBaD/uK3sSK1yQ==} + hpack.js@2.1.6: resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} @@ -5712,6 +5724,9 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} @@ -5864,6 +5879,9 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -7190,6 +7208,10 @@ packages: peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-string-replace@1.1.1: + resolution: {integrity: sha512-26TUbLzLfHQ5jO5N7y3Mx88eeKo0Ml0UjCQuX4BMfOd/JX+enQqlKpL1CZnmjeBRvQE8TR+ds9j1rqx9CxhKHQ==} + engines: {node: '>=0.12.0'} + react-syntax-highlighter@15.5.0: resolution: {integrity: sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==} peerDependencies: @@ -7206,6 +7228,12 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' + react-virtualized-auto-sizer@1.0.24: + resolution: {integrity: sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==} + peerDependencies: + react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 + react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 + react-virtuoso@4.9.0: resolution: {integrity: sha512-MiiSGKqvYPfAK3FUe852n2L3M5IXMKP0pUgYQ/UTk90A/l2UNQOvaEUvAZp+0ytL0kOCNk8i8/J8FMKvIq7kqg==} engines: {node: '>=10'} @@ -7213,6 +7241,13 @@ packages: react: '>=16 || >=17 || >= 18' react-dom: '>=16 || >=17 || >= 18' + react-window@1.8.10: + resolution: {integrity: sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==} + engines: {node: '>8.0.0'} + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -10783,6 +10818,16 @@ snapshots: '@types/react': 18.3.3 react: 18.3.1 + '@melloware/react-logviewer@5.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + hotkeys-js: 3.13.7 + mitt: 3.0.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-string-replace: 1.1.1 + react-virtualized-auto-sizer: 1.0.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-window: 1.8.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@nextjournal/lang-clojure@1.0.0': dependencies: '@codemirror/language': 6.10.2 @@ -14508,6 +14553,8 @@ snapshots: hoopy@0.1.4: {} + hotkeys-js@3.13.7: {} + hpack.js@2.1.6: dependencies: inherits: 2.0.4 @@ -15802,6 +15849,8 @@ snapshots: dependencies: fs-monkey: 1.0.6 + memoize-one@5.2.1: {} + memoizerific@1.11.3: dependencies: map-or-similar: 1.5.0 @@ -16003,6 +16052,8 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 + mitt@3.0.1: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -17558,6 +17609,8 @@ snapshots: react: 18.3.1 react-is: 18.3.1 + react-string-replace@1.1.1: {} + react-syntax-highlighter@15.5.0(react@18.3.1): dependencies: '@babel/runtime': 7.25.0 @@ -17583,11 +17636,23 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-virtualized-auto-sizer@1.0.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-virtuoso@4.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-window@1.8.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.25.0 + memoize-one: 5.2.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0 diff --git a/react/src/components/ComputeSessionNodeItems/ContainerLogModal.tsx b/react/src/components/ComputeSessionNodeItems/ContainerLogModal.tsx new file mode 100644 index 0000000000..6f1e010c20 --- /dev/null +++ b/react/src/components/ComputeSessionNodeItems/ContainerLogModal.tsx @@ -0,0 +1,271 @@ +import { downloadBlob } from '../../helper/csv-util'; +import { useSuspendedBackendaiClient } from '../../hooks'; +import { useTanQuery } from '../../hooks/reactQueryAlias'; +import BAIModal, { BAIModalProps } from '../BAIModal'; +import Flex from '../Flex'; +import { ContainerLogModalFragment$key } from './__generated__/ContainerLogModalFragment.graphql'; +import { ReloadOutlined } from '@ant-design/icons'; +import { LazyLog, ScrollFollow } from '@melloware/react-logviewer'; +import { + Button, + Divider, + Grid, + Select, + theme, + Tooltip, + Typography, +} from 'antd'; +import graphql from 'babel-plugin-relay/macro'; +import _ from 'lodash'; +import { DownloadIcon } from 'lucide-react'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useFragment } from 'react-relay'; + +interface ContainerLogModalProps extends BAIModalProps { + sessionFrgmt: ContainerLogModalFragment$key; +} + +const ContainerLogModal: React.FC = ({ + sessionFrgmt, + ...modalProps +}) => { + const baiClient = useSuspendedBackendaiClient(); + const { token } = theme.useToken(); + + const session = useFragment( + graphql` + fragment ContainerLogModalFragment on ComputeSessionNode { + id + row_id @required(action: NONE) + name + status + access_key + kernel_nodes { + edges { + node { + id + row_id + container_id + cluster_role + } + } + } + } + `, + sessionFrgmt, + ); + + const kernelNodes = session?.kernel_nodes?.edges?.map((e) => e?.node) || []; + const [selectedKernelId, setSelectedKernelId] = useState( + _.find(kernelNodes, (e) => e?.cluster_role === 'main')?.row_id || + kernelNodes[0]?.row_id, + ); + + // Currently we can only fetch full logs + // const [logSize, setLogSize] = useState<100|'full'>('full'); + + const { + data: logs, + refetch, + isPending, + isRefetching, + dataUpdatedAt, + } = useTanQuery({ + queryKey: [ + 'containerLog', + session?.row_id, + session?.access_key, + selectedKernelId, + modalProps.open, + ], + queryFn: async () => { + if ( + !modalProps.open || + !session?.row_id || + !session?.access_key || + !selectedKernelId + ) { + return ''; + } + return await baiClient + .get_logs(session?.row_id, session?.access_key, selectedKernelId, 15000) + .then((req: any) => req.result.logs); + }, + staleTime: 5000, + }); + + // let queryParams: Array = []; + // if (session?.access_key != null) { + // queryParams.push(`owner_access_key=${session.access_key}`); + // } + // if (baiClient.supports('per-kernel-logs') && selectedKernelId !== null) { + // queryParams.push(`kernel_id=${selectedKernelId}`); + // } + // let queryString = `/session/${session?.row_id}/logs`; + // if (queryParams.length > 0) { + // queryString += `?${queryParams.join('&')}`; + // } + // // const url = `${baiClient._endpoint}${queryString}` + + // const signed = baiClient.newSignedRequest('GET', queryString, null); + // console.log(signed) + // console.log(signed.uri); + + const previousLastLineNumber = useRef(0); + + useEffect(() => { + previousLastLineNumber.current = logs?.split('\n').length || 0; + }, [logs]); + + const { md } = Grid.useBreakpoint(); + const { t } = useTranslation(); + const fixedPreviousLastLineNumber = previousLastLineNumber.current; + + return ( + + + Logs + + + {session?.name} + + + ({md ? session?.row_id : session?.row_id.split('-')?.[0]}) + + + } + width={'100%'} + styles={{ + header: { + width: '100%', + }, + body: { + height: 'calc(100vh - 100px)', + maxHeight: 'calc(100vh - 100px)', + }, + }} + {...modalProps} + footer={null} + > + + + Kernel Role + { + setLogSize(value); + if(value!=='full'){ + previousLastLineNumber.current = 0; // reset previous last line number + } + refetch(); + }} + > */} + +