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
121 changes: 121 additions & 0 deletions react/src/components/FileBrowserButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { PrimaryAppOption } from './ComputeSessionNodeItems/SessionActionButtons';
import { App, ButtonProps, Image, Tooltip } from 'antd';
import { BAIButton, useErrorMessageResolver } from 'backend.ai-ui';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { graphql, useFragment } from 'react-relay';
import { FileBrowserButtonFragment$key } from 'src/__generated__/FileBrowserButtonFragment.graphql';
import { useDefaultFileBrowserImageWithFallback } from 'src/hooks/useDefaultFileBrowserImageWithFallback';
import {
StartSessionWithDefaultValue,
useStartSession,
} from 'src/hooks/useStartSession';

interface FileBrowserButtonProps extends ButtonProps {
showTitle?: boolean;
vfolderFrgmt: FileBrowserButtonFragment$key;
}
const FileBrowserButton: React.FC<FileBrowserButtonProps> = ({
showTitle = true,
vfolderFrgmt,
}) => {
'use memo';
const { t } = useTranslation();
const { message, modal } = App.useApp();

const { getErrorMessage } = useErrorMessageResolver();
const { startSessionWithDefault, upsertSessionNotification } =
useStartSession();

const filebrowserImage = useDefaultFileBrowserImageWithFallback();

const vfolder = useFragment(
graphql`
fragment FileBrowserButtonFragment on VirtualFolderNode {
id
row_id
}
`,
vfolderFrgmt,
);

return (
<Tooltip
title={
filebrowserImage === null
? t('data.explorer.NoImagesSupportingFileBrowser')
: !showTitle &&
filebrowserImage &&
t('data.explorer.ExecuteFileBrowser')
}
>
<BAIButton
icon={
<Image
width="18px"
src="/resources/icons/filebrowser.svg"
alt="File Browser"
preview={false}
style={
filebrowserImage
? undefined
: {
filter: 'grayscale(100%)',
}
}
/>
}
disabled={!filebrowserImage}
action={async () => {
if (!filebrowserImage) {
return;
}
const fileBrowserFormValue: StartSessionWithDefaultValue = {
sessionName: `filebrowser-${vfolder.row_id}`,
sessionType: 'interactive',
// use default file browser image if configured and allowed
environments: {
version: filebrowserImage,
},
allocationPreset: 'minimum-required',
cluster_mode: 'single-node',
cluster_size: 1,
mount_ids: [vfolder.row_id?.replaceAll('-', '') || ''],
resource: {
cpu: 1,
mem: '0.5g',
},
};

await startSessionWithDefault(fileBrowserFormValue)
.then((results) => {
if (results?.fulfilled && results.fulfilled.length > 0) {
upsertSessionNotification(results.fulfilled, [
{
extraData: {
appName: 'filebrowser',
} as PrimaryAppOption,
},
]);
}
if (results?.rejected && results.rejected.length > 0) {
const error = results.rejected[0].reason;
modal.error({
title: error?.title,
content: getErrorMessage(error),
});
}
})
.catch((error) => {
console.error('Unexpected error during session creation:', error);
message.error(t('error.UnexpectedError'));
});
}}
>
{showTitle && t('data.explorer.ExecuteFileBrowser')}
</BAIButton>
</Tooltip>
);
};

export default FileBrowserButton;
167 changes: 9 additions & 158 deletions react/src/components/FolderExplorerHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
import { FolderExplorerHeaderFragment$key } from '../__generated__/FolderExplorerHeaderFragment.graphql';
import { PrimaryAppOption } from './ComputeSessionNodeItems/SessionActionButtons';
import EditableVFolderName from './EditableVFolderName';
import FileBrowserButton from './FileBrowserButton';
import VFolderNodeIdenticon from './VFolderNodeIdenticon';
import { Button, Tooltip, Image, Grid, theme, Typography, App } from 'antd';
import { BAIButton, BAIFlex, useErrorMessageResolver } from 'backend.ai-ui';
import { Button, Tooltip, Image, Grid, theme, Typography } from 'antd';
import { BAIFlex } from 'backend.ai-ui';
import _ from 'lodash';
import React, { LegacyRef } from 'react';
import React, { Ref } from 'react';
import { useTranslation } from 'react-i18next';
import {
fetchQuery,
graphql,
useFragment,
useRelayEnvironment,
} from 'react-relay';
import { FolderExplorerHeader_ImageQuery } from 'src/__generated__/FolderExplorerHeader_ImageQuery.graphql';
import { getImageFullName } from 'src/helper';
import { useSuspendedBackendaiClient } from 'src/hooks';
import {
StartSessionWithDefaultValue,
useStartSession,
} from 'src/hooks/useStartSession';
import { graphql, useFragment } from 'react-relay';

interface FolderExplorerHeaderProps {
vfolderNodeFrgmt?: FolderExplorerHeaderFragment$key | null;
folderExplorerRef: LegacyRef<HTMLDivElement>;
folderExplorerRef: Ref<HTMLDivElement>;
titleStyle?: React.CSSProperties;
}

Expand All @@ -37,13 +25,6 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
const { t } = useTranslation();
const { token } = theme.useToken();
const { lg } = Grid.useBreakpoint();
const { message, modal } = App.useApp();

const relayEnv = useRelayEnvironment();
const baiClient = useSuspendedBackendaiClient();
const { getErrorMessage } = useErrorMessageResolver();
const { startSessionWithDefault, upsertSessionNotification } =
useStartSession();

const vfolderNode = useFragment(
graphql`
Expand All @@ -56,6 +37,7 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
...VFolderNameTitleNodeFragment
...VFolderNodeIdenticonFragment
...EditableVFolderNameFragment
...FileBrowserButtonFragment
}
`,
vfolderNodeFrgmt,
Expand Down Expand Up @@ -117,140 +99,9 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
justify="end"
gap={token.marginSM}
>
{!vfolderNode?.unmanaged_path ? (
{!vfolderNode?.unmanaged_path && vfolderNode ? (
<>
<Tooltip title={!lg && t('data.explorer.ExecuteFileBrowser')}>
<BAIButton
icon={
<Image
width="18px"
src="/resources/icons/filebrowser.svg"
alt="File Browser"
preview={false}
/>
}
action={async () => {
// FIXME: Currently, file browser filtering by server-side is not supported.
await fetchQuery<FolderExplorerHeader_ImageQuery>(
relayEnv,
graphql`
query FolderExplorerHeader_ImageQuery(
$installed: Boolean
) {
images(is_installed: $installed) {
id
tag
registry
architecture
name @deprecatedSince(version: "24.12.0")
namespace @since(version: "24.12.0")
labels {
key
value
}
tags @since(version: "24.12.0") {
key
value
}
}
}
`,
{
installed: true,
},
{
fetchPolicy: 'store-or-network',
},
)
.toPromise()
.then((response) =>
response?.images?.filter((image) =>
image?.labels?.find(
(label) =>
label?.key === 'ai.backend.service-ports' &&
label?.value?.toLowerCase().includes('filebrowser'),
),
),
)
.then(async (filebrowserImages) => {
if (_.isEmpty(filebrowserImages)) {
message.error(
t('data.explorer.NoImagesSupportingFileBrowser'),
);
return;
}

const fileBrowserFormValue: StartSessionWithDefaultValue =
{
sessionName: `filebrowser-${vfolderNode?.row_id.split('-')[0]}`,
sessionType: 'interactive',
// use default file browser image if configured and allowed
...(baiClient._config?.defaultFileBrowserImage &&
baiClient._config?.allow_manual_image_name_for_session
? {
environments: {
manual:
baiClient._config.defaultFileBrowserImage,
},
}
: // otherwise use the first image found
{
environments: {
version:
getImageFullName(filebrowserImages?.[0]) ||
'',
},
}),
allocationPreset: 'minimum-required',
cluster_mode: 'single-node',
cluster_size: 1,
mount_ids: [
vfolderNode?.row_id?.replaceAll('-', '') || '',
],
resource: {
cpu: 1,
mem: '0.5g',
},
};

await startSessionWithDefault(fileBrowserFormValue)
.then((results) => {
if (
results?.fulfilled &&
results.fulfilled.length > 0
) {
upsertSessionNotification(results.fulfilled, [
{
extraData: {
appName: 'filebrowser',
} as PrimaryAppOption,
},
]);
}
if (
results?.rejected &&
results.rejected.length > 0
) {
const error = results.rejected[0].reason;
modal.error({
title: error?.title,
content: getErrorMessage(error),
});
}
})
.catch((error) => {
console.error(
'Unexpected error during session creation:',
error,
);
message.error(t('error.UnexpectedError'));
});
});
}}
>
{lg && t('data.explorer.ExecuteFileBrowser')}
</BAIButton>
</Tooltip>
<FileBrowserButton vfolderFrgmt={vfolderNode} showTitle={lg} />
<Tooltip title={!lg && t('data.explorer.RunSSH/SFTPserver')}>
<Button
icon={
Expand Down
1 change: 0 additions & 1 deletion react/src/components/ImportNotebook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ const ImportNotebook: React.FC<FormProps> = (props) => {
}

webuiNavigate('/session');

};

return (
Expand Down
Loading