diff --git a/Directory.Build.props b/Directory.Build.props index 6b5b8ee..3296403 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,6 @@ - 0.6.2 + Tilework + 0.6.3 diff --git a/tilework.core/AppMetadata.cs b/tilework.core/AppMetadata.cs new file mode 100644 index 0000000..da9003a --- /dev/null +++ b/tilework.core/AppMetadata.cs @@ -0,0 +1,21 @@ +using System.Reflection; + +namespace Tilework.Core; + +public static class AppMetadata +{ + private static readonly Assembly _assembly = typeof(AppMetadata).Assembly; + + public static string Name { get; } = + _assembly.GetCustomAttribute()?.Product + ?? _assembly.GetName().Name + ?? "Application"; + + public static string InformationalVersion { get; } = + _assembly.GetCustomAttribute()?.InformationalVersion + ?? "0.0.0"; + + public static string Version { get; } = InformationalVersion.Split('+')[0]; + + public static string DisplayVersion => $"v{Version}"; +} diff --git a/tilework.core/Commands/PrintVersion.cs b/tilework.core/Commands/PrintVersion.cs index ebe3169..688f955 100644 --- a/tilework.core/Commands/PrintVersion.cs +++ b/tilework.core/Commands/PrintVersion.cs @@ -1,5 +1,4 @@ using Tilework.Core.Interfaces; -using System.Reflection; namespace Tilework.Core.Commands; @@ -14,9 +13,7 @@ public PrintVersionInfoCommand() public async Task run(string[] args) { - var assembly = Assembly.GetExecutingAssembly(); - var informationalVersion = assembly.GetCustomAttribute()?.InformationalVersion; - Console.WriteLine(informationalVersion); + Console.WriteLine(AppMetadata.InformationalVersion); return 0; } -} \ No newline at end of file +} diff --git a/tilework.core/Enums/LoadBalancing/ConditionType.cs b/tilework.core/Enums/LoadBalancing/ConditionType.cs index 48996f2..2c0f4c6 100644 --- a/tilework.core/Enums/LoadBalancing/ConditionType.cs +++ b/tilework.core/Enums/LoadBalancing/ConditionType.cs @@ -11,6 +11,7 @@ public enum ConditionType [Description("Query string")] QueryString, [Description("SNI FQDN")] - SNI + SNI, + [Description("Source IP")] + SourceIp } - diff --git a/tilework.core/Enums/LoadBalancing/LoadBalancerConditionRules.cs b/tilework.core/Enums/LoadBalancing/LoadBalancerConditionRules.cs index 84408db..d49301f 100644 --- a/tilework.core/Enums/LoadBalancing/LoadBalancerConditionRules.cs +++ b/tilework.core/Enums/LoadBalancing/LoadBalancerConditionRules.cs @@ -9,7 +9,8 @@ public static class LoadBalancerConditionRules { ConditionType.HostHeader, ConditionType.Path, - ConditionType.QueryString + ConditionType.QueryString, + ConditionType.SourceIp }; private static readonly ConditionType[] HttpsConditions = @@ -17,12 +18,14 @@ public static class LoadBalancerConditionRules ConditionType.HostHeader, ConditionType.Path, ConditionType.QueryString, - ConditionType.SNI + ConditionType.SNI, + ConditionType.SourceIp }; private static readonly ConditionType[] TlsConditions = { - ConditionType.SNI + ConditionType.SNI, + ConditionType.SourceIp }; public static IReadOnlyList GetAllowedConditions(LoadBalancerProtocol protocol) diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Enums/AclCondition.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Enums/AclCondition.cs new file mode 100644 index 0000000..1d9e5a3 --- /dev/null +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Enums/AclCondition.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; + +namespace Tilework.LoadBalancing.Haproxy; + +public enum AclCondition +{ + HostHeader, + Path, + QueryString, + SNI, + SourceIp, + VariableSet, +} \ No newline at end of file diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Enums/HttpRequestAction.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Enums/HttpRequestAction.cs new file mode 100644 index 0000000..80a8a8d --- /dev/null +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Enums/HttpRequestAction.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; + +namespace Tilework.LoadBalancing.Haproxy; + +public enum HttpRequestAction +{ + AddHeader, + Redirect, + Return, + Deny, + SetVariable +} diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Mappers/HAProxyConfigurationProfile.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Mappers/HAProxyConfigurationProfile.cs index f510c7c..31606ed 100644 --- a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Mappers/HAProxyConfigurationProfile.cs +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Mappers/HAProxyConfigurationProfile.cs @@ -8,102 +8,28 @@ namespace Tilework.LoadBalancing.Haproxy; public class HAProxyConfigurationProfile : Profile { + private const string BackendVariableName = "txn.tilework_backend"; + private const string BackendSelectedAclName = "backend_selected"; + public HAProxyConfigurationProfile() { + CreateMap() + .ConvertUsing(src => MapToAclCondition(src)); + CreateMap() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Id.ToString())) .ForPath(dest => dest.Bind.Address, opt => opt.MapFrom(src => "*")) .ForPath(dest => dest.Bind.Port, opt => opt.MapFrom(src => src.Port)) - .AfterMap((src, dest) => + .AfterMap((src, dest, context) => { if (src.Protocol == LoadBalancerProtocol.HTTPS || src.Protocol == LoadBalancerProtocol.TLS) dest.Bind.EnableTls = true; - if (src.Type == LoadBalancerType.APPLICATION) - { - dest.AddHeaders.Add(new HttpHeader() - { - Name = "X-Forwarded-Proto", - Value = src.Protocol == LoadBalancerProtocol.HTTPS ? "https" : "http" - }); - - dest.AddHeaders.Add(new HttpHeader() - { - Name = "X-Forwarded-Port", - Value = src.Port.ToString() - }); - } - dest.Mode = src.Type == LoadBalancerType.APPLICATION ? Mode.HTTP : Mode.TCP; - if (src.Rules != null) - { - foreach (var rule in src.Rules.OrderBy(r => r.Priority)) - { - var acls = new List(); - for (int i = 0; i < rule.Conditions.Count; i++) - { - var condition = rule.Conditions[i]; - - var acl = new Acl() - { - Name = $"{rule.Id.ToString()}-{i}", - Type = condition.Type, - Values = condition.Values - }; - - acls.Add(acl); - } - - dest.Acls.AddRange(acls); - - if (rule.Action == null) - throw new InvalidOperationException($"Rule {rule.Id} is missing an action."); - - var actionType = rule.Action.Type; - switch (actionType) - { - case RuleActionType.Forward: - var targetGroup = rule.TargetGroup; - if (targetGroup != null) - { - var usebe = new UseBackend() - { - Acls = acls.Select(a => a.Name).ToList(), - Target = targetGroup.Id.ToString(), - }; - dest.UseBackends.Add(usebe); - } - break; - case RuleActionType.Redirect: - dest.HttpRequests.Add(new HttpRequest() - { - ActionType = RuleActionType.Redirect, - RedirectUrl = rule.Action?.RedirectUrl, - RedirectStatusCode = rule.Action?.RedirectStatusCode, - Acls = acls.Select(a => a.Name).ToList() - }); - break; - case RuleActionType.FixedResponse: - dest.HttpRequests.Add(new HttpRequest() - { - ActionType = RuleActionType.FixedResponse, - FixedResponseStatusCode = rule.Action?.FixedResponseStatusCode, - FixedResponseContentType = rule.Action?.FixedResponseContentType, - FixedResponseBody = rule.Action?.FixedResponseBody, - Acls = acls.Select(a => a.Name).ToList() - }); - break; - case RuleActionType.Reject: - dest.TcpRequests.Add(new TcpRequest() - { - Acls = acls.Select(a => a.Name).ToList() - }); - break; - default: - throw new NotSupportedException($"Unsupported rule action: {actionType}"); - } - } - } + if (dest.Mode == Mode.HTTP) + MapHttpLoadBalancerRules(src, dest, context); + else + MapTcpLoadBalancerRules(src, dest, context); }); CreateMap() @@ -137,4 +63,158 @@ public HAProxyConfigurationProfile() }).ToList(); }); } + + private static AclCondition MapToAclCondition(ConditionType conditionType) + { + return conditionType switch + { + ConditionType.HostHeader => AclCondition.HostHeader, + ConditionType.Path => AclCondition.Path, + ConditionType.QueryString => AclCondition.QueryString, + ConditionType.SNI => AclCondition.SNI, + ConditionType.SourceIp => AclCondition.SourceIp, + _ => throw new NotSupportedException($"Unsupported condition type for HAProxy ACL mapping: {conditionType}") + }; + } + + private static void MapHttpLoadBalancerRules(LoadBalancer src, FrontendSection dest, ResolutionContext context) + { + dest.HttpRequests.Add(new AddHeaderHttpRequest( + "X-Forwarded-Proto", + src.Protocol == LoadBalancerProtocol.HTTPS ? "https" : "http")); + + dest.HttpRequests.Add(new AddHeaderHttpRequest( + "X-Forwarded-Port", + src.Port.ToString())); + + if (src.Rules is not { Count: > 0 }) + return; + + var hasForwardRule = false; + dest.Acls.Add(new Acl() + { + Name = BackendSelectedAclName, + Type = AclCondition.VariableSet, + Values = new List { BackendVariableName } + }); + + foreach (var rule in src.Rules.OrderBy(r => r.Priority)) + { + var ruleAcls = BuildRuleAcls(rule, context); + dest.Acls.AddRange(ruleAcls); + + if (rule.Action == null) + throw new InvalidOperationException($"Rule {rule.Id} is missing an action."); + + var gatedAcls = new List { $"!{BackendSelectedAclName}" }; + gatedAcls.AddRange(ruleAcls.Select(a => a.Name)); + + switch (rule.Action.Type) + { + case RuleActionType.Forward: + if (rule.TargetGroup != null) + { + hasForwardRule = true; + dest.HttpRequests.Add(new SetVariableHttpRequest(BackendVariableName, rule.TargetGroup.Id.ToString()) + { + Acls = gatedAcls + }); + } + break; + case RuleActionType.Redirect: + if (string.IsNullOrWhiteSpace(rule.Action.RedirectUrl)) + throw new InvalidOperationException($"Rule {rule.Id} redirect action is missing RedirectUrl."); + + dest.HttpRequests.Add(new RedirectHttpRequest(rule.Action.RedirectUrl) + { + StatusCode = rule.Action.RedirectStatusCode, + Acls = gatedAcls + }); + break; + case RuleActionType.FixedResponse: + if (rule.Action.FixedResponseStatusCode == null) + throw new InvalidOperationException($"Rule {rule.Id} fixed response action is missing FixedResponseStatusCode."); + + dest.HttpRequests.Add(new ReturnHttpRequest(rule.Action.FixedResponseStatusCode.Value) + { + ContentType = rule.Action.FixedResponseContentType, + Body = rule.Action.FixedResponseBody, + Acls = gatedAcls + }); + break; + case RuleActionType.Reject: + dest.HttpRequests.Add(new DenyHttpRequest() + { + Acls = gatedAcls + }); + break; + default: + throw new NotSupportedException($"Unsupported rule action: {rule.Action.Type}"); + } + } + + if (hasForwardRule) + { + dest.UseBackends.Add(new UseBackend() + { + Target = $"%[var({BackendVariableName})]", + Acls = new List { BackendSelectedAclName } + }); + } + } + + private static void MapTcpLoadBalancerRules(LoadBalancer src, FrontendSection dest, ResolutionContext context) + { + if (src.Rules is not { Count: > 0 }) + return; + + foreach (var rule in src.Rules.OrderBy(r => r.Priority)) + { + var ruleAcls = BuildRuleAcls(rule, context); + dest.Acls.AddRange(ruleAcls); + + if (rule.Action == null) + throw new InvalidOperationException($"Rule {rule.Id} is missing an action."); + + var aclNames = ruleAcls.Select(a => a.Name).ToList(); + switch (rule.Action.Type) + { + case RuleActionType.Forward: + if (rule.TargetGroup != null) + { + dest.UseBackends.Add(new UseBackend() + { + Acls = aclNames, + Target = rule.TargetGroup.Id.ToString() + }); + } + break; + case RuleActionType.Reject: + dest.TcpRequests.Add(new TcpRequest() + { + Acls = aclNames + }); + break; + default: + throw new NotSupportedException($"Unsupported TCP rule action: {rule.Action.Type}"); + } + } + } + + private static List BuildRuleAcls(Rule rule, ResolutionContext context) + { + var ruleAcls = new List(); + for (int i = 0; i < rule.Conditions.Count; i++) + { + var condition = rule.Conditions[i]; + ruleAcls.Add(new Acl() + { + Name = $"{rule.Id}-{i}", + Type = context.Mapper.Map(condition.Type), + Values = condition.Values + }); + } + + return ruleAcls; + } } diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Sections/FrontendSection.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Sections/FrontendSection.cs index 1eae359..d209afa 100644 --- a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Sections/FrontendSection.cs +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Sections/FrontendSection.cs @@ -23,9 +23,6 @@ public class FrontendSection : ConfigSection [Statement("default_backend")] public string DefaultBackend { get; set; } - [Statement("http-request add-header")] - public List AddHeaders { get; set; } = new List(); - public FrontendSection() : base("frontend") { } diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/Acl.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/Acl.cs index 25eb389..d62b967 100644 --- a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/Acl.cs +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/Acl.cs @@ -5,7 +5,7 @@ namespace Tilework.LoadBalancing.Haproxy; public class Acl { public string Name { get; set; } - public ConditionType Type { get; set; } + public AclCondition Type { get; set; } public List Values { get; set; } = new List(); public Acl() {} @@ -19,10 +19,12 @@ public override string ToString() { return Type switch { - ConditionType.HostHeader => $"{Name} hdr(host) -i {string.Join(" ", Values)}", - ConditionType.Path => $"{Name} path_beg -i {string.Join(" ", Values)}", - ConditionType.QueryString => $"{Name} {String.Join(" or ", Values.Select(v => $"url_param(plan) -i {v}"))}", - ConditionType.SNI => $"{Name} req.ssl_sni -i {string.Join(" ", Values)}" + AclCondition.HostHeader => $"{Name} hdr(host) -i {string.Join(" ", Values)}", + AclCondition.Path => $"{Name} path_beg -i {string.Join(" ", Values)}", + AclCondition.QueryString => $"{Name} {String.Join(" or ", Values.Select(v => $"url_param(plan) -i {v}"))}", + AclCondition.SNI => $"{Name} req.ssl_sni -i {string.Join(" ", Values)}", + AclCondition.SourceIp => $"{Name} src {string.Join(" ", Values)}", + AclCondition.VariableSet => $"{Name} {String.Join(" or ", Values.Select(v => $"var({v}) -m found"))}", }; } -} \ No newline at end of file +} diff --git a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/HttpRequest.cs b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/HttpRequest.cs index 3818839..dcafc18 100644 --- a/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/HttpRequest.cs +++ b/tilework.core/Providers/LoadBalancingProviders/HAProxy/Models/Config/Statements/HttpRequest.cs @@ -1,45 +1,15 @@ -using System.Linq; -using Tilework.LoadBalancing.Enums; - namespace Tilework.LoadBalancing.Haproxy; -public class HttpRequest +public abstract class HttpRequest { - public RuleActionType ActionType { get; set; } - public string? RedirectUrl { get; set; } - public int? RedirectStatusCode { get; set; } - public int? FixedResponseStatusCode { get; set; } - public string? FixedResponseContentType { get; set; } - public string? FixedResponseBody { get; set; } public List Acls { get; set; } = new(); - - public HttpRequest() { } - - public HttpRequest(string[] parameters) - { - } + public abstract HttpRequestAction Action { get; } public override string ToString() { - return ActionType switch - { - RuleActionType.Redirect => BuildRedirect(), - RuleActionType.FixedResponse => BuildReturn(), - _ => throw new NotSupportedException($"Unsupported HTTP action type: {ActionType}") - }; - } - - private string BuildRedirect() - { - var parts = new List { "redirect", "location", RedirectUrl ?? string.Empty }; + var parts = BuildParts(); - if (RedirectStatusCode.HasValue) - { - parts.Add("code"); - parts.Add(RedirectStatusCode.Value.ToString()); - } - - if (Acls != null && Acls.Count > 0) + if (Acls.Count > 0) { parts.Add("if"); parts.AddRange(Acls); @@ -48,96 +18,96 @@ private string BuildRedirect() return string.Join(" ", parts); } - private string BuildReturn() - { - var parts = new List - { - "return", - "status", - FixedResponseStatusCode!.ToString() - }; + protected abstract List BuildParts(); - if (!string.IsNullOrWhiteSpace(FixedResponseContentType)) - { - parts.Add("content-type"); - parts.Add(FixedResponseContentType); - } - - if (!string.IsNullOrWhiteSpace(FixedResponseBody)) - { - parts.Add("lf-string"); - parts.Add(Quote(FixedResponseBody)); - } + protected static string Quote(string value) + { + var escaped = value + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\r", string.Empty) + .Replace("\n", "\\n"); + return $"\"{escaped}\""; + } +} - if (Acls != null && Acls.Count > 0) - { - parts.Add("if"); - parts.AddRange(Acls); - } +public sealed class AddHeaderHttpRequest(string name, string value) : HttpRequest +{ + public string Name { get; set; } = name; + public string Value { get; set; } = value; + public override HttpRequestAction Action => HttpRequestAction.AddHeader; - return string.Join(" ", parts); + protected override List BuildParts() + { + return new List { "add-header", Name, Value }; } +} - private void ParseRedirect(string[] parameters) +public sealed class RedirectHttpRequest(string url) : HttpRequest +{ + public string Url { get; set; } = url; + public int? StatusCode { get; set; } + public override HttpRequestAction Action => HttpRequestAction.Redirect; + + protected override List BuildParts() { - RedirectUrl = GetValueAfter(parameters, "location"); - var code = GetValueAfter(parameters, "code"); - if (int.TryParse(code, out var parsed)) + var parts = new List { "redirect", "location", Url }; + + if (StatusCode.HasValue) { - RedirectStatusCode = parsed; + parts.Add("code"); + parts.Add(StatusCode.Value.ToString()); } - Acls = GetAcls(parameters); + + return parts; } +} + +public sealed class ReturnHttpRequest(int statusCode) : HttpRequest +{ + public int StatusCode { get; set; } = statusCode; + public string? ContentType { get; set; } + public string? Body { get; set; } + public override HttpRequestAction Action => HttpRequestAction.Return; - private void ParseReturn(string[] parameters) + protected override List BuildParts() { - var status = GetValueAfter(parameters, "status"); - if (int.TryParse(status, out var parsed)) - { - FixedResponseStatusCode = parsed; - } - FixedResponseContentType = GetValueAfter(parameters, "content-type"); - var body = GetValueAfter(parameters, "lf-string"); - if (!string.IsNullOrWhiteSpace(body)) + var parts = new List { "return", "status", StatusCode.ToString() }; + + if (!string.IsNullOrWhiteSpace(ContentType)) { - FixedResponseBody = body.Trim('"'); + parts.Add("content-type"); + parts.Add(ContentType); } - Acls = GetAcls(parameters); - } - private static string? GetValueAfter(string[] parameters, string token) - { - for (int i = 0; i < parameters.Length - 1; i++) + if (!string.IsNullOrWhiteSpace(Body)) { - if (string.Equals(parameters[i], token, StringComparison.OrdinalIgnoreCase)) - { - return parameters[i + 1]; - } + parts.Add("lf-string"); + parts.Add(Quote(Body)); } - return null; + return parts; } +} - private static List GetAcls(string[] parameters) - { - for (int i = 0; i < parameters.Length; i++) - { - if (string.Equals(parameters[i], "if", StringComparison.OrdinalIgnoreCase)) - { - return parameters.Skip(i + 1).ToList(); - } - } +public sealed class SetVariableHttpRequest(string variableName, string variableValue) : HttpRequest +{ + public string VariableName { get; set; } = variableName; + public string VariableValue { get; set; } = variableValue; + public override HttpRequestAction Action => HttpRequestAction.SetVariable; - return new List(); + protected override List BuildParts() + { + return new List { $"set-var({VariableName}) str({VariableValue})" }; } +} - private static string Quote(string value) +public sealed class DenyHttpRequest : HttpRequest +{ + public override HttpRequestAction Action => HttpRequestAction.Deny; + + protected override List BuildParts() { - var escaped = value - .Replace("\\", "\\\\") - .Replace("\"", "\\\"") - .Replace("\r", string.Empty) - .Replace("\n", "\\n"); - return $"\"{escaped}\""; + return new List { "deny" }; } } diff --git a/tilework.ui/Components/Layout/MainLayout.razor b/tilework.ui/Components/Layout/MainLayout.razor index ab39147..2ce3fc2 100644 --- a/tilework.ui/Components/Layout/MainLayout.razor +++ b/tilework.ui/Components/Layout/MainLayout.razor @@ -7,7 +7,7 @@ - Tilework + @AppMetadata.Name diff --git a/tilework.ui/Components/Layout/NavMenu.razor b/tilework.ui/Components/Layout/NavMenu.razor index bf6be0f..1e8e819 100644 --- a/tilework.ui/Components/Layout/NavMenu.razor +++ b/tilework.ui/Components/Layout/NavMenu.razor @@ -1,5 +1,4 @@ @namespace Tilework.Ui.Components.Layout -@using System.Reflection @@ -18,14 +17,6 @@ - @AppVersion + @AppMetadata.DisplayVersion - -@code { - private static readonly string AppVersion = - "v" + Assembly.GetExecutingAssembly() - .GetCustomAttribute() - ?.InformationalVersion? - .Split('+')[0] ?? "0.0.0"; -} diff --git a/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityDetail.razor b/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityDetail.razor index 37a8708..6e6a973 100644 --- a/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityDetail.razor +++ b/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityDetail.razor @@ -12,7 +12,7 @@ @page "/cm/authorities/{Id:guid}" -Certificate authority details +@PageTitleHelper.Format("Certificate authority details") @if(_item != null) { diff --git a/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityList.razor b/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityList.razor index 5bed706..e3f764d 100644 --- a/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityList.razor +++ b/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityList.razor @@ -10,7 +10,7 @@ @page "/cm/authorities" -Certificate authorities +@PageTitleHelper.Format("Certificate authorities") diff --git a/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityNew.razor b/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityNew.razor index 8a34f9e..61320df 100644 --- a/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityNew.razor +++ b/tilework.ui/Components/Pages/CertificateManagement/CertificateAuthorityNew.razor @@ -14,7 +14,7 @@ @page "/cm/authorities/new" -New certificate authority +@PageTitleHelper.Format("New certificate authority") @if(form is NewAcmeCertificateAuthorityForm acmeForm) { diff --git a/tilework.ui/Components/Pages/CertificateManagement/CertificateDetail.razor b/tilework.ui/Components/Pages/CertificateManagement/CertificateDetail.razor index 1297710..fbd841f 100644 --- a/tilework.ui/Components/Pages/CertificateManagement/CertificateDetail.razor +++ b/tilework.ui/Components/Pages/CertificateManagement/CertificateDetail.razor @@ -19,7 +19,7 @@ @page "/cm/certificates/{Id:guid}" -Certificate details +@PageTitleHelper.Format("Certificate details") @if(_item != null) { diff --git a/tilework.ui/Components/Pages/CertificateManagement/CertificateList.razor b/tilework.ui/Components/Pages/CertificateManagement/CertificateList.razor index d79d275..870f381 100644 --- a/tilework.ui/Components/Pages/CertificateManagement/CertificateList.razor +++ b/tilework.ui/Components/Pages/CertificateManagement/CertificateList.razor @@ -15,7 +15,7 @@ @page "/cm/certificates" -Certificates +@PageTitleHelper.Format("Certificates") diff --git a/tilework.ui/Components/Pages/CertificateManagement/CertificateNew.razor b/tilework.ui/Components/Pages/CertificateManagement/CertificateNew.razor index e252185..95a1e82 100644 --- a/tilework.ui/Components/Pages/CertificateManagement/CertificateNew.razor +++ b/tilework.ui/Components/Pages/CertificateManagement/CertificateNew.razor @@ -13,7 +13,7 @@ @page "/cm/certificates/new" -New certificate +@PageTitleHelper.Format("New certificate") Error +@PageTitleHelper.Format("Error")

Error.

An error occurred while processing your request.

diff --git a/tilework.ui/Components/Pages/Home.razor b/tilework.ui/Components/Pages/Home.razor index 41d8b0f..34538ab 100644 --- a/tilework.ui/Components/Pages/Home.razor +++ b/tilework.ui/Components/Pages/Home.razor @@ -3,7 +3,7 @@ @inject IContainerManager containerManager -Home +@PageTitleHelper.Format("Home") @code { diff --git a/tilework.ui/Components/Pages/IdentityManagement/UserDetail.razor b/tilework.ui/Components/Pages/IdentityManagement/UserDetail.razor index 711efde..0c1672f 100644 --- a/tilework.ui/Components/Pages/IdentityManagement/UserDetail.razor +++ b/tilework.ui/Components/Pages/IdentityManagement/UserDetail.razor @@ -12,7 +12,7 @@ @page "/im/users/{Id:guid}" -User details +@PageTitleHelper.Format("User details") @if(_item != null) { diff --git a/tilework.ui/Components/Pages/IdentityManagement/UserEdit.razor b/tilework.ui/Components/Pages/IdentityManagement/UserEdit.razor index 7d58d7b..25c4ab1 100644 --- a/tilework.ui/Components/Pages/IdentityManagement/UserEdit.razor +++ b/tilework.ui/Components/Pages/IdentityManagement/UserEdit.razor @@ -16,7 +16,7 @@ @page "/im/users/{Id:guid}/edit" -Edit user +@PageTitleHelper.Format("Edit user") Users +@PageTitleHelper.Format("Users") diff --git a/tilework.ui/Components/Pages/IdentityManagement/UserNew.razor b/tilework.ui/Components/Pages/IdentityManagement/UserNew.razor index e71ce98..81edc69 100644 --- a/tilework.ui/Components/Pages/IdentityManagement/UserNew.razor +++ b/tilework.ui/Components/Pages/IdentityManagement/UserNew.razor @@ -15,7 +15,7 @@ @page "/im/users/new" -New user +@PageTitleHelper.Format("New user") Load balancer details +@PageTitleHelper.Format("Load balancer details") diff --git a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerEdit.razor b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerEdit.razor index 36f15a6..8394b85 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerEdit.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerEdit.razor @@ -12,7 +12,7 @@ @inject ISnackbar Snackbar @page "/lb/loadbalancers/{Id:guid}/edit" -Edit load balancer +@PageTitleHelper.Format("Edit load balancer") Load balancers +@PageTitleHelper.Format("Load balancers") diff --git a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerNew.razor b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerNew.razor index 57009cc..ebe105a 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerNew.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/LoadBalancerNew.razor @@ -17,7 +17,7 @@ @page "/lb/loadbalancers/new" -New balancer +@PageTitleHelper.Format("New balancer") Target group details +@PageTitleHelper.Format("Target group details") @if(_item != null) { diff --git a/tilework.ui/Components/Pages/LoadBalancing/TargetGroupEdit.razor b/tilework.ui/Components/Pages/LoadBalancing/TargetGroupEdit.razor index 40e7de5..237eebc 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/TargetGroupEdit.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/TargetGroupEdit.razor @@ -14,7 +14,7 @@ @page "/lb/targetgroups/{Id:guid}/edit" -Edit target group +@PageTitleHelper.Format("Edit target group") Target groups +@PageTitleHelper.Format("Target groups") diff --git a/tilework.ui/Components/Pages/LoadBalancing/TargetGroupNew.razor b/tilework.ui/Components/Pages/LoadBalancing/TargetGroupNew.razor index 76eea2b..28cb016 100644 --- a/tilework.ui/Components/Pages/LoadBalancing/TargetGroupNew.razor +++ b/tilework.ui/Components/Pages/LoadBalancing/TargetGroupNew.razor @@ -17,7 +17,7 @@ @page "/lb/targetgroups/new" -New target group +@PageTitleHelper.Format("New target group") Login +@PageTitleHelper.Format("Login") - Sign in + Sign in to tilework
diff --git a/tilework.ui/Components/_Imports.razor b/tilework.ui/Components/_Imports.razor index f778ab9..ed4dc60 100644 --- a/tilework.ui/Components/_Imports.razor +++ b/tilework.ui/Components/_Imports.razor @@ -19,3 +19,5 @@ @using Tilework.Ui.Components.Shared @using Tilework.Ui.Models @using Tilework.Ui.ViewModels +@using Tilework.Core +@using Tilework.Ui.Utilities diff --git a/tilework.ui/Utilities/PageTitleHelper.cs b/tilework.ui/Utilities/PageTitleHelper.cs new file mode 100644 index 0000000..1eae195 --- /dev/null +++ b/tilework.ui/Utilities/PageTitleHelper.cs @@ -0,0 +1,11 @@ +using Tilework.Core; + +namespace Tilework.Ui.Utilities; + +public static class PageTitleHelper +{ + public static string Format(string pageTitle) + { + return $"{pageTitle} | {AppMetadata.Name}"; + } +}