mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-31 04:38:11 +00:00
feat(stack-files): cap directory listings at 1000 + add file-tree filter (#1208)
* feat(stack-files): cap directory listings at 1000 + add file-tree filter
The file-tree route returned every entry in a directory unbounded.
A logs/ or data/ subfolder with rotated artifacts could produce a
multi-megabyte response and a frontend cap at 500 entries silently
hid the rest with no way for the user to find a specific file.
The list route now caps the response at 1000 entries (the audit's
recommended bound), advertises the unfiltered total via
X-Total-Count, and sets X-Truncated when truncation happened. The
service exposes both a bare-array listStackDirectory (unchanged
contract for callers that just want the array) and a paginated
listStackDirectoryPage that returns {entries, total, truncated}.
The FileTree now offers a search input above the scroll area that
filters loaded entries by name (case-insensitive substring). Clearing
the filter restores the full listing. A non-matching filter shows a
short hint instead of an empty pane. The client-side MAX_ENTRIES
matches the server cap so a perfectly-sized directory never shows
the truncation hint.
* fix(stack-files): filter keeps parent dirs when loaded descendants match
The original filter applied per-render-level inside renderEntries, so a
parent directory whose name did not match was filtered out even when one
of its already-loaded children did. The match was then unreachable: the
parent had been removed from the visible list and its children never got
a chance to render.
Compute matching-descendant once per directory by walking the loaded
dirContents map (no extra fetch, bounded by what the user already
expanded). Keep ancestors of any match in the visible list. Auto-expand
those ancestors for the duration of the filter so the match comes into
view without a manual click on every parent.
Filter scope is still 'what is already loaded'; unexpanded subtrees do
not contribute to ancestor-keep until the user expands them. Two new
tests pin both behaviours.
This commit is contained in:
@@ -1374,6 +1374,8 @@ function isSafeUploadFilename(rawName: string): boolean {
|
||||
return path.basename(rawName) === rawName;
|
||||
}
|
||||
|
||||
const DIR_LIST_LIMIT = 1000;
|
||||
|
||||
stacksRouter.get('/:stackName/files', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!requirePermission(req, res, 'stack:read', 'stack', stackName)) return;
|
||||
@@ -1384,9 +1386,22 @@ stacksRouter.get('/:stackName/files', async (req: Request, res: Response) => {
|
||||
const startedAt = Date.now();
|
||||
logFileDiag('list start', { stackName, relPath, nodeId: req.nodeId });
|
||||
try {
|
||||
const entries = await FileSystemService.getInstance(req.nodeId).listStackDirectory(stackName, relPath);
|
||||
logFileDiag('list complete', { stackName, relPath, nodeId: req.nodeId, entries: entries.length, elapsedMs: Date.now() - startedAt });
|
||||
return res.json(entries);
|
||||
const result = await FileSystemService.getInstance(req.nodeId).listStackDirectoryPage(stackName, relPath, { limit: DIR_LIST_LIMIT });
|
||||
// Expose pagination context via headers; the JSON body stays
|
||||
// FileEntry[] for backward compatibility with any direct API caller.
|
||||
res.setHeader('X-Total-Count', String(result.total));
|
||||
res.setHeader('X-Returned-Count', String(result.entries.length));
|
||||
if (result.truncated) res.setHeader('X-Truncated', 'true');
|
||||
logFileDiag('list complete', {
|
||||
stackName,
|
||||
relPath,
|
||||
nodeId: req.nodeId,
|
||||
returned: result.entries.length,
|
||||
total: result.total,
|
||||
truncated: result.truncated,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
});
|
||||
return res.json(result.entries);
|
||||
} catch (err: unknown) {
|
||||
logFileOperation('warn', 'list failed', { nodeId: req.nodeId, errorCode: fsErrorCode(err) });
|
||||
return sendFsError(res, err, 'Failed to list directory');
|
||||
|
||||
Reference in New Issue
Block a user