Skip to content

Commit 2ecde6f

Browse files
committed
feat(FR-2893): disable upload button when host lacks upload-file permission (#7412)
Resolves #7411(FR-2893) test-server: 10.122.10.107 ## Summary `FolderExplorerModalV2` already fetches the caller's host-level permission set via `useMergedAllowedStorageHostPermission`, and that set carries both `download-file` and `upload-file`. Until now only `download-file` was consumed; write/upload/edit were hard-coded to `true` and stayed enabled regardless of host permission. This PR splits the upload gate from the generic write gate, wires V2 to the host `upload-file` capability, and surfaces _why_ the button is disabled via a tooltip. ## Component changes (`BAIFileExplorer` / `ExplorerActionControls`) - New optional `enableUpload?: boolean` prop on `BAIFileExplorer` and `ExplorerActionControls`. - `enableUpload` gates: upload dropdown, mobile upload button, drag-drop overlay. - `enableWrite` continues to gate: mkdir, create-file, inline rename. - Default is `enableUpload = false` (opt-in), matching the convention of the other capability props (`enableDownload` / `enableDelete` / `enableWrite` / `enableEdit`). - When `enableUpload` is falsy, the upload button's tooltip shows `comp:FileExplorer.NoUploadPermissionForHost` so users understand why the action is disabled. ## V2 wiring (`FolderExplorerModalV2`) ```tsx const hasUploadContentPermission = _.includes( unitedAllowedPermissionByVolume[vfolderNode?.host ?? ''], 'upload-file', ); ... enableWrite={hasWriteContentPermission} // = true (unchanged; FR-2619 follow-up) enableUpload={hasUploadContentPermission} // new gate enableEdit={hasUploadContentPermission} // edit save = overwrite upload ``` `enableEdit` is gated by `upload-file` because the in-app text editor saves by overwriting the file via the upload API (`VFolderTextFileEditorModal` → `uploadFiles`). If the user lacks `upload-file`, the save would fail server-side anyway; disabling upfront keeps the UX honest. ## V1 wiring (`FolderExplorerModal`) Since the new default is opt-in (`enableUpload = false`), V1 now explicitly passes `enableUpload={hasWriteContentPermission}` to preserve its previous bundled behavior. V1 still gates by `write_content` from the vfolder entity permission — adopting the host `upload-file` check would broaden V1's behavior change and is out of scope for this PR. ## i18n New key `comp:FileExplorer.NoUploadPermissionForHost` added to `en.json` (source) and translated into all 20 target languages via the `fw:i18n-translator` agent (de, el, es, fi, fr, id, it, ja, ko, mn, ms, pl, pt, pt-BR, ru, th, tr, vi, zh-CN, zh-TW). Korean: "호스트에 대한 업로드 권한이 없습니다." ## Out of scope - `hasWriteContentPermission` and `hasDeleteContentPermission` remain hard-coded to `true`. There is no host-level permission for mkdir / create-file / rename / delete today; FR-2619 will revisit when the backend exposes the effective permission set. - Separately, the storage host currently accepts uploads even when `upload-file` is not granted (server-side gap, observed during the Hyundai Mobis investigation). That belongs on the backend and is being tracked separately. ## Test plan - [ ] V2 explorer — user without `upload-file` on the folder's host: - Upload dropdown, mobile upload button, drag-drop overlay, and file-edit save are all disabled. - Hovering the upload button shows the "no upload permission" tooltip (and the localized version when the UI language is switched). - [ ] V2 explorer — mkdir / create-file / inline rename remain enabled regardless of `upload-file`. - [ ] V2 explorer — user with `upload-file`: all upload and edit actions behave as before. - [ ] V2 explorer — `download-file` gating is unchanged. - [ ] V1 `FolderExplorerModal` — no behavior change: `write_content` continues to gate upload, mkdir, create-file, rename, and edit. Same disabled-vs-enabled outcome as `main`.
1 parent 32dbd20 commit 2ecde6f

25 files changed

Lines changed: 61 additions & 5 deletions

packages/backend.ai-ui/src/components/baiClient/FileExplorer/BAIFileExplorer.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ export interface BAIFileExplorerProps {
5858
enableDownload?: boolean;
5959
enableDelete?: boolean;
6060
enableWrite?: boolean;
61+
// Gates upload entry points (upload dropdown + drag-drop). Defaults to
62+
// `enableWrite` for backwards compatibility — callers that need to gate
63+
// upload independently (e.g., on the `upload-file` host permission) should
64+
// pass this explicitly.
65+
enableUpload?: boolean;
6166
enableEdit?: boolean;
6267
onChangeFetchKey?: (fetchKey: string) => void;
6368
ref?: React.Ref<BAIFileExplorerRef>;
@@ -81,6 +86,7 @@ const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
8186
enableDownload = false,
8287
enableDelete = false,
8388
enableWrite = false,
89+
enableUpload = false,
8490
enableEdit = false,
8591
onDeleteFilesInBackground,
8692
deletingFilePaths,
@@ -301,7 +307,7 @@ const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
301307
currentPath,
302308
}}
303309
>
304-
{isDragMode && (
310+
{isDragMode && enableUpload && (
305311
<DragAndDrop
306312
portalContainer={fileDropContainerRef?.current || undefined}
307313
onUpload={(files, currentPath) => onUpload(files, currentPath)}
@@ -326,6 +332,7 @@ const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
326332
enableDownload={enableDownload}
327333
enableDelete={enableDelete}
328334
enableWrite={enableWrite}
335+
enableUpload={enableUpload}
329336
onUpload={(files, currentPath) => onUpload(files, currentPath)}
330337
onDeleteFilesInBackground={onDeleteFilesInBackground}
331338
onClearSelection={() => setSelectedItems([])}

packages/backend.ai-ui/src/components/baiClient/FileExplorer/ExplorerActionControls.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ interface ExplorerActionControlsProps {
5151
enableDownload?: boolean;
5252
enableDelete?: boolean;
5353
enableWrite?: boolean;
54+
// Gates the upload entry points (dropdown + drag-drop). The corresponding
55+
// server operation is `upload-file` on the storage host, which is distinct
56+
// from generic write capability (mkdir / create-file / rename) and from
57+
// file edit (which is also an upload underneath). Defaults to `enableWrite`
58+
// so callers that don't pass it explicitly keep the previous bundled
59+
// behavior.
60+
enableUpload?: boolean;
5461
// onClickRefresh?: (key: string) => void;
5562
extra?: React.ReactNode;
5663
}
@@ -64,6 +71,7 @@ const ExplorerActionControls: React.FC<ExplorerActionControlsProps> = ({
6471
enableDownload = false,
6572
enableDelete = false,
6673
enableWrite = false,
74+
enableUpload = enableWrite,
6775
extra,
6876
}) => {
6977
const { t } = useTranslation();
@@ -178,7 +186,7 @@ const ExplorerActionControls: React.FC<ExplorerActionControlsProps> = ({
178186
</Button>
179187
</Tooltip>
180188
<Dropdown
181-
disabled={!enableWrite}
189+
disabled={!enableUpload}
182190
trigger={['click']}
183191
open={openUploadDropdown}
184192
onOpenChange={toggleUploadDropdown}
@@ -237,8 +245,16 @@ const ExplorerActionControls: React.FC<ExplorerActionControlsProps> = ({
237245
);
238246
}}
239247
>
240-
<Tooltip title={!lg && t('general.button.Upload')}>
241-
<Button icon={<UploadOutlined />} disabled={!enableWrite}>
248+
<Tooltip
249+
title={
250+
!enableUpload
251+
? t('comp:FileExplorer.NoUploadPermissionForHost')
252+
: !lg
253+
? t('general.button.Upload')
254+
: undefined
255+
}
256+
>
257+
<Button icon={<UploadOutlined />} disabled={!enableUpload}>
242258
{lg && t('general.button.Upload')}
243259
</Button>
244260
</Tooltip>

packages/backend.ai-ui/src/locale/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@
333333
"ModifiedAt": "Modifiziert bei",
334334
"MoreOptions": "Weitere Optionen",
335335
"Name": "Name",
336+
"NoUploadPermissionForHost": "Sie haben keine Hochlade-Berechtigung für diesen Host.",
336337
"PleaseEnterAFileName": "Please enter the file name.",
337338
"PleaseEnterAFolderName": "Bitte geben Sie den Ordneramen ein.",
338339
"RenameSuccess": "Der Name wurde erfolgreich geändert.",

packages/backend.ai-ui/src/locale/el.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@
333333
"ModifiedAt": "Τροποποιημένος",
334334
"MoreOptions": "Περισσότερες επιλογές",
335335
"Name": "Ονομα",
336+
"NoUploadPermissionForHost": "Δεν έχετε άδεια μεταφόρτωσης για αυτόν τον κεντρικό χώρο αποθήκευσης.",
336337
"PleaseEnterAFileName": "Please enter the file name.",
337338
"PleaseEnterAFolderName": "Εισαγάγετε το όνομα του φακέλου.",
338339
"RenameSuccess": "Το όνομα έχει αλλάξει με επιτυχία.",

packages/backend.ai-ui/src/locale/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@
339339
"ModifiedAt": "Modified At",
340340
"MoreOptions": "More options",
341341
"Name": "Name",
342+
"NoUploadPermissionForHost": "You don't have upload permission for this host.",
342343
"PleaseEnterAFileName": "Please enter the file name.",
343344
"PleaseEnterAFolderName": "Please enter the folder name.",
344345
"RenameSuccess": "The name has been successfully changed.",

packages/backend.ai-ui/src/locale/es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@
333333
"ModifiedAt": "Modificado en",
334334
"MoreOptions": "Más opciones",
335335
"Name": "Nombre",
336+
"NoUploadPermissionForHost": "No tiene permiso de carga para este host.",
336337
"PleaseEnterAFileName": "Please enter the file name.",
337338
"PleaseEnterAFolderName": "Ingrese el nombre de la carpeta.",
338339
"RenameSuccess": "El nombre ha sido cambiado con éxito.",

packages/backend.ai-ui/src/locale/fi.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@
333333
"ModifiedAt": "Muutettu",
334334
"MoreOptions": "Lisää vaihtoehtoja",
335335
"Name": "Nimi",
336+
"NoUploadPermissionForHost": "Sinulla ei ole latausoikeutta tälle tallennuspalvelimelle.",
336337
"PleaseEnterAFileName": "Please enter the file name.",
337338
"PleaseEnterAFolderName": "Anna kansionimi.",
338339
"RenameSuccess": "Nimi on muutettu onnistuneesti.",

packages/backend.ai-ui/src/locale/fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@
333333
"ModifiedAt": "Modifié à",
334334
"MoreOptions": "Plus d'options",
335335
"Name": "Nom",
336+
"NoUploadPermissionForHost": "Vous n'avez pas la permission de téléverser sur cet hôte.",
336337
"PleaseEnterAFileName": "Please enter the file name.",
337338
"PleaseEnterAFolderName": "Veuillez saisir le nom du dossier.",
338339
"RenameSuccess": "Le nom a été modifié avec succès.",

packages/backend.ai-ui/src/locale/id.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@
333333
"ModifiedAt": "Dimodifikasi di",
334334
"MoreOptions": "Opsi lainnya",
335335
"Name": "Nama",
336+
"NoUploadPermissionForHost": "Anda tidak memiliki izin unggah untuk host ini.",
336337
"PleaseEnterAFileName": "Please enter the file name.",
337338
"PleaseEnterAFolderName": "Harap masukkan nama folder.",
338339
"RenameSuccess": "Namanya telah berhasil diubah.",

packages/backend.ai-ui/src/locale/it.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@
333333
"ModifiedAt": "Modificato a",
334334
"MoreOptions": "Più opzioni",
335335
"Name": "Nome",
336+
"NoUploadPermissionForHost": "Non si dispone del permesso di caricamento per questo host.",
336337
"PleaseEnterAFileName": "Please enter the file name.",
337338
"PleaseEnterAFolderName": "Immettere il nome della cartella.",
338339
"RenameSuccess": "Il nome è stato cambiato con successo.",

0 commit comments

Comments
 (0)