Файловое хранилище готово

This commit is contained in:
2026-01-26 11:08:18 +03:00
parent a542e7d2df
commit edf9286071
35 changed files with 377 additions and 497 deletions

View File

@@ -15,6 +15,7 @@
import { onMount } from 'svelte';
import { listFiles, deleteFile } from '../../../services/storageService';
import { addToast } from '../../../lib/toasts';
import { t } from '../../../lib/i18n';
import FileList from '../../../components/storage/FileList.svelte';
import FileUpload from '../../../components/storage/FileUpload.svelte';
// [/SECTION: IMPORTS]
@@ -26,15 +27,30 @@
*/
let files = [];
let isLoading = false;
let activeTab = 'all';
let activeTab = 'backups';
let currentPath = 'backups'; // Relative to storage root
async function loadFiles() {
isLoading = true;
try {
const category = activeTab === 'all' ? null : activeTab;
files = await listFiles(category);
const category = activeTab;
// If we have a currentPath, we use it.
// But if user switched tabs, we should reset currentPath to category root
let effectivePath = currentPath;
if (category && !currentPath.startsWith(category)) {
effectivePath = category;
currentPath = category;
}
// API expects path relative to category root if category is provided
const subpath = (category && effectivePath.startsWith(category))
? effectivePath.substring(category.length).replace(/^\/+/, '')
: effectivePath;
files = await listFiles(category, subpath);
} catch (error) {
addToast(`Failed to load files: ${error.message}`, 'error');
addToast($t.storage.messages.load_failed.replace('{error}', error.message), 'error');
} finally {
isLoading = false;
}
@@ -44,39 +60,73 @@
// [DEF:handleDelete:Function]
/**
* @purpose Handles the file deletion process.
* @param {CustomEvent} event - The delete event containing category and filename.
* @param {CustomEvent} event - The delete event containing category and path.
*/
async function handleDelete(event) {
const { category, filename } = event.detail;
if (!confirm(`Are you sure you want to delete ${filename}?`)) return;
const { category, path, name } = event.detail;
if (!confirm($t.storage.messages.delete_confirm.replace('{name}', name))) return;
try {
await deleteFile(category, filename);
addToast(`File ${filename} deleted.`, 'success');
await deleteFile(category, path);
addToast($t.storage.messages.delete_success.replace('{name}', name), 'success');
await loadFiles();
} catch (error) {
addToast(`Delete failed: ${error.message}`, 'error');
addToast($t.storage.messages.delete_failed.replace('{error}', error.message), 'error');
}
}
// [/DEF:handleDelete:Function]
function handleNavigate(event) {
currentPath = event.detail;
loadFiles();
}
function navigateUp() {
if (!currentPath || currentPath === activeTab) return;
const parts = currentPath.split('/');
parts.pop();
currentPath = parts.join('/') || '';
loadFiles();
}
onMount(loadFiles);
$: if (activeTab) {
// Reset path when switching tabs
if (!currentPath.startsWith(activeTab)) {
currentPath = activeTab;
}
loadFiles();
}
</script>
<!-- [SECTION: TEMPLATE] -->
<div class="container mx-auto p-4 max-w-6xl">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-900">File Storage Management</h1>
<button
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900">{$t.storage.management}</h1>
{#if currentPath}
<div class="flex items-center mt-2 text-sm text-gray-500">
<button on:click={() => { currentPath = activeTab; loadFiles(); }} class="hover:text-indigo-600">{$t.storage.root}</button>
{#each currentPath.split('/').slice(1) as part, i}
<span class="mx-2">/</span>
<button
on:click={() => { currentPath = currentPath.split('/').slice(0, i + 1).join('/'); loadFiles(); }}
class="hover:text-indigo-600 capitalize"
>
{part}
</button>
{/each}
</div>
{/if}
</div>
<div class="flex justify-end mb-4">
<button
on:click={loadFiles}
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
disabled={isLoading}
>
{isLoading ? 'Refreshing...' : 'Refresh'}
{isLoading ? $t.storage.refreshing : $t.storage.refresh}
</button>
</div>
@@ -86,33 +136,45 @@
<!-- Tabs -->
<div class="border-b border-gray-200">
<nav class="-mb-px flex space-x-8">
<button
on:click={() => activeTab = 'all'}
class="py-4 px-1 border-b-2 font-medium text-sm {activeTab === 'all' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}"
<button
on:click={() => activeTab = 'backups'}
class="py-4 px-1 border-b-2 font-medium text-sm {activeTab === 'backups' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}"
>
All Files
{$t.storage.backups}
</button>
<button
on:click={() => activeTab = 'backup'}
class="py-4 px-1 border-b-2 font-medium text-sm {activeTab === 'backup' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}"
<button
on:click={() => activeTab = 'repositorys'}
class="py-4 px-1 border-b-2 font-medium text-sm {activeTab === 'repositorys' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}"
>
Backups
</button>
<button
on:click={() => activeTab = 'repository'}
class="py-4 px-1 border-b-2 font-medium text-sm {activeTab === 'repository' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}"
>
Repositories
{$t.storage.repositories}
</button>
</nav>
</div>
<FileList {files} on:delete={handleDelete} />
<div class="flex items-center mb-2">
{#if currentPath && currentPath !== activeTab}
<button
on:click={navigateUp}
class="mr-4 inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50"
>
<svg class="h-4 w-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Back
</button>
{/if}
</div>
<FileList {files} on:delete={handleDelete} on:navigate={handleNavigate} />
</div>
<!-- Sidebar: Upload -->
<div class="lg:col-span-1">
<FileUpload on:uploaded={loadFiles} />
<FileUpload
category={activeTab}
path={currentPath}
on:uploaded={loadFiles}
/>
</div>
</div>
</div>