feat: add tag extraction functionality to Caddy configuration and display in UI
All checks were successful
Caddy Manager CI build / docker (push) Successful in 49s
All checks were successful
Caddy Manager CI build / docker (push) Successful in 49s
This commit is contained in:
@@ -35,4 +35,11 @@ public interface ICaddyConfigurationParsingService
|
|||||||
/// <param name="caddyfileContent"></param>
|
/// <param name="caddyfileContent"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
List<int> GetReverseProxyPortsFromCaddyfileContent(string caddyfileContent);
|
List<int> GetReverseProxyPortsFromCaddyfileContent(string caddyfileContent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts tags from a Caddyfile content using the format: # Tags: [tag1;tag2;tag3]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caddyfileContent"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
List<string> GetTagsFromCaddyfileContent(string caddyfileContent);
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,11 @@ public class CaddyConfigurationInfo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<int> AggregatedReverseProxyPorts { get; set; } = [];
|
public List<int> AggregatedReverseProxyPorts { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tags extracted from the configuration content using the format: # Tags: [tag1;tag2;tag3]
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Tags { get; set; } = [];
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (obj is not CaddyConfigurationInfo other)
|
if (obj is not CaddyConfigurationInfo other)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public partial class CaddyConfigurationParsingService: ICaddyConfigurationParsin
|
|||||||
hostnames.AddRange(splitHostnames);
|
hostnames.AddRange(splitHostnames);
|
||||||
}
|
}
|
||||||
// Remove duplicates and return the list
|
// Remove duplicates and return the list
|
||||||
return hostnames.Distinct().ToList();
|
return [.. hostnames.Distinct()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -80,6 +80,39 @@ public partial class CaddyConfigurationParsingService: ICaddyConfigurationParsin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.Distinct().ToList();
|
return [.. results.Distinct()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public List<string> GetTagsFromCaddyfileContent(string caddyfileContent)
|
||||||
|
{
|
||||||
|
// Split the content into lines and look for the tags line
|
||||||
|
var lines = caddyfileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
var trimmedLine = line.Trim();
|
||||||
|
if (trimmedLine.StartsWith("#"))
|
||||||
|
{
|
||||||
|
// Remove the # and any leading whitespace, then check if it starts with "tags:"
|
||||||
|
var afterHash = trimmedLine.Substring(1).TrimStart();
|
||||||
|
if (afterHash.StartsWith("tags:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// Extract the part after "tags:"
|
||||||
|
var tagsString = afterHash.Substring(5).Trim(); // 5 = length of "tags:"
|
||||||
|
if (string.IsNullOrWhiteSpace(tagsString))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
// Split by semicolon and clean up each tag
|
||||||
|
return [.. tagsString.Split(';')
|
||||||
|
.Select(tag => tag.Trim())
|
||||||
|
.Where(tag => !string.IsNullOrWhiteSpace(tag))
|
||||||
|
.Distinct()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No tags line found
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,11 @@ public class CaddyService(
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public CaddyConfigurationInfo GetCaddyConfigurationInfo(string configurationName)
|
public CaddyConfigurationInfo GetCaddyConfigurationInfo(string configurationName)
|
||||||
{
|
{
|
||||||
var result = new CaddyConfigurationInfo();
|
var result = new CaddyConfigurationInfo
|
||||||
|
{
|
||||||
|
FileName = configurationName
|
||||||
|
};
|
||||||
|
|
||||||
var content = GetCaddyConfigurationContent(configurationName);
|
var content = GetCaddyConfigurationContent(configurationName);
|
||||||
if (string.IsNullOrWhiteSpace(content))
|
if (string.IsNullOrWhiteSpace(content))
|
||||||
{
|
{
|
||||||
@@ -156,6 +160,7 @@ public class CaddyService(
|
|||||||
result.Hostnames = parsingService.GetHostnamesFromCaddyfileContent(content);
|
result.Hostnames = parsingService.GetHostnamesFromCaddyfileContent(content);
|
||||||
result.ReverseProxyHostname = parsingService.GetReverseProxyTargetFromCaddyfileContent(content);
|
result.ReverseProxyHostname = parsingService.GetReverseProxyTargetFromCaddyfileContent(content);
|
||||||
result.ReverseProxyPorts = parsingService.GetReverseProxyPortsFromCaddyfileContent(content);
|
result.ReverseProxyPorts = parsingService.GetReverseProxyPortsFromCaddyfileContent(content);
|
||||||
|
result.Tags = parsingService.GetTagsFromCaddyfileContent(content);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ public class CaddyConfigurationInfoTests
|
|||||||
info.FileName.Should().Be(string.Empty);
|
info.FileName.Should().Be(string.Empty);
|
||||||
info.AggregatedReverseProxyPorts.Should().NotBeNull();
|
info.AggregatedReverseProxyPorts.Should().NotBeNull();
|
||||||
info.AggregatedReverseProxyPorts.Should().BeEmpty();
|
info.AggregatedReverseProxyPorts.Should().BeEmpty();
|
||||||
|
info.Tags.Should().NotBeNull();
|
||||||
|
info.Tags.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,6 +45,7 @@ public class CaddyConfigurationInfoTests
|
|||||||
var reverseProxyPorts = new List<int> { 8080, 9090 };
|
var reverseProxyPorts = new List<int> { 8080, 9090 };
|
||||||
var fileName = "test-config";
|
var fileName = "test-config";
|
||||||
var aggregatedPorts = new List<int> { 8080, 9090, 3000 };
|
var aggregatedPorts = new List<int> { 8080, 9090, 3000 };
|
||||||
|
var tags = new List<string> { "web", "production", "ssl" };
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var info = new CaddyConfigurationInfo
|
var info = new CaddyConfigurationInfo
|
||||||
@@ -51,7 +54,8 @@ public class CaddyConfigurationInfoTests
|
|||||||
ReverseProxyHostname = reverseProxyHostname,
|
ReverseProxyHostname = reverseProxyHostname,
|
||||||
ReverseProxyPorts = reverseProxyPorts,
|
ReverseProxyPorts = reverseProxyPorts,
|
||||||
FileName = fileName,
|
FileName = fileName,
|
||||||
AggregatedReverseProxyPorts = aggregatedPorts
|
AggregatedReverseProxyPorts = aggregatedPorts,
|
||||||
|
Tags = tags
|
||||||
};
|
};
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -60,6 +64,7 @@ public class CaddyConfigurationInfoTests
|
|||||||
info.ReverseProxyPorts.Should().BeEquivalentTo(reverseProxyPorts);
|
info.ReverseProxyPorts.Should().BeEquivalentTo(reverseProxyPorts);
|
||||||
info.FileName.Should().Be(fileName);
|
info.FileName.Should().Be(fileName);
|
||||||
info.AggregatedReverseProxyPorts.Should().BeEquivalentTo(aggregatedPorts);
|
info.AggregatedReverseProxyPorts.Should().BeEquivalentTo(aggregatedPorts);
|
||||||
|
info.Tags.Should().BeEquivalentTo(tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -665,4 +665,289 @@ api.test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region GetTagsFromCaddyfileContent Tests
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service correctly extracts tags from a basic tags comment line.
|
||||||
|
/// Setup: Provides a Caddyfile content string with a simple tags comment in the correct format.
|
||||||
|
/// Expectation: The service should return a list containing all tags separated by semicolons, enabling proper tag-based organization of Caddy configurations.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithBasicTags_ReturnsCorrectTags()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = @"
|
||||||
|
# Tags: web;production;ssl
|
||||||
|
example.com {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().HaveCount(3);
|
||||||
|
result.Should().Contain("web");
|
||||||
|
result.Should().Contain("production");
|
||||||
|
result.Should().Contain("ssl");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service correctly extracts tags with whitespace variations.
|
||||||
|
/// Setup: Provides a Caddyfile content string with tags containing various whitespace patterns.
|
||||||
|
/// Expectation: The service should trim whitespace and return clean tag names, ensuring consistent tag handling regardless of formatting.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithWhitespaceVariations_ReturnsTrimmedTags()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = @"
|
||||||
|
# Tags: web ; production ; ssl
|
||||||
|
example.com {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().HaveCount(3);
|
||||||
|
result.Should().Contain("web");
|
||||||
|
result.Should().Contain("production");
|
||||||
|
result.Should().Contain("ssl");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service correctly extracts a single tag.
|
||||||
|
/// Setup: Provides a Caddyfile content string with only one tag in the tags comment.
|
||||||
|
/// Expectation: The service should return a list containing exactly one tag, ensuring proper handling of minimal tag configurations.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithSingleTag_ReturnsOneTag()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = @"
|
||||||
|
# Tags: production
|
||||||
|
example.com {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().HaveCount(1);
|
||||||
|
result.Should().Contain("production");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service handles case-insensitive tags comment detection.
|
||||||
|
/// Setup: Provides a Caddyfile content string with various case combinations for the tags comment.
|
||||||
|
/// Expectation: The service should detect tags comments regardless of case, ensuring robust tag parsing across different writing styles.
|
||||||
|
/// </summary>
|
||||||
|
[Theory]
|
||||||
|
[InlineData("# Tags: web;production")]
|
||||||
|
[InlineData("# tags: web;production")]
|
||||||
|
[InlineData("# TAGS: web;production")]
|
||||||
|
[InlineData("#Tags: web;production")]
|
||||||
|
[InlineData("# Tags: web;production")]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithCaseVariations_ReturnsCorrectTags(string tagsLine)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = $@"
|
||||||
|
{tagsLine}
|
||||||
|
example.com {{
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().HaveCount(2);
|
||||||
|
result.Should().Contain("web");
|
||||||
|
result.Should().Contain("production");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service handles empty tags list gracefully.
|
||||||
|
/// Setup: Provides a Caddyfile content string with an empty tags comment.
|
||||||
|
/// Expectation: The service should return an empty list when tags are empty, ensuring proper handling of configurations without tags.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithEmptyTagsList_ReturnsEmptyList()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = @"
|
||||||
|
# Tags:
|
||||||
|
example.com {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service handles duplicate tags by removing them.
|
||||||
|
/// Setup: Provides a Caddyfile content string with duplicate tags in the tags comment.
|
||||||
|
/// Expectation: The service should return a list with unique tags only, ensuring clean tag lists without duplicates.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithDuplicateTags_ReturnsUniqueTags()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = @"
|
||||||
|
# Tags: web;production;web;ssl;production
|
||||||
|
example.com {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().HaveCount(3);
|
||||||
|
result.Should().Contain("web");
|
||||||
|
result.Should().Contain("production");
|
||||||
|
result.Should().Contain("ssl");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service handles content without tags comment gracefully.
|
||||||
|
/// Setup: Provides a Caddyfile content string without any tags comment.
|
||||||
|
/// Expectation: The service should return an empty list when no tags comment is found, ensuring proper handling of non-tagged configurations.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithoutTagsComment_ReturnsEmptyList()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = @"
|
||||||
|
example.com {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service handles malformed tags comment gracefully.
|
||||||
|
/// Setup: Provides a Caddyfile content string with malformed tags comment that doesn't match the expected format.
|
||||||
|
/// Expectation: The service should return an empty list when tags comment is malformed, ensuring robust error handling for invalid tag formats.
|
||||||
|
/// </summary>
|
||||||
|
[Theory]
|
||||||
|
[InlineData("# Tags web;production")] // Missing colon
|
||||||
|
[InlineData("Tags: web;production")] // Missing hash
|
||||||
|
public void GetTagsFromCaddyfileContent_WithMalformedTagsComment_ReturnsEmptyList(string tagsLine)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = $@"
|
||||||
|
{tagsLine}
|
||||||
|
example.com {{
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service handles empty string content gracefully.
|
||||||
|
/// Setup: Provides an empty string as Caddyfile content.
|
||||||
|
/// Expectation: The service should return an empty list when content is empty, ensuring robust error handling for missing configurations.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithEmptyContent_ReturnsEmptyList()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = string.Empty;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service handles complex tag names with special characters.
|
||||||
|
/// Setup: Provides a Caddyfile content string with tags containing special characters and complex names.
|
||||||
|
/// Expectation: The service should correctly extract tags with special characters, ensuring support for comprehensive tag naming schemes.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithComplexTagNames_ReturnsCorrectTags()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = @"
|
||||||
|
# Tags: web-frontend;backend_api;v2.0;production-env;team:alpha
|
||||||
|
example.com {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().HaveCount(5);
|
||||||
|
result.Should().Contain("web-frontend");
|
||||||
|
result.Should().Contain("backend_api");
|
||||||
|
result.Should().Contain("v2.0");
|
||||||
|
result.Should().Contain("production-env");
|
||||||
|
result.Should().Contain("team:alpha");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the parsing service only extracts the first tags comment when multiple are present.
|
||||||
|
/// Setup: Provides a Caddyfile content string with multiple tags comments.
|
||||||
|
/// Expectation: The service should only extract tags from the first valid tags comment, ensuring consistent behavior when multiple tag definitions exist.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetTagsFromCaddyfileContent_WithMultipleTagsComments_ReturnsFirstMatch()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var caddyfileContent = @"
|
||||||
|
# Tags: web;production
|
||||||
|
example.com {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}
|
||||||
|
|
||||||
|
# Tags: api;development
|
||||||
|
api.example.com {
|
||||||
|
reverse_proxy localhost:8081
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetTagsFromCaddyfileContent(caddyfileContent);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().HaveCount(2);
|
||||||
|
result.Should().Contain("web");
|
||||||
|
result.Should().Contain("production");
|
||||||
|
result.Should().NotContain("api");
|
||||||
|
result.Should().NotContain("development");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -594,6 +594,7 @@ public class CaddyServiceTests : IDisposable
|
|||||||
var expectedHostnames = new List<string> { "example.com" };
|
var expectedHostnames = new List<string> { "example.com" };
|
||||||
var expectedTarget = "localhost";
|
var expectedTarget = "localhost";
|
||||||
var expectedPorts = new List<int> { 8080 };
|
var expectedPorts = new List<int> { 8080 };
|
||||||
|
var expectedTags = new List<string>();
|
||||||
|
|
||||||
_mockParsingService
|
_mockParsingService
|
||||||
.Setup(x => x.GetHostnamesFromCaddyfileContent(testContent))
|
.Setup(x => x.GetHostnamesFromCaddyfileContent(testContent))
|
||||||
@@ -604,6 +605,9 @@ public class CaddyServiceTests : IDisposable
|
|||||||
_mockParsingService
|
_mockParsingService
|
||||||
.Setup(x => x.GetReverseProxyPortsFromCaddyfileContent(testContent))
|
.Setup(x => x.GetReverseProxyPortsFromCaddyfileContent(testContent))
|
||||||
.Returns(expectedPorts);
|
.Returns(expectedPorts);
|
||||||
|
_mockParsingService
|
||||||
|
.Setup(x => x.GetTagsFromCaddyfileContent(testContent))
|
||||||
|
.Returns(expectedTags);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = _service.GetCaddyConfigurationInfo("test");
|
var result = _service.GetCaddyConfigurationInfo("test");
|
||||||
@@ -613,6 +617,57 @@ public class CaddyServiceTests : IDisposable
|
|||||||
result.Hostnames.Should().BeEquivalentTo(expectedHostnames);
|
result.Hostnames.Should().BeEquivalentTo(expectedHostnames);
|
||||||
result.ReverseProxyHostname.Should().Be(expectedTarget);
|
result.ReverseProxyHostname.Should().Be(expectedTarget);
|
||||||
result.ReverseProxyPorts.Should().BeEquivalentTo(expectedPorts);
|
result.ReverseProxyPorts.Should().BeEquivalentTo(expectedPorts);
|
||||||
|
result.Tags.Should().BeEquivalentTo(expectedTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the Caddy service correctly populates the Tags property when configuration content contains tags.
|
||||||
|
/// Setup: Creates a configuration file with tags comment and mocks the parsing service to return expected tags.
|
||||||
|
/// Expectation: The service should correctly populate the Tags property using the parsing service, ensuring tag information is available for configuration management.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetCaddyConfigurationInfo_WithTags_PopulatesTagsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testContent = @"
|
||||||
|
# Tags: [web;production;ssl]
|
||||||
|
example.com {
|
||||||
|
reverse_proxy localhost:8080
|
||||||
|
}";
|
||||||
|
var filePath = Path.Combine(_tempConfigDir, "test-with-tags.caddy");
|
||||||
|
File.WriteAllText(filePath, testContent);
|
||||||
|
|
||||||
|
var expectedHostnames = new List<string> { "example.com" };
|
||||||
|
var expectedTarget = "localhost";
|
||||||
|
var expectedPorts = new List<int> { 8080 };
|
||||||
|
var expectedTags = new List<string> { "web", "production", "ssl" };
|
||||||
|
|
||||||
|
_mockParsingService
|
||||||
|
.Setup(x => x.GetHostnamesFromCaddyfileContent(testContent))
|
||||||
|
.Returns(expectedHostnames);
|
||||||
|
_mockParsingService
|
||||||
|
.Setup(x => x.GetReverseProxyTargetFromCaddyfileContent(testContent))
|
||||||
|
.Returns(expectedTarget);
|
||||||
|
_mockParsingService
|
||||||
|
.Setup(x => x.GetReverseProxyPortsFromCaddyfileContent(testContent))
|
||||||
|
.Returns(expectedPorts);
|
||||||
|
_mockParsingService
|
||||||
|
.Setup(x => x.GetTagsFromCaddyfileContent(testContent))
|
||||||
|
.Returns(expectedTags);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _service.GetCaddyConfigurationInfo("test-with-tags");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Hostnames.Should().BeEquivalentTo(expectedHostnames);
|
||||||
|
result.ReverseProxyHostname.Should().Be(expectedTarget);
|
||||||
|
result.ReverseProxyPorts.Should().BeEquivalentTo(expectedPorts);
|
||||||
|
result.Tags.Should().BeEquivalentTo(expectedTags);
|
||||||
|
result.Tags.Should().HaveCount(3);
|
||||||
|
result.Tags.Should().Contain("web");
|
||||||
|
result.Tags.Should().Contain("production");
|
||||||
|
result.Tags.Should().Contain("ssl");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -7,6 +7,21 @@
|
|||||||
<MudIcon Icon="@Icons.Custom.FileFormats.FileCode"></MudIcon>
|
<MudIcon Icon="@Icons.Custom.FileFormats.FileCode"></MudIcon>
|
||||||
<MudText>@ConfigurationInfo.FileName</MudText>
|
<MudText>@ConfigurationInfo.FileName</MudText>
|
||||||
<MudSpacer/>
|
<MudSpacer/>
|
||||||
|
@if (ConfigurationInfo.Tags.Count > 0)
|
||||||
|
{
|
||||||
|
<MudTooltip Delay="0" Placement="Placement.Left">
|
||||||
|
<ChildContent>
|
||||||
|
<MudChip T="string" Variant="Variant.Outlined" Style="min-width: 80px;"
|
||||||
|
Color="Color.Tertiary">@("tag".ToQuantity(ConfigurationInfo.Tags.Count))</MudChip>
|
||||||
|
</ChildContent>
|
||||||
|
<TooltipContent>
|
||||||
|
@foreach (var tag in ConfigurationInfo.Tags)
|
||||||
|
{
|
||||||
|
<MudText Align="Align.Start">⏵ @tag</MudText>
|
||||||
|
}
|
||||||
|
</TooltipContent>
|
||||||
|
</MudTooltip>
|
||||||
|
}
|
||||||
<MudTooltip Delay="0" Placement="Placement.Left">
|
<MudTooltip Delay="0" Placement="Placement.Left">
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<MudChip T="string" Variant="Variant.Outlined">@ConfigurationInfo.ReverseProxyHostname</MudChip>
|
<MudChip T="string" Variant="Variant.Outlined">@ConfigurationInfo.ReverseProxyHostname</MudChip>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using CaddyManager.Contracts.Caddy;
|
||||||
using CaddyManager.Contracts.Models.Caddy;
|
using CaddyManager.Contracts.Models.Caddy;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
@@ -28,6 +29,11 @@ public partial class CaddyReverseProxyItem : ComponentBase
|
|||||||
[Inject]
|
[Inject]
|
||||||
private IDialogService DialogService { get; set; } = null!;
|
private IDialogService DialogService { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caddy service for ops on the Caddy configuration
|
||||||
|
/// </summary>
|
||||||
|
[Inject] private ICaddyService CaddyService { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show the Caddy file editor dialog
|
/// Show the Caddy file editor dialog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -45,6 +51,9 @@ public partial class CaddyReverseProxyItem : ComponentBase
|
|||||||
});
|
});
|
||||||
|
|
||||||
var result = await dialog.Result;
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
ConfigurationInfo = CaddyService.GetCaddyConfigurationInfo(ConfigurationInfo.FileName);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
if (result is { Data: bool, Canceled: false } && (bool)result.Data)
|
if (result is { Data: bool, Canceled: false } && (bool)result.Data)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public partial class CaddyfileEditor : ComponentBase
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Snackbar.Add(response.Message, Severity.Error);
|
Snackbar.Add(response.Message, Severity.Error);
|
||||||
MudDialog.Close(DialogResult.Ok(false)); // Indicate failed save
|
// MudDialog.Close(DialogResult.Ok(false)); // Indicate failed save
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ public partial class CaddyfileEditor : ComponentBase
|
|||||||
{
|
{
|
||||||
Snackbar.Add(submitResponse.Message, Severity.Error);
|
Snackbar.Add(submitResponse.Message, Severity.Error);
|
||||||
// Indicate failed save, no restart needed
|
// Indicate failed save, no restart needed
|
||||||
MudDialog.Close(DialogResult.Ok(false));
|
// MudDialog.Close(DialogResult.Ok(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,9 +139,7 @@ public partial class CaddyfileEditor : ComponentBase
|
|||||||
private async Task Duplicate()
|
private async Task Duplicate()
|
||||||
{
|
{
|
||||||
var content = await _codeEditor.GetValue();
|
var content = await _codeEditor.GetValue();
|
||||||
|
|
||||||
await OnDuplicate.InvokeAsync(content);
|
|
||||||
|
|
||||||
MudDialog.Close(DialogResult.Ok(false));
|
MudDialog.Close(DialogResult.Ok(false));
|
||||||
|
await OnDuplicate.InvokeAsync(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user