Merge pull request #98 from Moonlight-Panel/ImageImportExport
Image import export
This commit is contained in:
33
Moonlight/App/Services/FileDownloadService.cs
Normal file
33
Moonlight/App/Services/FileDownloadService.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
|
public class FileDownloadService
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime JSRuntime;
|
||||||
|
|
||||||
|
public FileDownloadService(IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
JSRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloadStream(string fileName, Stream stream)
|
||||||
|
{
|
||||||
|
using var streamRef = new DotNetStreamReference(stream);
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("moonlight.downloads.downloadStream", fileName, streamRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloadBytes(string fileName, byte[] bytes)
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream(bytes);
|
||||||
|
|
||||||
|
await DownloadStream(fileName, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloadString(string fileName, string content)
|
||||||
|
{
|
||||||
|
await DownloadBytes(fileName, Encoding.UTF8.GetBytes(content));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,6 +105,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<StatisticsViewService>();
|
builder.Services.AddScoped<StatisticsViewService>();
|
||||||
builder.Services.AddSingleton<DateTimeService>();
|
builder.Services.AddSingleton<DateTimeService>();
|
||||||
builder.Services.AddSingleton<EventSystem>();
|
builder.Services.AddSingleton<EventSystem>();
|
||||||
|
builder.Services.AddScoped<FileDownloadService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<GoogleOAuth2Service>();
|
builder.Services.AddScoped<GoogleOAuth2Service>();
|
||||||
builder.Services.AddScoped<DiscordOAuth2Service>();
|
builder.Services.AddScoped<DiscordOAuth2Service>();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
@inject ImageRepository ImageRepository
|
@inject ImageRepository ImageRepository
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
|
@inject FileDownloadService FileDownloadService
|
||||||
|
|
||||||
<OnlyAdmin>
|
<OnlyAdmin>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -230,6 +231,11 @@
|
|||||||
<a href="/admin/servers/images" class="btn btn-danger me-3">
|
<a href="/admin/servers/images" class="btn btn-danger me-3">
|
||||||
<TL>Cancel</TL>
|
<TL>Cancel</TL>
|
||||||
</a>
|
</a>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Export"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Exporting"))"
|
||||||
|
CssClasses="btn-primary me-3"
|
||||||
|
OnClick="Export">
|
||||||
|
</WButton>
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||||
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
||||||
CssClasses="btn-success"
|
CssClasses="btn-success"
|
||||||
@@ -350,4 +356,13 @@
|
|||||||
|
|
||||||
await LazyLoader.Reload();
|
await LazyLoader.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Export()
|
||||||
|
{
|
||||||
|
Image.TagsJson = JsonConvert.SerializeObject(Tags);
|
||||||
|
Image.InstallScript = await Editor.GetData();
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(Image, Formatting.Indented);
|
||||||
|
await FileDownloadService.DownloadString(Image.Name + ".json", json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
@page "/admin/servers/images"
|
@page "/admin/servers/images"
|
||||||
|
|
||||||
@using BlazorTable
|
@using BlazorTable
|
||||||
|
@using Logging.Net
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Moonlight.App.Repositories.Servers
|
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
@using System.Text
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
|
||||||
@inject Repository<Image> ImageRepository
|
@inject Repository<Image> ImageRepository
|
||||||
@inject Repository<ImageVariable> ImageVariableRepository
|
@inject Repository<ImageVariable> ImageVariableRepository
|
||||||
@inject Repository<Server> ServerRepository
|
@inject Repository<Server> ServerRepository
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
<OnlyAdmin>
|
<OnlyAdmin>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -23,10 +28,28 @@
|
|||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="card-toolbar">
|
<div class="card-toolbar">
|
||||||
<a href="/admin/servers/images/new" class="btn btn-sm btn-light-success">
|
<a href="/admin/servers/images/new" class="btn btn-sm btn-light-success me-3">
|
||||||
<i class="bx bx-layer-plus"></i>
|
<i class="bx bx-layer-plus"></i>
|
||||||
<TL>New image</TL>
|
<TL>New image</TL>
|
||||||
</a>
|
</a>
|
||||||
|
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
||||||
|
<label for="fileUpload" class="btn btn-sm btn-light-primary @(Uploading ? "disabled" : "")">
|
||||||
|
@if (Uploading)
|
||||||
|
{
|
||||||
|
<span>@(Percent)%</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="svg-icon svg-icon-2">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||||
|
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||||
|
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<TL>Import</TL>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body pt-0">
|
<div class="card-body pt-0">
|
||||||
@@ -86,6 +109,9 @@
|
|||||||
private LazyLoader LazyLoader;
|
private LazyLoader LazyLoader;
|
||||||
|
|
||||||
private Dictionary<Image, int> ServersCount = new();
|
private Dictionary<Image, int> ServersCount = new();
|
||||||
|
|
||||||
|
private bool Uploading = false;
|
||||||
|
private int Percent = 0;
|
||||||
|
|
||||||
private async Task Load(LazyLoader lazyLoader)
|
private async Task Load(LazyLoader lazyLoader)
|
||||||
{
|
{
|
||||||
@@ -127,4 +153,66 @@
|
|||||||
ImageRepository.Delete(image);
|
ImageRepository.Delete(image);
|
||||||
await LazyLoader.Reload();
|
await LazyLoader.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnFileChanged(InputFileChangeEventArgs arg)
|
||||||
|
{
|
||||||
|
await ToastService.CreateProcessToast("upload", SmartTranslateService.Translate("Uploading files"));
|
||||||
|
|
||||||
|
Uploading = true;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
foreach (var browserFile in arg.GetMultipleFiles())
|
||||||
|
{
|
||||||
|
if (browserFile.Size < 1024 * 1024 * 100)
|
||||||
|
{
|
||||||
|
Percent = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stream = browserFile.OpenReadStream(1024 * 1024 * 100);
|
||||||
|
var data = new byte[browserFile.Size];
|
||||||
|
await stream.ReadAsync(data, 0, data.Length);
|
||||||
|
Percent = 50;
|
||||||
|
|
||||||
|
var text = Encoding.UTF8.GetString(data);
|
||||||
|
|
||||||
|
var image = JsonConvert.DeserializeObject<Image>(text);
|
||||||
|
foreach (var imageDockerImage in image.DockerImages)
|
||||||
|
{
|
||||||
|
imageDockerImage.Id = 0;
|
||||||
|
}
|
||||||
|
foreach (var imageVariable in image.Variables)
|
||||||
|
{
|
||||||
|
imageVariable.Id = 0;
|
||||||
|
}
|
||||||
|
image.Id = 0;
|
||||||
|
|
||||||
|
image = ImageRepository.Add(image);
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo("/admin/servers/images/edit/" + image.Id);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while uploading and importing the image"));
|
||||||
|
Logger.Error("Error uploading file");
|
||||||
|
Logger.Error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
await ToastService.UpdateProcessToast("upload", $"{i}/{arg.GetMultipleFiles().Count} {SmartTranslateService.Translate("complete")}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ToastService.Error(SmartTranslateService.Translate("The uploaded file should not be bigger than 100MB"));
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uploading = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
await ToastService.UpdateProcessToast("upload", SmartTranslateService.Translate("Upload complete"));
|
||||||
|
await ToastService.RemoveProcessToast("upload");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -334,5 +334,18 @@
|
|||||||
light.style.animation = "";
|
light.style.animation = "";
|
||||||
light.style.opacity = "0";
|
light.style.opacity = "0";
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
downloads:{
|
||||||
|
downloadStream: async function (fileName, contentStreamReference){
|
||||||
|
const arrayBuffer = await contentStreamReference.arrayBuffer();
|
||||||
|
const blob = new Blob([arrayBuffer]);
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const anchorElement = document.createElement('a');
|
||||||
|
anchorElement.href = url;
|
||||||
|
anchorElement.download = fileName ?? '';
|
||||||
|
anchorElement.click();
|
||||||
|
anchorElement.remove();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user