Skip to content

Commit 5d2a259

Browse files
committed
feat(FR-1093): migrate file browser function to react
1 parent 83b6f17 commit 5d2a259

File tree

10 files changed

+185
-30
lines changed

10 files changed

+185
-30
lines changed

config.toml.sample

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ hideAgents = true # If false, show the `Agent Summary` men
2222
force2FA = false # (Deprecated since v25.9.0, This will be replaced by the false setting in the Totp plugin.) If true, user should be register the 2FA to use Backend.AI WebUI.
2323
appDownloadUrl = "" # URL to download the electron app. If blank, https://github.com/lablup/backend.ai-webui/releases/download will be used.
2424
allowAppDownloadPanel = true # If true, display the download WebUI app panel on the summary page.
25-
systemSSHImage = "" # This image is used to launch ssh session from the filebrowser dialog to support fast uploading.
25+
systemSSHImage = "" # This image is used to launch ssh session from the file explorer to support fast uploading.
26+
defaultFileBrowserImage = "" # This image is used to launch file browser session from the file explorer.
2627
directoryBasedUsage = false # If true, display the amount of usage per directory such as folder capacity, and number of files and directories.
2728
isDirectorySizeVisible = true # If false, directory size in folder explorer will show `-`. default value is set to true.
2829
maxCountForPreopenPorts = 10 # The maximum allowed number of preopen ports. If you set this option to 0, the feature of preopen ports is disabled.

react/src/components/FolderExplorerHeader.tsx

Lines changed: 140 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
import { FolderExplorerHeaderFragment$key } from '../__generated__/FolderExplorerHeaderFragment.graphql';
22
import EditableVFolderName from './EditableVFolderName';
33
import VFolderNodeIdenticon from './VFolderNodeIdenticon';
4-
import { Button, Tooltip, Image, Grid, theme, Typography } from 'antd';
5-
import { BAIFlex } from 'backend.ai-ui';
4+
import { Button, Tooltip, Image, Grid, theme, Typography, App } from 'antd';
5+
import { BAIButton, BAIFlex, useErrorMessageResolver } from 'backend.ai-ui';
6+
import _ from 'lodash';
67
import React, { LegacyRef } from 'react';
78
import { useTranslation } from 'react-i18next';
8-
import { graphql, useFragment } from 'react-relay';
9+
import {
10+
fetchQuery,
11+
graphql,
12+
useFragment,
13+
useRelayEnvironment,
14+
} from 'react-relay';
15+
import { FolderExplorerHeader_ImageQuery } from 'src/__generated__/FolderExplorerHeader_ImageQuery.graphql';
16+
import { getImageFullName } from 'src/helper';
17+
import { useSuspendedBackendaiClient } from 'src/hooks';
18+
import {
19+
StartSessionWithDefaultValue,
20+
useStartSession,
21+
} from 'src/hooks/useStartSession';
922

1023
interface FolderExplorerHeaderProps {
1124
vfolderNodeFrgmt?: FolderExplorerHeaderFragment$key | null;
@@ -18,16 +31,26 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
1831
folderExplorerRef,
1932
titleStyle,
2033
}) => {
34+
'use memo';
35+
2136
const { t } = useTranslation();
2237
const { token } = theme.useToken();
2338
const { lg } = Grid.useBreakpoint();
39+
const { message, modal } = App.useApp();
40+
41+
const relayEnv = useRelayEnvironment();
42+
const baiClient = useSuspendedBackendaiClient();
43+
const { getErrorMessage } = useErrorMessageResolver();
44+
const { startSessionWithDefault, upsertSessionNotification } =
45+
useStartSession();
2446

2547
const vfolderNode = useFragment(
2648
graphql`
2749
fragment FolderExplorerHeaderFragment on VirtualFolderNode {
2850
id
2951
user
3052
permission
53+
row_id @required(action: THROW)
3154
unmanaged_path @since(version: "25.04.0")
3255
...VFolderNameTitleNodeFragment
3356
...VFolderNodeIdenticonFragment
@@ -96,7 +119,7 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
96119
{!vfolderNode?.unmanaged_path ? (
97120
<>
98121
<Tooltip title={!lg && t('data.explorer.ExecuteFileBrowser')}>
99-
<Button
122+
<BAIButton
100123
icon={
101124
<Image
102125
width="18px"
@@ -105,13 +128,121 @@ const FolderExplorerHeader: React.FC<FolderExplorerHeaderProps> = ({
105128
preview={false}
106129
/>
107130
}
108-
onClick={() =>
109-
// @ts-ignore
110-
folderExplorerRef.current?._executeFileBrowser()
111-
}
131+
action={async () => {
132+
// FIXME: Currently, file browser filtering by server-side is not supported.
133+
await fetchQuery<FolderExplorerHeader_ImageQuery>(
134+
relayEnv,
135+
graphql`
136+
query FolderExplorerHeader_ImageQuery(
137+
$installed: Boolean
138+
) {
139+
images(is_installed: $installed) {
140+
id
141+
tag
142+
registry
143+
architecture
144+
name @deprecatedSince(version: "24.12.0")
145+
namespace @since(version: "24.12.0")
146+
labels {
147+
key
148+
value
149+
}
150+
tags @since(version: "24.12.0") {
151+
key
152+
value
153+
}
154+
}
155+
}
156+
`,
157+
{
158+
installed: true,
159+
},
160+
{
161+
fetchPolicy: 'store-or-network',
162+
},
163+
)
164+
.toPromise()
165+
.then((response) =>
166+
response?.images?.filter((image) =>
167+
image?.labels?.find(
168+
(label) =>
169+
label?.key === 'ai.backend.service-ports' &&
170+
label?.value?.toLowerCase().includes('filebrowser'),
171+
),
172+
),
173+
)
174+
.then(async (filebrowserImages) => {
175+
if (_.isEmpty(filebrowserImages)) {
176+
message.error(
177+
t('data.explorer.NoImagesSupportingFileBrowser'),
178+
);
179+
return;
180+
}
181+
182+
const fileBrowserFormValue: StartSessionWithDefaultValue =
183+
{
184+
sessionName: `filebrowser-${vfolderNode?.row_id.split('-')[0]}`,
185+
sessionType: 'interactive',
186+
// use default file browser image if configured and allowed
187+
...(baiClient._config?.defaultFileBrowserImage &&
188+
baiClient._config?.allow_manual_image_name_for_session
189+
? {
190+
environments: {
191+
manual:
192+
baiClient._config.defaultFileBrowserImage,
193+
},
194+
}
195+
: // otherwise use the first image found
196+
{
197+
environments: {
198+
version:
199+
getImageFullName(filebrowserImages?.[0]) ||
200+
'',
201+
},
202+
}),
203+
allocationPreset: 'minimum-required',
204+
cluster_mode: 'single-node',
205+
cluster_size: 1,
206+
mount_ids: [
207+
vfolderNode?.row_id?.replaceAll('-', '') || '',
208+
],
209+
resource: {
210+
cpu: 1,
211+
mem: '0.5g',
212+
},
213+
};
214+
215+
await startSessionWithDefault(fileBrowserFormValue)
216+
.then((results) => {
217+
if (
218+
results?.fulfilled &&
219+
results.fulfilled.length > 0
220+
) {
221+
upsertSessionNotification(results.fulfilled);
222+
}
223+
if (
224+
results?.rejected &&
225+
results.rejected.length > 0
226+
) {
227+
const error = results.rejected[0].reason;
228+
modal.error({
229+
title: error?.title,
230+
content: getErrorMessage(error),
231+
});
232+
}
233+
})
234+
.catch((error) => {
235+
console.error(
236+
'Unexpected error during session creation:',
237+
error,
238+
);
239+
message.error(t('error.UnexpectedError'));
240+
});
241+
});
242+
}}
112243
>
113244
{lg && t('data.explorer.ExecuteFileBrowser')}
114-
</Button>
245+
</BAIButton>
115246
</Tooltip>
116247
<Tooltip title={!lg && t('data.explorer.RunSSH/SFTPserver')}>
117248
<Button

react/src/components/ModelTryContentButton.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -202,21 +202,25 @@ const ModelTryContentButton: React.FC<ModelTryContentButtonProps> = ({
202202
// FIXME: manually convert to string since server-side only allows [str,str] tuple
203203
cpu: values.resource.cpu.toString(),
204204
mem: values.resource.mem,
205-
...(values.resource.accelerator > 0
205+
...(values.resource?.acceleratorType &&
206+
values.resource.accelerator &&
207+
values.resource.accelerator > 0
206208
? {
207209
[values.resource.acceleratorType]:
208210
// FIXME: manually convert to string since server-side only allows [str,str] tuple
209211
values.resource.accelerator.toString(),
210212
}
211213
: undefined),
212214
},
213-
resource_opts: {
214-
shmem:
215-
compareNumberWithUnits(values.resource.mem, '4g') > 0 &&
216-
compareNumberWithUnits(values.resource.shmem, '1g') < 0
217-
? '1g'
218-
: values.resource.shmem,
219-
},
215+
...(values.resource.shmem && {
216+
resource_opts: {
217+
shmem:
218+
compareNumberWithUnits(values.resource.mem, '4g') > 0 &&
219+
compareNumberWithUnits(values.resource.shmem, '1g') < 0
220+
? '1g'
221+
: values.resource.shmem,
222+
},
223+
}),
220224
},
221225
};
222226
return baiSignedRequestWithPromise({

react/src/components/ServiceLauncherPageContent.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,21 +335,23 @@ const ServiceLauncherPageContent: React.FC<ServiceLauncherPageContentProps> = ({
335335
// FIXME: manually convert to string since server-side only allows [str,str] tuple
336336
cpu: values.resource.cpu?.toString(),
337337
mem: values.resource.mem,
338-
...(values.resource.accelerator > 0
338+
...(values.resource?.acceleratorType &&
339+
values.resource?.accelerator &&
340+
values.resource.accelerator > 0
339341
? {
340342
[values.resource.acceleratorType]:
341343
// FIXME: manually convert to string since server-side only allows [str,str] tuple
342344
values.resource.accelerator?.toString(),
343345
}
344346
: undefined),
345347
},
346-
resource_opts: {
348+
...(values.resource.shmem && {
347349
shmem:
348350
compareNumberWithUnits(values.resource.mem, '4g') > 0 &&
349351
compareNumberWithUnits(values.resource.shmem, '1g') < 0
350352
? '1g'
351353
: values.resource.shmem,
352-
},
354+
}),
353355
},
354356
};
355357
return baiSignedRequestWithPromise({
@@ -487,7 +489,9 @@ const ServiceLauncherPageContent: React.FC<ServiceLauncherPageContentProps> = ({
487489
resource_slots: JSON.stringify({
488490
cpu: values.resource.cpu,
489491
mem: values.resource.mem,
490-
...(values.resource.accelerator > 0
492+
...(values.resource?.acceleratorType &&
493+
values.resource?.accelerator &&
494+
values.resource.accelerator > 0
491495
? {
492496
[values.resource.acceleratorType]:
493497
values.resource.accelerator,

react/src/components/ServiceValidationView.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,22 @@ const ServiceValidationView: React.FC<ServiceValidationModalProps> = ({
104104
// FIXME: manually convert to string since server-side only allows [str,str] tuple
105105
cpu: values.resource.cpu.toString(),
106106
mem: values.resource.mem,
107-
...(values.resource.accelerator > 0
107+
...(values.resource?.acceleratorType &&
108+
values.resource?.accelerator &&
109+
values.resource.accelerator > 0
108110
? {
109111
[values.resource.acceleratorType]:
110112
values.resource.accelerator,
111113
}
112114
: undefined),
113115
},
114-
resource_opts: {
116+
...(values.resource.shmem && {
115117
shmem:
116118
compareNumberWithUnits(values.resource.mem, '4g') > 0 &&
117119
compareNumberWithUnits(values.resource.shmem, '1g') < 0
118120
? '1g'
119121
: values.resource.shmem,
120-
},
122+
}),
121123
},
122124
};
123125

react/src/components/SessionFormItems/ResourceAllocationFormItems.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ export interface ResourceAllocationFormValue {
5757
resource: {
5858
cpu: number;
5959
mem: string;
60-
shmem: string;
61-
accelerator: number;
62-
acceleratorType: string;
60+
shmem?: string;
61+
accelerator?: number;
62+
acceleratorType?: string;
6363
};
6464
resourceGroup: string;
6565
num_of_sessions?: number;

react/src/helper/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ export const isOutsideRangeWithUnits = (
386386
};
387387

388388
export const getImageFullName = (
389-
image: Image | CommittedImage | EnvironmentImage,
389+
image: DeepPartial<Image | CommittedImage | EnvironmentImage>,
390390
) => {
391391
return image
392392
? `${image.registry}/${image.namespace ?? image.name}:${image.tag}@${image.architecture}`

react/src/hooks/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ type BackendAIConfig = {
592592
enableModelFolders: boolean;
593593
appDownloadUrl: string;
594594
systemSSHImage: string;
595+
defaultFileBrowserImage: string;
595596
fasttrackEndpoint: string;
596597
hideAgents: boolean;
597598
force2FA: boolean;

react/src/hooks/useStartSession.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ export const useStartSession = () => {
214214
cpu: values.resource.cpu,
215215
mem: values.resource.mem,
216216
// Add accelerator only if specified
217-
...(values.resource.accelerator > 0
217+
...(values.resource?.acceleratorType &&
218+
values.resource?.accelerator &&
219+
values.resource?.accelerator > 0
218220
? {
219221
[values.resource.acceleratorType]:
220222
values.resource.accelerator,

src/components/backend-ai-login.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export default class BackendAILogin extends BackendAIPage {
8686
@property({ type: Boolean }) allowAppDownloadPanel = true;
8787
@property({ type: String }) connection_mode = 'SESSION' as ConnectionMode;
8888
@property({ type: String }) systemSSHImage = '';
89+
@property({ type: String }) defaultFileBrowserImage = '';
8990
@property({ type: String }) fasttrackEndpoint = '';
9091
@property({ type: Number }) login_attempt_limit = 500;
9192
@property({ type: Number }) login_block_time = 180;
@@ -754,6 +755,13 @@ export default class BackendAILogin extends BackendAIPage {
754755
value: generalConfig?.systemSSHImage,
755756
} as ConfigValueObject) as string;
756757

758+
// Default file browser image
759+
this.defaultFileBrowserImage = this._getConfigValueByExists(generalConfig, {
760+
valueType: 'string',
761+
defaultValue: '',
762+
value: generalConfig?.defaultFileBrowserImage,
763+
} as ConfigValueObject) as string;
764+
757765
// Enable hide agent flag
758766
this.hideAgents = this._getConfigValueByExists(generalConfig, {
759767
valueType: 'boolean',
@@ -1919,6 +1927,8 @@ export default class BackendAILogin extends BackendAIPage {
19191927
globalThis.backendaiclient._config.allowAppDownloadPanel =
19201928
this.allowAppDownloadPanel;
19211929
globalThis.backendaiclient._config.systemSSHImage = this.systemSSHImage;
1930+
globalThis.backendaiclient._config.defaultFileBrowserImage =
1931+
this.defaultFileBrowserImage;
19221932
globalThis.backendaiclient._config.fasttrackEndpoint =
19231933
this.fasttrackEndpoint;
19241934
globalThis.backendaiclient._config.hideAgents = this.hideAgents;

0 commit comments

Comments
 (0)