feat(volume-browser): phase 2 - file browser UI
- Browse route: /projects/{id}/volumes/{volId}/browse
- Directory listing with file icons, sizes, dates
- Breadcrumb navigation, click-to-navigate directories
- Download entire volume or folder as ZIP
- Upload files via file picker
- i18n EN/RU for all browser strings
This commit is contained in:
+62
-1
@@ -21,7 +21,8 @@ import type {
|
||||
StandaloneProxy,
|
||||
ValidationResult,
|
||||
Volume,
|
||||
VolumeScopeInfo
|
||||
VolumeScopeInfo,
|
||||
BrowseResult
|
||||
} from './types';
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────
|
||||
@@ -352,6 +353,66 @@ export function deleteVolume(
|
||||
return del<{ deleted: string }>(`/api/projects/${projectId}/volumes/${volId}`);
|
||||
}
|
||||
|
||||
export function browseVolume(
|
||||
projectId: string,
|
||||
volId: string,
|
||||
params?: { path?: string; stage?: string; tag?: string }
|
||||
): Promise<BrowseResult> {
|
||||
const query = new URLSearchParams();
|
||||
if (params?.path) query.set('path', params.path);
|
||||
if (params?.stage) query.set('stage', params.stage);
|
||||
if (params?.tag) query.set('tag', params.tag);
|
||||
const qs = query.toString();
|
||||
return get<BrowseResult>(`/api/projects/${projectId}/volumes/${volId}/browse${qs ? `?${qs}` : ''}`);
|
||||
}
|
||||
|
||||
export function volumeDownloadUrl(
|
||||
projectId: string,
|
||||
volId: string,
|
||||
params?: { path?: string; stage?: string; tag?: string }
|
||||
): string {
|
||||
const query = new URLSearchParams();
|
||||
if (params?.path) query.set('path', params.path);
|
||||
if (params?.stage) query.set('stage', params.stage);
|
||||
if (params?.tag) query.set('tag', params.tag);
|
||||
const token = typeof localStorage !== 'undefined' ? localStorage.getItem('auth_token') : null;
|
||||
if (token) query.set('token', token);
|
||||
const qs = query.toString();
|
||||
return `/api/projects/${projectId}/volumes/${volId}/download${qs ? `?${qs}` : ''}`;
|
||||
}
|
||||
|
||||
export async function uploadToVolume(
|
||||
projectId: string,
|
||||
volId: string,
|
||||
files: FileList,
|
||||
params?: { path?: string; stage?: string; tag?: string }
|
||||
): Promise<{ uploaded: string[]; count: number }> {
|
||||
const query = new URLSearchParams();
|
||||
if (params?.path) query.set('path', params.path);
|
||||
if (params?.stage) query.set('stage', params.stage);
|
||||
if (params?.tag) query.set('tag', params.tag);
|
||||
const qs = query.toString();
|
||||
|
||||
const formData = new FormData();
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
formData.append('files', files[i]);
|
||||
}
|
||||
|
||||
const token = typeof localStorage !== 'undefined' ? localStorage.getItem('auth_token') : null;
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
const res = await fetch(`/api/projects/${projectId}/volumes/${volId}/upload${qs ? `?${qs}` : ''}`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const envelope = await res.json();
|
||||
if (!envelope.success) throw new Error(envelope.error ?? 'Upload failed');
|
||||
return envelope.data;
|
||||
}
|
||||
|
||||
// ── Event Log ───────────────────────────────────────────────────────
|
||||
|
||||
export function fetchEventLog(params?: {
|
||||
|
||||
Reference in New Issue
Block a user