mirror of
https://github.com/aaru-dps/Aaru.Server.git
synced 2026-09-24 15:44:26 +00:00
Add Upload Report functionality and navigation
This commit is contained in:
@@ -263,6 +263,10 @@
|
||||
<span class="bi bi-file-earmark-text-nav-menu"></span>
|
||||
<span class="nav-link-text">Uploaded Reports</span>
|
||||
</NavLink>
|
||||
<NavLink class="nav-link" href="/admin/reports/upload" style="@GetLinkVisibility("upload report")">
|
||||
<span class="bi bi-cloud-arrow-up-nav-menu"></span>
|
||||
<span class="nav-link-text">Upload Report</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -744,9 +748,14 @@
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z'/%3E%3Cpath d='M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-chevron-down-nav-menu
|
||||
.bi-cloud-arrow-up-nav-menu
|
||||
{
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M7.646 5.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 6.707V10.5a.5.5 0 0 1-1 0V6.707L6.354 7.854a.5.5 0 1 1-.708-.708l2-2z'/%3E%3Cpath d='M4.406 3.504a.75.75 0 0 1 .992.06l2.286 2.393.992-1.391a.75.75 0 1 1 1.23.888l-1.868 2.612a.75.75 0 0 1-1.06.06L4.338 4.496a.75.75 0 0 1 .068-1.061z'/%3E%3Cpath d='M11.5 6h.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h.5a.75.75 0 0 0 0-1.5h-.5A3.5 3.5 0 0 0 1.5 13v5a3.5 3.5 0 0 0 3.5 3.5h7a3.5 3.5 0 0 0 3.5-3.5v-5a3.5 3.5 0 0 0-3.5-3.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
{
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E")
|
||||
;
|
||||
}
|
||||
|
||||
.bi-chevron-right-nav-menu
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
</h1>
|
||||
<p class="text-secondary mb-0">Manage device identification reports</p>
|
||||
</div>
|
||||
<a href="/admin/reports/upload" class="btn btn-success">
|
||||
<i class="bi bi-cloud-arrow-up"></i> Upload Report
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter Bar -->
|
||||
|
||||
82
Aaru.Server/Components/Admin/Pages/Reports/Upload.razor
Normal file
82
Aaru.Server/Components/Admin/Pages/Reports/Upload.razor
Normal file
@@ -0,0 +1,82 @@
|
||||
@page "/admin/reports/upload"
|
||||
@attribute [Authorize]
|
||||
@layout AdminLayout
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Upload Device Report</PageTitle>
|
||||
|
||||
<div class="container-fluid px-4 py-4">
|
||||
<!-- Header Section -->
|
||||
<div class="mb-4">
|
||||
<h1 class="h2 mb-1 text-light">
|
||||
<i class="bi bi-cloud-arrow-up-nav-menu"></i> Upload Device Report
|
||||
</h1>
|
||||
<p class="text-secondary mb-0">Upload a device identification report in JSON format</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card bg-dark shadow-sm border-secondary">
|
||||
<div class="card-header bg-info text-dark">
|
||||
<h5 class="mb-0">Select and Upload Report</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if(!string.IsNullOrEmpty(_errorMessage))
|
||||
{
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
@_errorMessage
|
||||
<button type="button" class="btn-close" @onclick="@(() => _errorMessage = null)"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(!string.IsNullOrEmpty(_successMessage))
|
||||
{
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
@_successMessage
|
||||
<button type="button" class="btn-close" @onclick="@(() => _successMessage = null)"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="jsonFile" class="form-label text-light">Select JSON Report File</label>
|
||||
<InputFile class="form-control bg-dark text-light border-secondary" id="jsonFile" accept=".json"
|
||||
@ref="_fileInput" OnChange="HandleFileSelected" />
|
||||
<small class="form-text text-secondary">
|
||||
Select a valid device report JSON file (.json)
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@if(_fileSelected)
|
||||
{
|
||||
<div class="mb-4">
|
||||
<h5 class="text-light">File Information</h5>
|
||||
<div class="alert alert-secondary text-dark">
|
||||
<strong>File:</strong> @_selectedFileName<br />
|
||||
<strong>Size:</strong> @FormatFileSize(_selectedFileSize)<br />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-success" @onclick="UploadFile" disabled="@_isUploading">
|
||||
@if(_isUploading)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-2"
|
||||
role="status"
|
||||
aria-hidden="true"></span>
|
||||
<span>Uploading...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="bi bi-cloud-arrow-up"></i>
|
||||
<span>Upload Report</span>
|
||||
}
|
||||
</button>
|
||||
<a class="btn btn-secondary" href="/admin/reports">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
140
Aaru.Server/Components/Admin/Pages/Reports/Upload.razor.cs
Normal file
140
Aaru.Server/Components/Admin/Pages/Reports/Upload.razor.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace Aaru.Server.Components.Admin.Pages.Reports;
|
||||
|
||||
public partial class Upload : ComponentBase
|
||||
{
|
||||
private string _errorMessage;
|
||||
private byte[] _fileContent;
|
||||
|
||||
private InputFile _fileInput;
|
||||
private bool _fileSelected;
|
||||
private bool _isUploading;
|
||||
private string _selectedFileName;
|
||||
private long _selectedFileSize;
|
||||
private string _successMessage;
|
||||
[Inject]
|
||||
private HttpClient Http { get; set; }
|
||||
|
||||
[Inject]
|
||||
private NavigationManager NavigationManager { get; set; }
|
||||
|
||||
private async Task HandleFileSelected(InputFileChangeEventArgs e)
|
||||
{
|
||||
_errorMessage = null;
|
||||
_successMessage = null;
|
||||
_fileSelected = false;
|
||||
|
||||
try
|
||||
{
|
||||
IBrowserFile? file = e.File;
|
||||
|
||||
if(file == null)
|
||||
{
|
||||
_errorMessage = "No file selected.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!file.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_errorMessage = "Please select a valid JSON file (.json)";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const long maxFileSize = 10 * 1024 * 1024; // 10 MB
|
||||
|
||||
if(file.Size > maxFileSize)
|
||||
{
|
||||
_errorMessage = $"File size exceeds the maximum allowed size of {FormatFileSize(maxFileSize)}";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedFileName = file.Name;
|
||||
_selectedFileSize = file.Size;
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
await file.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
|
||||
_fileContent = memoryStream.ToArray();
|
||||
|
||||
_fileSelected = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_errorMessage = $"Error reading file: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UploadFile()
|
||||
{
|
||||
if(!_fileSelected)
|
||||
{
|
||||
_errorMessage = "Please select a file first.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_isUploading = true;
|
||||
_errorMessage = null;
|
||||
_successMessage = null;
|
||||
|
||||
try
|
||||
{
|
||||
string jsonContent = Encoding.UTF8.GetString(_fileContent);
|
||||
|
||||
using var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
var uploadUrl = NavigationManager.ToAbsoluteUri("/api/uploadreportv2").ToString();
|
||||
using HttpResponseMessage response = await Http.PostAsync(uploadUrl, content);
|
||||
|
||||
string? responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if(response.IsSuccessStatusCode && responseContent == "ok")
|
||||
{
|
||||
_successMessage = "Report uploaded successfully!";
|
||||
_fileSelected = false;
|
||||
_selectedFileName = null;
|
||||
_selectedFileSize = 0;
|
||||
_fileContent = null;
|
||||
|
||||
// Reset file input
|
||||
if(_fileInput != null)
|
||||
{
|
||||
// Navigate away after a short delay to show success message
|
||||
await Task.Delay(1500);
|
||||
NavigationManager.NavigateTo("/admin/reports");
|
||||
}
|
||||
}
|
||||
else if(responseContent == "notstats")
|
||||
_errorMessage = "Invalid report format: The uploaded file is not a valid device report.";
|
||||
else
|
||||
_errorMessage = $"Upload failed: {responseContent ?? response.ReasonPhrase}";
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_errorMessage = $"Error uploading file: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUploading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatFileSize(long bytes)
|
||||
{
|
||||
string[] sizes = ["B", "KB", "MB", "GB"];
|
||||
double len = bytes;
|
||||
var order = 0;
|
||||
|
||||
while(len >= 1024 && order < sizes.Length - 1)
|
||||
{
|
||||
order++;
|
||||
len = len / 1024;
|
||||
}
|
||||
|
||||
return $"{len:0.##} {sizes[order]}";
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,8 @@ builder.WebHost.UseSentry(o =>
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||
|
||||
builder.Services.AddScoped<HttpClient>();
|
||||
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
builder.Services.AddScoped<IdentityUserAccessor>();
|
||||
builder.Services.AddScoped<IdentityRedirectManager>();
|
||||
|
||||
Reference in New Issue
Block a user