Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions packages/backend.ai-ui/src/components/Table/BAITable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import BAIFlex from '../BAIFlex';
import BAIUnmountAfterClose from '../BAIUnmountAfterClose';
import BAIPaginationInfoText from './BAIPaginationInfoText';
import BAITableSettingModal from './BAITableSettingModal';
import { SettingOutlined } from '@ant-design/icons';
import { LoadingOutlined, SettingOutlined } from '@ant-design/icons';
import { useControllableValue, useDebounce } from 'ahooks';
import {
Button,
Expand Down Expand Up @@ -171,6 +171,7 @@ export interface BAITableProps<RecordType extends AnyObject>
tableSettings?: BAITableSettings;
/** Array of column configurations using BAIColumnType */
columns?: BAIColumnsType<RecordType>;
spinnerLoading?: boolean;
}

/**
Expand Down Expand Up @@ -208,6 +209,7 @@ const BAITable = <RecordType extends object = any>({
columns,
components,
loading,
spinnerLoading,
order,
onChangeOrder,
tableSettings,
Expand Down Expand Up @@ -327,8 +329,16 @@ const BAITable = <RecordType extends object = any>({
tableProps.rowSelection?.columnWidth === 0 &&
styles.zeroWithSelectionColumn,
)}
loading={
spinnerLoading
? {
indicator: <LoadingOutlined spin />,
spinning: true,
}
: undefined
}
style={{
opacity: loading ? 0.7 : 1,
opacity: loading ? 0.6 : 1,
transition: 'opacity 0.3s ease',
}}
components={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
};
}, []);

const mergedLoading = files?.items !== fetchedFilesCache || isFetching;
return (
<FolderInfoContext.Provider value={{ targetVFolderId, currentPath }}>
{isDragMode && (
Expand Down Expand Up @@ -304,7 +305,12 @@ const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
scroll={{ x: 'max-content' }}
dataSource={fetchedFilesCache}
columns={tableColumns}
loading={files?.items !== fetchedFilesCache || isFetching}
// If no files have been loaded yet (including cache), show spinner loading
spinnerLoading={!files?.items ? mergedLoading : undefined}
// If files have been loaded before, use normal loading style (opacity)
loading={
files?.items && files?.items.length >= 0 ? mergedLoading : undefined
}
pagination={false}
rowSelection={{
type: 'checkbox',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ export const useSearchVFolderFiles = (vfolder: string, fetchKey?: string) => {
return res;
}),
enabled: !!vfolder,
// not using cache, always refetch
staleTime: 5 * 60 * 1000,
gcTime: 0,
staleTime: 3000,
});

return {
Expand Down
28 changes: 7 additions & 21 deletions react/src/components/FileUploadManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { RcFile } from 'antd/es/upload';
import {
BAIFlex,
BAILink,
toGlobalId,
toLocalId,
useConnectedBAIClient,
} from 'backend.ai-ui';
import { atom, useAtom, useSetAtom } from 'jotai';
Expand All @@ -14,8 +14,6 @@ import _ from 'lodash';
import PQueue from 'p-queue';
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { graphql, useLazyLoadQuery } from 'react-relay';
import { FileUploadManagerQuery } from 'src/__generated__/FileUploadManagerQuery.graphql';
import { useSuspendedBackendaiClient } from 'src/hooks';
import { useBAISettingUserState } from 'src/hooks/useBAISetting';
import * as tus from 'tus-js-client';
Expand Down Expand Up @@ -339,29 +337,17 @@ const FileUploadManager: React.FC = () => {

export default FileUploadManager;

export const useFileUploadManager = (vFolderId: string) => {
export const useFileUploadManager = (id?: string, folderName?: string) => {
'use memo';

const baiClient = useConnectedBAIClient();
const { t } = useTranslation();
const { upsertNotification } = useSetBAINotification();
const [uploadStatus, setUploadStatus] = useUploadStatusAtomStatus(vFolderId);

const setUploadRequests = useSetAtom(uploadRequestAtom);

const { vfolder_node } = useLazyLoadQuery<FileUploadManagerQuery>(
graphql`
query FileUploadManagerQuery($vfolderGlobalId: String!) {
vfolder_node(id: $vfolderGlobalId) {
name @required(action: THROW)
}
}
`,
{
vfolderGlobalId: toGlobalId('VirtualFolderNode', vFolderId),
},
{
fetchPolicy: vFolderId ? 'network-only' : 'store-only',
},
const [uploadStatus, setUploadStatus] = useUploadStatusAtomStatus(
id ? toLocalId(id) : '',
);

const validateUploadRequest = (
Expand All @@ -379,7 +365,7 @@ export const useFileUploadManager = (vFolderId: string) => {
open: true,
key: 'upload:' + vfolderId,
message: t('explorer.UploadFailed', {
folderName: vfolder_node?.name ?? '',
folderName: folderName ?? '',
}),
description: t('data.explorer.FileUploadSizeLimit'),
duration: 3,
Expand Down Expand Up @@ -474,7 +460,7 @@ export const useFileUploadManager = (vFolderId: string) => {

const uploadRequestInfo: UploadRequest = {
vFolderId: vfolderId,
vFolderName: vfolder_node?.name ?? '',
vFolderName: folderName ?? '',
uploadFileInfo: _.zipWith(
fileToUpload,
startUploadFunctionMap,
Expand Down
141 changes: 80 additions & 61 deletions react/src/components/FolderExplorerModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useFileUploadManager } from './FileUploadManager';
import FolderExplorerHeader from './FolderExplorerHeader';
import VFolderNodeDescription from './VFolderNodeDescription';
import { Alert, Divider, Grid, Splitter, theme } from 'antd';
import { Alert, Divider, Grid, Skeleton, Splitter, theme } from 'antd';
import { createStyles } from 'antd-style';
import { RcFile } from 'antd/es/upload';
import {
Expand All @@ -12,7 +12,7 @@ import {
toGlobalId,
} from 'backend.ai-ui';
import _ from 'lodash';
import { useEffect, useRef } from 'react';
import { Suspense, useDeferredValue, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { graphql, useLazyLoadQuery } from 'react-relay';
import { FolderExplorerModalQuery } from 'src/__generated__/FolderExplorerModalQuery.graphql';
Expand Down Expand Up @@ -54,7 +54,6 @@ const FolderExplorerModal: React.FC<FolderExplorerProps> = ({
const { xl } = Grid.useBreakpoint();
const { styles } = useStyles();
const folderExplorerRef = useRef<FolderExplorerElement>(null);
const { uploadStatus, uploadFiles } = useFileUploadManager(vfolderID);
const [fetchKey, updateFetchKey] = useFetchKey();
const baiClient = useSuspendedBackendaiClient();
const currentDomain = useCurrentDomainValue();
Expand All @@ -68,12 +67,7 @@ const FolderExplorerModal: React.FC<FolderExplorerProps> = ({
);
const bodyRef = useRef<HTMLDivElement | null>(null);

useEffect(() => {
if (uploadStatus && _.isEmpty(uploadStatus?.pendingFiles)) {
updateFetchKey();
}
}, [uploadStatus, updateFetchKey]);

const deferredOpen = useDeferredValue(modalProps.open);
const { vfolder_node } = useLazyLoadQuery<FolderExplorerModalQuery>(
graphql`
query FolderExplorerModalQuery($vfolderGlobalId: String!) {
Expand All @@ -82,6 +76,8 @@ const FolderExplorerModal: React.FC<FolderExplorerProps> = ({
unmanaged_path @since(version: "25.04.0")
permissions
host
id
name
...FolderExplorerHeaderFragment
...VFolderNodeDescriptionFragment
...VFolderNameTitleNodeFragment
Expand All @@ -90,9 +86,20 @@ const FolderExplorerModal: React.FC<FolderExplorerProps> = ({
`,
{ vfolderGlobalId: toGlobalId('VirtualFolderNode', vfolderID) },
{
fetchPolicy: modalProps.open ? 'network-only' : 'store-only',
// Only fetch when both deferredOpen and modalProps.open are true to prevent unnecessary requests during React transitions
fetchPolicy:
deferredOpen && modalProps.open ? 'network-only' : 'store-only',
},
);
const { uploadStatus, uploadFiles } = useFileUploadManager(
vfolder_node?.id,
vfolder_node?.name || undefined,
);
useEffect(() => {
if (uploadStatus && _.isEmpty(uploadStatus?.pendingFiles)) {
updateFetchKey();
}
}, [uploadStatus, updateFetchKey]);

const hasDownloadContentPermission = _.includes(
unitedAllowedPermissionByVolume[vfolder_node?.host ?? ''],
Expand All @@ -114,7 +121,7 @@ const FolderExplorerModal: React.FC<FolderExplorerProps> = ({
message={t('explorer.NoExplorerSupportForUnmanagedFolder')}
showIcon
/>
) : !hasNoPermissions ? (
) : !hasNoPermissions && vfolder_node ? (
<BAIFileExplorer
targetVFolderId={vfolderID}
fetchKey={fetchKey}
Expand Down Expand Up @@ -149,10 +156,15 @@ const FolderExplorerModal: React.FC<FolderExplorerProps> = ({
<BAIModal
className={styles.baiModalHeader}
width={'90%'}
centered
keyboard
destroyOnHidden
footer={null}
style={{ maxWidth: '1600px' }}
styles={{
body: {
height: '100vh',
},
}}
title={
vfolder_node ? (
<FolderExplorerHeader
Expand All @@ -171,58 +183,65 @@ const FolderExplorerModal: React.FC<FolderExplorerProps> = ({
}}
{...modalProps}
>
<BAIFlex direction="column" gap={'lg'} align="stretch">
{!vfolder_node ? (
<Alert
message={t('explorer.FolderNotFoundOrNoAccess')}
type="error"
showIcon
/>
) : hasNoPermissions ? (
<Alert message={t('explorer.NoPermissions')} type="error" showIcon />
) : currentProject?.id !== vfolder_node?.group &&
!!vfolder_node?.group ? (
<Alert message={t('data.NotInProject')} type="warning" showIcon />
) : null}

{xl ? (
<Splitter
// Force re-render component when xl breakpoint changes to reset panel sizes
// This ensures defaultSize is recalculated based on current screen size
key={xl ? 'large' : 'small'}
style={{
gap: token.size,
// maxHeight: 'calc(100vh - 220px)',
}}
layout={xl ? 'horizontal' : 'vertical'}
>
<Splitter.Panel resizable={false}>
{fileExplorerElement}
</Splitter.Panel>
<Splitter.Panel defaultSize={500}>
{vFolderDescriptionElement}
</Splitter.Panel>
</Splitter>
<Suspense fallback={<Skeleton active />}>
{/* Use <Skeleton/> instead of using `loading` prop because layout align issue. */}
{deferredOpen !== modalProps.open ? (
<Skeleton active />
) : (
<BAIFlex direction="column" align="stretch">
{fileExplorerElement}
<Divider
style={{
borderColor: token.colorBorderSecondary,
}}
/>
{vFolderDescriptionElement}
<BAIFlex direction="column" gap={'lg'} align="stretch">
{!vfolder_node ? (
<Alert
message={t('explorer.FolderNotFoundOrNoAccess')}
type="error"
showIcon
/>
) : hasNoPermissions ? (
<Alert
message={t('explorer.NoPermissions')}
type="error"
showIcon
/>
) : currentProject?.id !== vfolder_node?.group &&
!!vfolder_node?.group ? (
<Alert message={t('data.NotInProject')} type="warning" showIcon />
) : null}

{xl ? (
<Splitter
style={{
gap: token.size,
}}
layout={'horizontal'}
>
<Splitter.Panel resizable={false}>
{fileExplorerElement}
</Splitter.Panel>
<Splitter.Panel defaultSize={500}>
{vFolderDescriptionElement}
</Splitter.Panel>
</Splitter>
) : (
<BAIFlex direction="column" align="stretch">
{fileExplorerElement}
<Divider
style={{
borderColor: token.colorBorderSecondary,
}}
/>
{vFolderDescriptionElement}
</BAIFlex>
)}
<div style={{ display: 'none' }}>
{/* @ts-ignore TODO: delete below after https://lablup.atlassian.net/browse/FR-1150 */}
<backend-ai-folder-explorer
ref={folderExplorerRef}
active
vfolderID={vfolderID}
/>
</div>
</BAIFlex>
)}
<div style={{ display: 'none' }}>
{/* @ts-ignore TODO: delete below after https://lablup.atlassian.net/browse/FR-1150 */}
<backend-ai-folder-explorer
ref={folderExplorerRef}
active
vfolderID={vfolderID}
/>
</div>
</BAIFlex>
</Suspense>
</BAIModal>
);
};
Expand Down
2 changes: 0 additions & 2 deletions react/src/components/SFTPServerButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ const SFTPServerButton: React.FC<SFTPServerButtonProps> = ({
const { systemSSHImage, systemSSHImageInfo } =
useDefaultSystemSSHImageWithFallback();

console.log('systemSSHImage', systemSSHImageInfo);

const vfolder = useFragment(
graphql`
fragment SFTPServerButtonFragment on VirtualFolderNode {
Expand Down
Loading