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>
<Folder Include="Models\" />
<Folder Include="Repositories\" />
</ItemGroup>

View File

@@ -19,8 +19,8 @@
<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/editor/editor.main.js"></script>
<script src="_framework/blazor.web.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
<script src="_framework/blazor.web.js"></script>
</body>
</html>

View File

@@ -1,6 +1,4 @@
@attribute [StreamRendering]
@using CaddyManager.Contracts.Caddy
@inject ICaddyService CaddyService
<MudDialog>
<DialogContent>
@@ -9,7 +7,8 @@
ShrinkLabel="true"
Disabled="@(!IsNew)"></MudTextField>
<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>
</DialogContent>
<DialogActions>

View File

@@ -1,5 +1,6 @@
using BlazorMonaco.Editor;
using CaddyManager.Contracts.Caddy;
using CaddyManager.Models.Caddy;
using Microsoft.AspNetCore.Components;
using MudBlazor;
@@ -8,31 +9,34 @@ namespace CaddyManager.Components.Pages.CaddyfileEditor;
public partial class CaddyfileEditor : ComponentBase
{
private string _caddyConfigurationContent = string.Empty;
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
private StandaloneCodeEditor _codeEditor = null!;
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
/// <summary>
/// Determines if the Caddy configuration file is new
/// </summary>
private bool IsNew { get; set; }
[Parameter]
public string FileName { get; set; } = string.Empty;
[Parameter] 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()
{
IsNew = string.IsNullOrWhiteSpace(FileName);
if (!IsNew)
{
// Load the content of the Caddy configuration file
_caddyConfigurationContent = CaddyService.GetCaddyConfigurationContent(FileName);
}
return base.OnInitializedAsync();
}
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
{
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();
}

View File

@@ -1,7 +1,5 @@
@page "/caddyfile"
@attribute [StreamRendering]
@using CaddyManager.Contracts.Caddy
@inject ICaddyService CaddyService
<PageTitle>Global Caddyfile</PageTitle>
@@ -12,7 +10,9 @@
</MudText>
</MudCardHeader>
<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/>
</MudCardContent>
<MudCardActions>

View File

@@ -1,11 +1,18 @@
using BlazorMonaco.Editor;
using CaddyManager.Contracts.Caddy;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace CaddyManager.Components.Pages;
public partial class Caddyfile: ComponentBase
public partial class CaddyfilePage : ComponentBase
{
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()
{
@@ -13,7 +20,7 @@ public partial class Caddyfile: ComponentBase
_caddyConfigurationContent = CaddyService.GetCaddyGlobalConfigurationContent();
return base.OnInitializedAsync();
}
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
{
return new StandaloneEditorConstructionOptions
@@ -23,12 +30,21 @@ public partial class Caddyfile: ComponentBase
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()
{
// CaddyService.GetCaddyGlobalConfigurationContent();

View File

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

View File

@@ -1,3 +1,4 @@
using CaddyManager.Contracts.Caddy;
using Microsoft.AspNetCore.Components;
using MudBlazor;
@@ -7,20 +8,28 @@ public partial class ReverseProxiesPage : ComponentBase
{
private List<string> _availableCaddyConfigurations = [];
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();
return base.OnInitializedAsync();
if (firstRender)
{
Refresh();
}
}
/// <summary>
/// Method to help open the dialog to create a new reverse proxy configuration
/// </summary>
/// <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
{
FullWidth = true,
@@ -29,5 +38,21 @@ public partial class ReverseProxiesPage : ComponentBase
{
{ "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]
@inject ICaddyService CaddyService
@inject IDialogService DialogService
<MudListItem T="string" Text="@FileName" OnClick="Edit" OnClickPreventDefault="true">
<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 MudBlazor;
@@ -10,6 +11,12 @@ public partial class ReverseProxyItem : ComponentBase
/// </summary>
[Parameter]
public string FileName { get; set; } = string.Empty;
[Inject]
private IDialogService DialogService { get; set; } = null!;
[Inject]
private ICaddyService CaddyService { get; set; } = null!;
private Task Edit()
{

View File

@@ -1,3 +1,5 @@
using CaddyManager.Models.Caddy;
namespace CaddyManager.Contracts.Caddy;
/// <summary>
@@ -24,4 +26,18 @@ public interface ICaddyService
/// </summary>
/// <returns></returns>
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"))
.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();
// Configure the HTTP request pipeline.

View File

@@ -1,6 +1,7 @@
using CaddyManager.Configurations.Caddy;
using CaddyManager.Contracts.Caddy;
using CaddyManager.Contracts.Configurations;
using CaddyManager.Models.Caddy;
namespace CaddyManager.Services;
@@ -10,10 +11,10 @@ public class CaddyService(IConfigurationsService configurationsService) : ICaddy
/// <summary>
/// File name of the global configuration Caddyfile
/// </summary>
public const string CaddyGlobalConfigName = "Caddyfile";
private const string CaddyGlobalConfigName = "Caddyfile";
private CaddyServiceConfigurations Configurations => configurationsService.CaddyServiceConfigurations;
/// <inheritdoc />
public List<string> GetExistingCaddyConfigurations()
{
@@ -26,18 +27,69 @@ public class CaddyService(IConfigurationsService configurationsService) : ICaddy
/// <inheritdoc />
public string GetCaddyConfigurationContent(string configurationName)
{
var path = configurationName == CaddyGlobalConfigName
? Path.Combine(Configurations.ConfigDir, CaddyGlobalConfigName)
var path = configurationName == CaddyGlobalConfigName
? Path.Combine(Configurations.ConfigDir, CaddyGlobalConfigName)
: Path.Combine(Configurations.ConfigDir, $"{configurationName}.caddy");
if (File.Exists(path))
{
return File.ReadAllText(path);
}
return string.Empty;
}
/// <inheritdoc />
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
});
}