mirror of
https://github.com/vxcontrol/pentagi.git
synced 2026-08-28 05:56:42 +00:00
feat(flows): surface partial container listings in the Pull dialog
The container file endpoint now returns HTTP 200 with the readable entries
plus a Failures list when some entries can't be stat'd, instead of failing
the whole request. Plumb those failures through useFlowContainerFiles and
show a non-blocking warning banner above the readable files ("N entries
could not be read"), reserving the full error empty-state for a true
listing failure. When nothing is readable, the empty-state says so rather
than "Directory is empty".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1300f60a2b
commit
56e8035b8a
@@ -1,4 +1,4 @@
|
||||
import { ArrowDownToLine, ArrowUp, FolderOpen, Loader2, RefreshCw } from 'lucide-react';
|
||||
import { ArrowDownToLine, ArrowUp, FolderOpen, Loader2, RefreshCw, TriangleAlert } from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type FileNode,
|
||||
} from '@/components/shared/file-manager';
|
||||
import { OverwriteButtons, OverwriteDialog, useOverwrite } from '@/components/shared/overwrite';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteContent,
|
||||
@@ -133,6 +134,7 @@ function FlowFilesPullDialogForm({ cachedFiles, flowId, onClose, onSuccess }: Fl
|
||||
|
||||
const {
|
||||
error: listingError,
|
||||
failures: listingFailures,
|
||||
files,
|
||||
isLoading: isListingLoading,
|
||||
refetch: refetchListing,
|
||||
@@ -331,6 +333,18 @@ function FlowFilesPullDialogForm({ cachedFiles, flowId, onClose, onSuccess }: Fl
|
||||
<EmptyDescription>{listingError.message}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
) : listingFailures.length > 0 ? (
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<TriangleAlert />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Nothing readable here</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
None of the entries in <code>{currentPath}</code> could be read.
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
) : (
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
@@ -424,6 +438,24 @@ function FlowFilesPullDialogForm({ cachedFiles, flowId, onClose, onSuccess }: Fl
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{listingFailures.length > 0 && flatFiles.length > 0 && (
|
||||
<Alert>
|
||||
<TriangleAlert />
|
||||
<AlertTitle>
|
||||
{listingFailures.length} {listingFailures.length === 1 ? 'entry' : 'entries'} could not
|
||||
be read
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
The readable entries are shown below. Skipped:{' '}
|
||||
{listingFailures
|
||||
.slice(0, 5)
|
||||
.map((failure) => failure.name)
|
||||
.join(', ')}
|
||||
{listingFailures.length > 5 ? `, and ${listingFailures.length - 5} more` : ''}.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<FileManager
|
||||
bulkActions={bulkActions}
|
||||
className="h-[360px]"
|
||||
|
||||
@@ -10,7 +10,14 @@ import { CONTAINER_PATH_PREFIX, RESOURCES_PATH_PREFIX, UPLOADS_PATH_PREFIX } fro
|
||||
* Wire shape of `models.ContainerFiles`. `path` echoes back the queried path
|
||||
* when exactly one was requested — empty string for multi-path queries.
|
||||
*/
|
||||
export interface ContainerFileFailure {
|
||||
message: string;
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ContainerFilesResponse {
|
||||
failures?: ContainerFileFailure[];
|
||||
files: RestContainerFile[];
|
||||
path: string;
|
||||
total: number;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { buildPathsQuery } from '@/features/resources/resources-utils';
|
||||
import { api, getApiErrorMessage, unwrapApiResponse } from '@/lib/axios';
|
||||
|
||||
import { FLOW_FILES_CONTAINER_API_PATH } from './flow-files-constants';
|
||||
import { type ContainerFilesResponse, containerFileToFileNode } from './flow-files-utils';
|
||||
import { type ContainerFileFailure, type ContainerFilesResponse, containerFileToFileNode } from './flow-files-utils';
|
||||
|
||||
interface UseFlowContainerFilesParams {
|
||||
flowId: null | string;
|
||||
@@ -21,6 +21,13 @@ interface UseFlowContainerFilesParams {
|
||||
|
||||
interface UseFlowContainerFilesResult {
|
||||
error: Error | null;
|
||||
/**
|
||||
* Entries the backend could not stat (dangling symlink, a file removed
|
||||
* mid-listing, a transient /proc pid). The listing still succeeds with the
|
||||
* readable entries in `files`; these are surfaced so the UI can warn that
|
||||
* the directory is shown partially.
|
||||
*/
|
||||
failures: ContainerFileFailure[];
|
||||
files: FileNode[];
|
||||
isLoading: boolean;
|
||||
/**
|
||||
@@ -46,6 +53,7 @@ interface UseFlowContainerFilesResult {
|
||||
*/
|
||||
export function useFlowContainerFiles({ flowId, paths }: UseFlowContainerFilesParams): UseFlowContainerFilesResult {
|
||||
const [files, setFiles] = useState<FileNode[]>([]);
|
||||
const [failures, setFailures] = useState<ContainerFileFailure[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
@@ -65,6 +73,7 @@ export function useFlowContainerFiles({ flowId, paths }: UseFlowContainerFilesPa
|
||||
if (!flowId || paths.length === 0) {
|
||||
currentTokenRef.current += 1;
|
||||
setFiles([]);
|
||||
setFailures([]);
|
||||
setIsLoading(false);
|
||||
setError(null);
|
||||
|
||||
@@ -89,6 +98,7 @@ export function useFlowContainerFiles({ flowId, paths }: UseFlowContainerFilesPa
|
||||
}
|
||||
|
||||
setFiles(data.files.map(containerFileToFileNode));
|
||||
setFailures(data.failures ?? []);
|
||||
} catch (caught) {
|
||||
if (token !== currentTokenRef.current) {
|
||||
return;
|
||||
@@ -96,6 +106,7 @@ export function useFlowContainerFiles({ flowId, paths }: UseFlowContainerFilesPa
|
||||
|
||||
setError(new Error(getApiErrorMessage(caught, 'Failed to load container files')));
|
||||
setFiles([]);
|
||||
setFailures([]);
|
||||
} finally {
|
||||
if (token === currentTokenRef.current) {
|
||||
setIsLoading(false);
|
||||
@@ -116,6 +127,7 @@ export function useFlowContainerFiles({ flowId, paths }: UseFlowContainerFilesPa
|
||||
|
||||
return {
|
||||
error,
|
||||
failures,
|
||||
files,
|
||||
isLoading,
|
||||
refetch: fetchListing,
|
||||
|
||||
Reference in New Issue
Block a user