feat: save caddy file on edit and add new

This commit is contained in:
2025-01-24 12:47:20 +07:00
parent 6c0bbef5d3
commit 760281d377
15 changed files with 221 additions and 43 deletions

View File

@@ -21,7 +21,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Models\" />
<Folder Include="Repositories\" /> <Folder Include="Repositories\" />
</ItemGroup> </ItemGroup>

View File

@@ -19,8 +19,8 @@
<script src="_content/BlazorMonaco/jsInterop.js"></script> <script src="_content/BlazorMonaco/jsInterop.js"></script>
<script src="_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script> <script src="_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
<script src="_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script> <script src="_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="_framework/blazor.web.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script> <script src="_content/MudBlazor/MudBlazor.min.js"></script>
<script src="_framework/blazor.web.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,6 +1,4 @@
@attribute [StreamRendering] @attribute [StreamRendering]
@using CaddyManager.Contracts.Caddy
@inject ICaddyService CaddyService
<MudDialog> <MudDialog>
<DialogContent> <DialogContent>
@@ -9,7 +7,8 @@
ShrinkLabel="true" ShrinkLabel="true"
Disabled="@(!IsNew)"></MudTextField> Disabled="@(!IsNew)"></MudTextField>
<MudText Typo="Typo.caption" Style="padding-left: 18px; padding-bottom: 8px;">File content</MudText> <MudText Typo="Typo.caption" Style="padding-left: 18px; padding-bottom: 8px;">File content</MudText>
<StandaloneCodeEditor ConstructionOptions="@EditorConstructionOptions" <StandaloneCodeEditor @ref="_codeEditor"
ConstructionOptions="@EditorConstructionOptions"
CssClass="caddy-file-editor"></StandaloneCodeEditor> CssClass="caddy-file-editor"></StandaloneCodeEditor>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>

View File

@@ -1,5 +1,6 @@
using BlazorMonaco.Editor; using BlazorMonaco.Editor;
using CaddyManager.Contracts.Caddy; using CaddyManager.Contracts.Caddy;
using CaddyManager.Models.Caddy;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using MudBlazor; using MudBlazor;
@@ -8,31 +9,34 @@ namespace CaddyManager.Components.Pages.CaddyfileEditor;
public partial class CaddyfileEditor : ComponentBase public partial class CaddyfileEditor : ComponentBase
{ {
private string _caddyConfigurationContent = string.Empty; private string _caddyConfigurationContent = string.Empty;
private StandaloneCodeEditor _codeEditor = null!;
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!; [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
/// <summary> /// <summary>
/// Determines if the Caddy configuration file is new /// Determines if the Caddy configuration file is new
/// </summary> /// </summary>
private bool IsNew { get; set; } private bool IsNew { get; set; }
[Parameter] [Parameter] public string FileName { get; set; } = string.Empty;
public string FileName { get; set; } = string.Empty;
[Inject] private ICaddyService CaddyService { get; set; } = null!;
[Inject] private ISnackbar Snackbar { get; set; } = null!;
protected override Task OnInitializedAsync() protected override Task OnInitializedAsync()
{ {
IsNew = string.IsNullOrWhiteSpace(FileName); IsNew = string.IsNullOrWhiteSpace(FileName);
if (!IsNew) if (!IsNew)
{ {
// Load the content of the Caddy configuration file // Load the content of the Caddy configuration file
_caddyConfigurationContent = CaddyService.GetCaddyConfigurationContent(FileName); _caddyConfigurationContent = CaddyService.GetCaddyConfigurationContent(FileName);
} }
return base.OnInitializedAsync(); return base.OnInitializedAsync();
} }
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor) private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
{ {
return new StandaloneEditorConstructionOptions return new StandaloneEditorConstructionOptions
@@ -44,7 +48,25 @@ public partial class CaddyfileEditor : ComponentBase
}; };
} }
private void Submit() => MudDialog.Close(DialogResult.Ok(true)); private async Task Submit()
{
var response = CaddyService.SaveCaddyConfiguration(new CaddySaveConfigurationRequest
{
IsNew = IsNew,
FileName = FileName,
Content = await _codeEditor.GetValue(),
});
if (response.Success)
{
Snackbar.Add($"{FileName} Caddy configuration saved successfully", Severity.Success);
MudDialog.Close(DialogResult.Ok(true));
}
else
{
Snackbar.Add("Failed to save Caddy configuration", Severity.Error);
}
}
private void Cancel() => MudDialog.Cancel(); private void Cancel() => MudDialog.Cancel();
} }

View File

@@ -1,7 +1,5 @@
@page "/caddyfile" @page "/caddyfile"
@attribute [StreamRendering] @attribute [StreamRendering]
@using CaddyManager.Contracts.Caddy
@inject ICaddyService CaddyService
<PageTitle>Global Caddyfile</PageTitle> <PageTitle>Global Caddyfile</PageTitle>
@@ -12,7 +10,9 @@
</MudText> </MudText>
</MudCardHeader> </MudCardHeader>
<MudCardContent> <MudCardContent>
<StandaloneCodeEditor ConstructionOptions="@EditorConstructionOptions" CssClass="caddy-file-editor global-caddy"></StandaloneCodeEditor> <MudText Typo="Typo.caption" Style="padding-left: 18px; padding-bottom: 8px;">File content</MudText>
<StandaloneCodeEditor @ref="_codeEditor" ConstructionOptions="@EditorConstructionOptions"
CssClass="caddy-file-editor global-caddy"></StandaloneCodeEditor>
<MudDivider/> <MudDivider/>
</MudCardContent> </MudCardContent>
<MudCardActions> <MudCardActions>

View File

@@ -1,11 +1,18 @@
using BlazorMonaco.Editor; using BlazorMonaco.Editor;
using CaddyManager.Contracts.Caddy;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace CaddyManager.Components.Pages; namespace CaddyManager.Components.Pages;
public partial class Caddyfile: ComponentBase public partial class CaddyfilePage : ComponentBase
{ {
private string _caddyConfigurationContent = string.Empty; private string _caddyConfigurationContent = string.Empty;
private StandaloneCodeEditor _codeEditor = null!;
[Inject] private ICaddyService CaddyService { get; set; } = null!;
[Inject] private ISnackbar Snackbar { get; set; } = null!;
protected override Task OnInitializedAsync() protected override Task OnInitializedAsync()
{ {
@@ -13,7 +20,7 @@ public partial class Caddyfile: ComponentBase
_caddyConfigurationContent = CaddyService.GetCaddyGlobalConfigurationContent(); _caddyConfigurationContent = CaddyService.GetCaddyGlobalConfigurationContent();
return base.OnInitializedAsync(); return base.OnInitializedAsync();
} }
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor) private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
{ {
return new StandaloneEditorConstructionOptions return new StandaloneEditorConstructionOptions
@@ -23,12 +30,21 @@ public partial class Caddyfile: ComponentBase
Value = _caddyConfigurationContent, Value = _caddyConfigurationContent,
}; };
} }
private void Submit() private async Task Submit()
{ {
// CaddyService.SaveCaddyGlobalConfigurationContent(_caddyConfigurationContent); var response = CaddyService.SaveCaddyGlobalConfiguration(await _codeEditor.GetValue());
if (response.Success)
{
Snackbar.Add("Caddy configuration saved successfully", Severity.Success);
}
else
{
Snackbar.Add("Failed to save Caddy configuration", Severity.Error);
}
} }
private void Cancel() private void Cancel()
{ {
// CaddyService.GetCaddyGlobalConfigurationContent(); // CaddyService.GetCaddyGlobalConfigurationContent();

View File

@@ -1,8 +1,5 @@
@page "/" @page "/"
@attribute [StreamRendering] @attribute [StreamRendering]
@using CaddyManager.Contracts.Caddy
@inject ICaddyService CaddyService
@inject IDialogService DialogService
<PageTitle>Reverse proxy configurations</PageTitle> <PageTitle>Reverse proxy configurations</PageTitle>

View File

@@ -1,3 +1,4 @@
using CaddyManager.Contracts.Caddy;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using MudBlazor; using MudBlazor;
@@ -7,20 +8,28 @@ public partial class ReverseProxiesPage : ComponentBase
{ {
private List<string> _availableCaddyConfigurations = []; private List<string> _availableCaddyConfigurations = [];
private IReadOnlyCollection<string> _selectedCaddyConfigurations = []; private IReadOnlyCollection<string> _selectedCaddyConfigurations = [];
[Inject]
private ICaddyService CaddyService { get; set; } = null!;
[Inject]
private IDialogService DialogService { get; set; } = null!;
protected override Task OnInitializedAsync() protected override void OnAfterRender(bool firstRender)
{ {
_availableCaddyConfigurations = CaddyService.GetExistingCaddyConfigurations(); if (firstRender)
return base.OnInitializedAsync(); {
Refresh();
}
} }
/// <summary> /// <summary>
/// Method to help open the dialog to create a new reverse proxy configuration /// Method to help open the dialog to create a new reverse proxy configuration
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private Task NewReverseProxy() private async Task NewReverseProxy()
{ {
return DialogService.ShowAsync<CaddyfileEditor.CaddyfileEditor>("New configuration", var dialog = await DialogService.ShowAsync<CaddyfileEditor.CaddyfileEditor>("New configuration",
options: new DialogOptions options: new DialogOptions
{ {
FullWidth = true, FullWidth = true,
@@ -29,5 +38,21 @@ public partial class ReverseProxiesPage : ComponentBase
{ {
{ "FileName", string.Empty } { "FileName", string.Empty }
}); });
var result = await dialog.Result;
if (result is { Data: bool, Canceled: false } && (bool)result.Data)
{
Refresh();
}
}
/// <summary>
/// Get the latest information from the server
/// </summary>
private void Refresh()
{
_availableCaddyConfigurations = CaddyService.GetExistingCaddyConfigurations();
StateHasChanged();
} }
} }

View File

@@ -1,7 +1,4 @@
@using CaddyManager.Contracts.Caddy
@attribute [StreamRendering] @attribute [StreamRendering]
@inject ICaddyService CaddyService
@inject IDialogService DialogService
<MudListItem T="string" Text="@FileName" OnClick="Edit" OnClickPreventDefault="true"> <MudListItem T="string" Text="@FileName" OnClick="Edit" OnClickPreventDefault="true">
<MudContainer Class="d-flex flex-row flex-grow-1 gap-4"> <MudContainer Class="d-flex flex-row flex-grow-1 gap-4">

View File

@@ -1,3 +1,4 @@
using CaddyManager.Contracts.Caddy;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using MudBlazor; using MudBlazor;
@@ -10,6 +11,12 @@ public partial class ReverseProxyItem : ComponentBase
/// </summary> /// </summary>
[Parameter] [Parameter]
public string FileName { get; set; } = string.Empty; public string FileName { get; set; } = string.Empty;
[Inject]
private IDialogService DialogService { get; set; } = null!;
[Inject]
private ICaddyService CaddyService { get; set; } = null!;
private Task Edit() private Task Edit()
{ {

View File

@@ -1,3 +1,5 @@
using CaddyManager.Models.Caddy;
namespace CaddyManager.Contracts.Caddy; namespace CaddyManager.Contracts.Caddy;
/// <summary> /// <summary>
@@ -24,4 +26,18 @@ public interface ICaddyService
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
string GetCaddyGlobalConfigurationContent(); string GetCaddyGlobalConfigurationContent();
/// <summary>
/// Method to help save a Caddy configuration file
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
CaddyOperationResponse SaveCaddyConfiguration(CaddySaveConfigurationRequest request);
/// <summary>
/// Method to help save the global Caddyfile configuration
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
CaddyOperationResponse SaveCaddyGlobalConfiguration(string content);
} }

View File

@@ -0,0 +1,17 @@
namespace CaddyManager.Models.Caddy;
/// <summary>
/// Class to wrap the response of a generic Caddy operation
/// </summary>
public class CaddyOperationResponse
{
/// <summary>
/// Indicates if the operation was successful
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Message to describe the operation result to provide more context
/// </summary>
public string Message { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,22 @@
namespace CaddyManager.Models.Caddy;
/// <summary>
/// Wraps the information needed to save a Caddy configuration
/// </summary>
public class CaddySaveConfigurationRequest
{
/// <summary>
/// Indicates if the configuration is new
/// </summary>
public bool IsNew { get; set; } = false;
/// <summary>
/// Name of the Caddy configuration file
/// </summary>
public required string FileName { get; init; } = string.Empty;
/// <summary>
/// Content of the Caddy configuration file
/// </summary>
public string Content { get; set; } = string.Empty;
}

View File

@@ -15,6 +15,15 @@ builder.Services.RegisterAssemblyPublicNonGenericClasses()
.Where(t => t.Name.EndsWith("Service") || t.Name.EndsWith("Repository")) .Where(t => t.Name.EndsWith("Service") || t.Name.EndsWith("Repository"))
.AsPublicImplementedInterfaces(); .AsPublicImplementedInterfaces();
builder.Services.AddSignalR(e => { e.MaximumReceiveMessageSize = 102400000; });
builder.Services.AddMudServices(config =>
{
config.SnackbarConfiguration.VisibleStateDuration = 4000;
config.SnackbarConfiguration.HideTransitionDuration = 100;
config.SnackbarConfiguration.ShowTransitionDuration = 100;
});
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.

View File

@@ -1,6 +1,7 @@
using CaddyManager.Configurations.Caddy; using CaddyManager.Configurations.Caddy;
using CaddyManager.Contracts.Caddy; using CaddyManager.Contracts.Caddy;
using CaddyManager.Contracts.Configurations; using CaddyManager.Contracts.Configurations;
using CaddyManager.Models.Caddy;
namespace CaddyManager.Services; namespace CaddyManager.Services;
@@ -10,10 +11,10 @@ public class CaddyService(IConfigurationsService configurationsService) : ICaddy
/// <summary> /// <summary>
/// File name of the global configuration Caddyfile /// File name of the global configuration Caddyfile
/// </summary> /// </summary>
public const string CaddyGlobalConfigName = "Caddyfile"; private const string CaddyGlobalConfigName = "Caddyfile";
private CaddyServiceConfigurations Configurations => configurationsService.CaddyServiceConfigurations; private CaddyServiceConfigurations Configurations => configurationsService.CaddyServiceConfigurations;
/// <inheritdoc /> /// <inheritdoc />
public List<string> GetExistingCaddyConfigurations() public List<string> GetExistingCaddyConfigurations()
{ {
@@ -26,18 +27,69 @@ public class CaddyService(IConfigurationsService configurationsService) : ICaddy
/// <inheritdoc /> /// <inheritdoc />
public string GetCaddyConfigurationContent(string configurationName) public string GetCaddyConfigurationContent(string configurationName)
{ {
var path = configurationName == CaddyGlobalConfigName var path = configurationName == CaddyGlobalConfigName
? Path.Combine(Configurations.ConfigDir, CaddyGlobalConfigName) ? Path.Combine(Configurations.ConfigDir, CaddyGlobalConfigName)
: Path.Combine(Configurations.ConfigDir, $"{configurationName}.caddy"); : Path.Combine(Configurations.ConfigDir, $"{configurationName}.caddy");
if (File.Exists(path)) if (File.Exists(path))
{ {
return File.ReadAllText(path); return File.ReadAllText(path);
} }
return string.Empty; return string.Empty;
} }
/// <inheritdoc /> /// <inheritdoc />
public string GetCaddyGlobalConfigurationContent() => GetCaddyConfigurationContent(CaddyGlobalConfigName); public string GetCaddyGlobalConfigurationContent() => GetCaddyConfigurationContent(CaddyGlobalConfigName);
/// <inheritdoc />
public CaddyOperationResponse SaveCaddyConfiguration(CaddySaveConfigurationRequest request)
{
if (string.IsNullOrWhiteSpace(request.FileName))
{
return new CaddyOperationResponse
{
Success = false,
Message = "The configuration file name is required"
};
}
var filePath = Path.Combine(Configurations.ConfigDir,
request.FileName == CaddyGlobalConfigName ? CaddyGlobalConfigName : $"{request.FileName}.caddy");
// if in the new mode, we would have to check if the file already exists
if (request.IsNew && File.Exists(filePath))
{
return new CaddyOperationResponse
{
Success = false,
Message = "The configuration file already exists"
};
}
try
{
File.WriteAllText(filePath, request.Content);
return new CaddyOperationResponse
{
Success = true,
Message = "Configuration file saved successfully"
};
}
catch (Exception e)
{
return new CaddyOperationResponse
{
Success = false,
Message = e.Message
};
}
}
/// <inheritdoc />
public CaddyOperationResponse SaveCaddyGlobalConfiguration(string content) => SaveCaddyConfiguration(
new CaddySaveConfigurationRequest
{
FileName = CaddyGlobalConfigName,
Content = content
});
} }