using System.Globalization; using CaddyManager.Components.Pages.Generic; using CaddyManager.Contracts.Caddy; using CaddyManager.Contracts.Docker; using CaddyManager.Models.Caddy; using Humanizer; using Microsoft.AspNetCore.Components; using MudBlazor; using CaddyfileEditorComponent = CaddyManager.Components.Pages.Caddy.CaddyfileEditor.CaddyfileEditor; namespace CaddyManager.Components.Pages.Caddy.CaddyReverseProxies; /// /// Page to manage reverse proxy configurations in the form of *.caddy files /// // ReSharper disable once ClassNeverInstantiated.Global public partial class CaddyReverseProxiesPage : ComponentBase { private bool _isProcessing; private List _availableCaddyConfigurations = []; private IReadOnlyCollection _selectedCaddyConfigurations = []; private string _debouncedText = string.Empty; [Inject] private ICaddyService CaddyService { get; set; } = null!; [Inject] private IDockerService DockerService { get; set; } = null!; [Inject] private IDialogService DialogService { get; set; } = null!; [Inject] private ISnackbar Snackbar { get; set; } = null!; protected override void OnAfterRender(bool firstRender) { if (firstRender) { Refresh(); } } /// /// Method to help open the dialog to create a new reverse proxy configuration /// /// private async Task NewReverseProxy() { var dialog = await DialogService.ShowAsync("New configuration", options: new DialogOptions { FullWidth = true, MaxWidth = MaxWidth.Medium }, parameters: new DialogParameters { { p => p.FileName, string.Empty } }); var result = await dialog.Result; if (result is { Data: bool, Canceled: false } && (bool)result.Data) { Refresh(); await RestartCaddy(); } } /// /// Get the latest information from the server /// private void Refresh() { var notSearching = string.IsNullOrWhiteSpace(_debouncedText); _availableCaddyConfigurations = [..CaddyService.GetExistingCaddyConfigurations() .Where(conf => notSearching || conf.FileName.Contains(_debouncedText, StringComparison.OrdinalIgnoreCase) || conf.ReverseProxyHostname.Contains(_debouncedText, StringComparison.OrdinalIgnoreCase)) .OrderBy(conf => conf.FileName)]; StateHasChanged(); } /// /// Have the selected configurations be deleted /// private Task Delete() { var confWord = "configuration".ToQuantity(_selectedCaddyConfigurations.Count, ShowQuantityAs.None); return DialogService.ShowAsync($"Delete {_selectedCaddyConfigurations.Count} {confWord}", options: new DialogOptions { FullWidth = true, MaxWidth = MaxWidth.ExtraSmall }, parameters: new DialogParameters { { p => p.Message, $"Are you sure to delete the selected {confWord}?\n\n" + $"{string.Join("\n", _selectedCaddyConfigurations.Select(c => $"⏵\t{c}"))}" }, { p => p.OnConfirm, EventCallback.Factory.Create(this, () => { var response = CaddyService.DeleteCaddyConfigurations(_selectedCaddyConfigurations.Select(c => c.FileName).ToList()); _selectedCaddyConfigurations = _selectedCaddyConfigurations.Where(c => !response.DeletedConfigurations.Contains(c.FileName)).ToList(); if (response.Success) { Snackbar.Add( $"{CultureInfo.InvariantCulture.TextInfo.ToTitleCase(confWord)} deleted successfully", Severity.Success); Refresh(); } else { Snackbar.Add(response.Message, Severity.Error); } }) }, { p => p.ConfirmText, "Yes" }, { p => p.ConfirmColor, Color.Error }, { p => p.CancelText, "No" } }); } /// /// Restart the Caddy container /// /// private async Task RestartCaddy() { try { _isProcessing = true; StateHasChanged(); Snackbar.Add("Restarting Caddy container", Severity.Info); // Added a small delay for debugging purposes to ensure UI renders await Task.Delay(100); await DockerService.RestartCaddyContainerAsync(); Snackbar.Add("Caddy container restarted successfully", Severity.Success); _isProcessing = false; StateHasChanged(); } catch { Snackbar.Add("Failed to restart the Caddy container", Severity.Error); } } /// /// Handle the interval elapsed event for debounced text input for search functionality. /// /// private void HandleIntervalElapsed(string debouncedText) { // Simply refresh the page with the new debounced text Refresh(); } /// /// Handle the click event for the search bar adornment. If the debounced text is empty, then simply refresh /// to have the search be effective, otherwise, clear the debounced text to reset the search. /// private void HandleSearchBarAdornmentClick() { if (!string.IsNullOrWhiteSpace(_debouncedText)) { _debouncedText = string.Empty; } Refresh(); } }