@page "/storage" @inject IHttpClientFactory HttpFactory @inject NavigationManager Nav @using System.Net.Http.Json @using Pldpro.Web.Models Storage S3 Storage Buckets Erstellen @if (buckets is null) { (lädt...) } else if (!buckets.Any()) { (keine Buckets) } else { @foreach (var b in buckets) { @b.Name } } @if (!string.IsNullOrEmpty(selectedBucket)) { Objekte in '@selectedBucket' Key Größe Geändert @context.Key @context.Size @context.LastModified Download @* Name = letzter Segmentteil des Keys *@ @{ var fileName = context.Key.Split('/', StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? context.Key; var encodedName = Uri.EscapeDataString(fileName); } Download by Name Löschen } @code { private record BucketVm(string Name, DateTime? CreationDate); private record ObjectVm(string Key, long? Size, DateTime? LastModified); private List? buckets; private List? objects; private string? selectedBucket; private string newBucketName = ""; private string? uploadPath; // optionaler Pfad private const long StreamLimit = 512L * 1024 * 1024; // 512 MB (Program.cs erhöht Multipart-Limit) private HttpClient? Http; protected override Task OnInitializedAsync() { Http = HttpFactory.CreateClient(); Http.BaseAddress = new Uri(Nav.BaseUri); // für relative URLs wie "/api/storage/..." return LoadBuckets(); } private async Task LoadBuckets() { var data = await Http.GetFromJsonAsync>("/api/storage/buckets"); buckets = data ?? new(); StateHasChanged(); } private async Task SelectBucket(string name) { selectedBucket = name; objects = await Http.GetFromJsonAsync>($"/api/storage/buckets/{name}/objects") ?? new(); } private async Task CreateBucket() { if (string.IsNullOrWhiteSpace(newBucketName)) return; await Http.PostAsJsonAsync("/api/storage/buckets", new S3CreateBucketDto { BucketName = newBucketName! }); newBucketName = ""; await LoadBuckets(); } private async Task OnFilesSelected(InputFileChangeEventArgs e) { if (string.IsNullOrEmpty(selectedBucket)) return; foreach (var file in e.GetMultipleFiles()) { using var stream = file.OpenReadStream(StreamLimit); using var content = new MultipartFormDataContent(); content.Add(new StreamContent(stream), "file", file.Name); if (!string.IsNullOrWhiteSpace(uploadPath)) content.Add(new StringContent(uploadPath!), "path"); var resp = await Http.PostAsync($"/api/storage/buckets/{selectedBucket}/upload", content); resp.EnsureSuccessStatusCode(); } // Refresh list objects = await Http.GetFromJsonAsync>($"/api/storage/buckets/{selectedBucket}/objects") ?? new(); } private string GetDownloadUrl(string key) { // URL-encode für sichere Übergabe in Catch-all Route var encodedKey = Uri.EscapeDataString(key); return $"/api/storage/buckets/{selectedBucket}/download/{encodedKey}"; } private async Task ConfirmAndDelete(string key) { // Simple Bestätigung; alternativ MudDialog verwenden var really = await JSConfirm($"Objekt löschen?\n\nBucket: {selectedBucket}\nKey: {key}"); if (really) { await DeleteObject(key); } } private async Task DeleteObject(string key) { if (string.IsNullOrEmpty(selectedBucket)) return; var encodedKey = Uri.EscapeDataString(key); var url = $"/api/storage/buckets/{selectedBucket}/objects/{encodedKey}"; var resp = await Http!.DeleteAsync(url); if (resp.IsSuccessStatusCode) { // Liste aktualisieren objects = await Http!.GetFromJsonAsync>($"/api/storage/buckets/{selectedBucket}/objects") ?? new(); StateHasChanged(); } else { var msg = await resp.Content.ReadAsStringAsync(); throw new InvalidOperationException($"Delete fehlgeschlagen: {(int)resp.StatusCode} {resp.ReasonPhrase}\n{msg}"); } } // Sehr einfache JS-Confirm-Hilfe (füge IJSRuntime-Injection hinzu) [Inject] private IJSRuntime JS { get; set; } = default!; private async Task JSConfirm(string message) => await JS.InvokeAsync("confirm", message); }