From b4c6b6b794db2b1cd6bfb17f08c1b1f35130bba0 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 29 Dec 2025 11:08:10 -0500 Subject: [PATCH 01/27] clarify SMTP Relay site option to avoid confusion --- Oqtane.Client/Modules/Admin/Site/Index.razor | 2 +- Oqtane.Client/Resources/Modules/Admin/Site/Index.resx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 0ceec1fd..d7b03cdd 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -265,7 +265,7 @@
- +
+@if (_initialized) +{ +
+
+ @if (!_editable) + { + + } +
+ +
+ +
-
-
- -
- +
+ +
+ +
-
-
- -
- +
+ +
+ +
-
-
- -
- - +
+ +
+ + +
-
-
- -
- +
+ +
+ +
-
-
- -
-
-
- -
-
- +
+ +
+
+
+ +
+
+ +
-
-
- -
-
-
- -
-
- +
+ +
+
+
+ +
+
+ +
-
-
- -
-
-
- -
-
- -
+
+ +
+
-
-
- - @SharedLocalizer["Cancel"] -
-
- - +
+ + @if (!_editable) + { + + } + @SharedLocalizer["Cancel"] +
+
+ + +} @code { private ElementReference form; private bool validated = false; + private bool _initialized = false; + private bool _editable = true; private int _jobId; private string _name = string.Empty; private string _jobType = string.Empty; @@ -113,26 +121,27 @@ private DateTime? _nextDate = null; private DateTime? _nextTime = null; private string createdby; - private DateTime createdon; - private string modifiedby; - private DateTime modifiedon; + private DateTime createdon; + private string modifiedby; + private DateTime modifiedon; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - protected override async Task OnInitializedAsync() - { - try - { - _jobId = Int32.Parse(PageState.QueryString["id"]); - Job job = await JobService.GetJobAsync(_jobId); - if (job != null) - { - _name = job.Name; - _jobType = job.JobType; - _isEnabled = job.IsEnabled.ToString(); - _interval = job.Interval.ToString(); - _frequency = job.Frequency; - _startDate = UtcToLocal(job.StartDate); + protected override async Task OnInitializedAsync() + { + try + { + _jobId = Int32.Parse(PageState.QueryString["id"]); + Job job = await JobService.GetJobAsync(_jobId); + if (job != null) + { + _editable = !job.IsEnabled && !job.IsExecuting; + _name = job.Name; + _jobType = job.JobType; + _isEnabled = job.IsEnabled.ToString(); + _interval = job.Interval.ToString(); + _frequency = job.Frequency; + _startDate = UtcToLocal(job.StartDate); _startTime = UtcToLocal(job.StartDate); _endDate = UtcToLocal(job.EndDate); _endTime = UtcToLocal(job.EndDate); @@ -140,70 +149,132 @@ _nextDate = UtcToLocal(job.NextExecution); _nextTime = UtcToLocal(job.NextExecution); createdby = job.CreatedBy; - createdon = job.CreatedOn; - modifiedby = job.ModifiedBy; - modifiedon = job.ModifiedOn; - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message); - AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error); - } - } + createdon = job.CreatedOn; + modifiedby = job.ModifiedBy; + modifiedon = job.ModifiedOn; + } - private async Task SaveJob() - { + _initialized = true; + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message); + AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error); + } + } + + private async Task SaveJob() + { if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate)) { AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning); return; } - validated = true; - var interop = new Interop(JSRuntime); - if (await interop.FormValid(form)) - { - var job = await JobService.GetJobAsync(_jobId); - job.Name = _name; - job.JobType = _jobType; - job.IsEnabled = Boolean.Parse(_isEnabled); - job.Frequency = _frequency; - if (job.Frequency == "O") // once - { - job.Interval = 1; - } - else - { - job.Interval = int.Parse(_interval); - } - job.StartDate = _startDate.HasValue && _startTime.HasValue - ? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay)) - : null; + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(form)) + { + var job = await JobService.GetJobAsync(_jobId); + job.Name = _name; + job.JobType = _jobType; + var enabledChanged = job.IsEnabled != Boolean.Parse(_isEnabled); + job.IsEnabled = Boolean.Parse(_isEnabled); - job.EndDate = _endDate.HasValue && _endTime.HasValue - ? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay)) - : null; + if (_editable) + { + job.Frequency = _frequency; + if (job.Frequency == "O") // once + { + job.Interval = 1; + } + else + { + job.Interval = int.Parse(_interval); + } + job.StartDate = _startDate.HasValue && _startTime.HasValue + ? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay)) + : null; - job.NextExecution = _nextDate.HasValue && _nextTime.HasValue - ? LocalToUtc(_nextDate.GetValueOrDefault().Date.Add(_nextTime.GetValueOrDefault().TimeOfDay)) - : null; - job.RetentionHistory = int.Parse(_retentionHistory); + job.EndDate = _endDate.HasValue && _endTime.HasValue + ? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay)) + : null; - try - { - job = await JobService.UpdateJobAsync(job); - await logger.LogInformation("Job Updated {Job}", job); - NavigationManager.NavigateTo(NavigateUrl()); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Updating Job {Job} {Error}", job, ex.Message); - AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error); - } - } - else - { - AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning); - } - } + job.RetentionHistory = int.Parse(_retentionHistory); + } + + if (!job.IsEnabled) + { + job.NextExecution = null; + } + else if (enabledChanged) + { + job.NextExecution = null; + if(job.StartDate != null && job.StartDate < DateTime.UtcNow) + { + var startDate = DateTime.UtcNow; + if ((job.Frequency == "d" || job.Frequency == "w" || job.Frequency == "M") && job.StartDate.Value.TimeOfDay.TotalSeconds != 0) + { + // set the start time + startDate = startDate.Date.Add(job.StartDate.Value.TimeOfDay); + if(startDate < DateTime.UtcNow) + { + switch (job.Frequency) + { + case "d": + startDate = startDate.AddDays(job.Interval); + break; + case "w": + startDate = startDate.AddDays(job.Interval * 7); + break; + case "M": + startDate = startDate.AddMonths(job.Interval); + break; + } + } + } + + job.StartDate = startDate; + } + } + + await PerformSaveJobAction(job); + NavigationManager.NavigateTo(NavigateUrl()); + } + else + { + AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning); + } + } + + private async Task ExecuteJob() + { + var job = await JobService.GetJobAsync(_jobId); + if (job != null) + { + job.NextExecution = null; + + await PerformSaveJobAction(job); + + if(!job.IsStarted) + { + await JobService.StartJobAsync(_jobId); + } + + NavigationManager.NavigateTo(NavigateUrl()); + } + } + + private async Task PerformSaveJobAction(Job job) + { + try + { + job = await JobService.UpdateJobAsync(job); + await logger.LogInformation("Job Updated {Job}", job); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Updating Job {Job} {Error}", job, ex.Message); + AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error); + } + } } diff --git a/Oqtane.Client/Resources/Modules/Admin/Jobs/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Jobs/Edit.resx index 672f5dc9..0ac43748 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Jobs/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Jobs/Edit.resx @@ -195,4 +195,10 @@ Start Date cannot be after End Date. + + The job properties is not able to be modified because it's enabled or running in progress. + + + Execute + \ No newline at end of file From 5c70f4f6a7530718ec115b2858e85ad635da5828 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 9 Jan 2026 08:44:13 +0800 Subject: [PATCH 07/27] Fix #5948: re-render the module message when it's been changed. --- .../Modules/Controls/ModuleMessage.razor | 2 +- Oqtane.Client/UI/RenderModeBoundary.razor | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/ModuleMessage.razor b/Oqtane.Client/Modules/Controls/ModuleMessage.razor index f904b5a6..c365a321 100644 --- a/Oqtane.Client/Modules/Controls/ModuleMessage.razor +++ b/Oqtane.Client/Modules/Controls/ModuleMessage.razor @@ -21,7 +21,7 @@ @if (_style == MessageStyle.Toast) { -
+
- +
+ @foreach (var version in _versions) + { + + } + +
+
+
+
- - - -
-
} else { @@ -528,7 +519,6 @@ private string _togglesmtpclientsecret = string.Empty; private string _smtpscopes = string.Empty; private string _smtpsender = string.Empty; - private string _smtprelay = "False"; private int _retention = 30; private string _pwaisenabled; @@ -647,7 +637,6 @@ _togglesmtpclientsecret = SharedLocalizer["ShowPassword"]; _smtpscopes = SettingService.GetSetting(settings, "SMTPScopes", string.Empty); _smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty); - _smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False"); _retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30")); } @@ -840,7 +829,6 @@ settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true); settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); - settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true); settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true); if (_smtpenabled == "True") diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 64d16606..3d41770b 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -339,12 +339,6 @@ Home Page: - - This option will send email directly from the sender's unique email address rather than from the authorized Email Sender specified below. This option should only be used when the SMTP service has been configured with SPF/DKIM/DMARC for each unique sender, or else the email will be identified as malicious spoofing. - - - Allow Sender Delegation? - The site map url for this site which can be submitted to search engines for indexing. The sitemap is cached for 5 minutes and the cache can be manually cleared. diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 3114f9e7..5d0ba275 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -170,17 +170,7 @@ namespace Oqtane.Infrastructure fromName = string.IsNullOrEmpty(fromName) ? user.DisplayName ?? "" : fromName; } } - - // preserve reply to - var replyToEmail = fromEmail; - var replyToName = fromName; - - // SMTP Sender should always be used when Sender Delegation is disabled (default) or if the "from" email address is not specified (ie. system messages) - if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") != "True" || string.IsNullOrEmpty(fromEmail)) - { - fromEmail = settingRepository.GetSettingValue(settings, "SMTPSender", ""); - fromName = string.IsNullOrEmpty(fromName) ? site.Name : fromName; - } + fromName = string.IsNullOrEmpty(fromName) ? site.Name : fromName; // get recipient from user information if "to" email or name is not specified and user id is available if ((string.IsNullOrEmpty(toEmail) || string.IsNullOrEmpty(toName)) && notification.ToUserId != null) @@ -199,29 +189,29 @@ namespace Oqtane.Infrastructure MailboxAddress replyTo = null; var mailboxAddressValidationError = ""; - // sender - if (MailboxAddress.TryParse(fromEmail, out from)) + // always send from SMTP Sender + if (MailboxAddress.TryParse(settingRepository.GetSettingValue(settings, "SMTPSender", ""), out from)) { - from.Name = fromName; //override with "from" name set previously + from.Name = fromName; } else { - mailboxAddressValidationError += $" Invalid Sender: {fromName} <{fromEmail}>"; + mailboxAddressValidationError += $" Invalid Sender: {fromName} <{settingRepository.GetSettingValue(settings, "SMTPSender", "")}>"; } // reply to - if (!string.IsNullOrEmpty(replyToEmail) && replyToEmail != fromEmail) + if (!string.IsNullOrEmpty(fromEmail) && fromEmail != from.Address) { - if (MailboxAddress.TryParse(replyToEmail, out replyTo)) + if (MailboxAddress.TryParse(fromEmail, out replyTo)) { - replyTo.Name = replyToName; //override with "replyToName" name set previously + replyTo.Name = fromName; } } // recipient if (MailboxAddress.TryParse(toEmail, out to)) { - to.Name = toName; //override with "to" name set previously + to.Name = toName; } else { From 1a777b29e00757923e7031e73d8e8f498c079767 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 19 Jan 2026 11:36:27 -0500 Subject: [PATCH 22/27] allow SQL Management to support non-tenant databases --- Oqtane.Client/Modules/Admin/Sql/Index.razor | 248 +++++++++--------- Oqtane.Server/Controllers/SystemController.cs | 2 +- 2 files changed, 129 insertions(+), 121 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Sql/Index.razor b/Oqtane.Client/Modules/Admin/Sql/Index.razor index bf2eefec..79da5c01 100644 --- a/Oqtane.Client/Modules/Admin/Sql/Index.razor +++ b/Oqtane.Client/Modules/Admin/Sql/Index.razor @@ -83,14 +83,14 @@ else { @if (_connection != "-") { - @if (!string.IsNullOrEmpty(_tenant)) - { -
- -
- -
+
+ +
+
+
+ @if (!string.IsNullOrEmpty(_tenant)) + {
@@ -150,130 +150,138 @@ else } @code { - private string _connection = "-"; - private Dictionary _connections; - private List _tenants; - private List _databases; + private string _connection = "-"; + private Dictionary _connections; + private List _tenants; + private List _databases; - private string _name = string.Empty; - private string _databasetype = string.Empty; - private Type _databaseConfigType; - private object _databaseConfig; - private RenderFragment DatabaseConfigComponent { get; set; } - private bool _showConnectionString = false; - private string _tenant = string.Empty; - private string _connectionstring = string.Empty; - private string _connectionstringtype = "password"; - private string _connectionstringtoggle = string.Empty; - private string _sql = string.Empty; - private List> _results; + private string _name = string.Empty; + private string _databasetype = string.Empty; + private Type _databaseConfigType; + private object _databaseConfig; + private RenderFragment DatabaseConfigComponent { get; set; } + private bool _showConnectionString = false; + private string _tenant = string.Empty; + private string _connectionstring = string.Empty; + private string _connectionstringtype = "password"; + private string _connectionstringtoggle = string.Empty; + private string _sql = string.Empty; + private List> _results; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - protected override async Task OnInitializedAsync() - { - try - { - _connections = await SystemService.GetSystemInfoAsync("connectionstrings"); - _tenants = await TenantService.GetTenantsAsync(); - _databases = await DatabaseService.GetDatabasesAsync(); - _connectionstringtoggle = SharedLocalizer["ShowPassword"]; - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message); - AddModuleMessage(ex.Message, MessageType.Error); - } - } + protected override async Task OnInitializedAsync() + { + try + { + _connections = await SystemService.GetSystemInfoAsync("connectionstrings"); + _tenants = await TenantService.GetTenantsAsync(); + _databases = await DatabaseService.GetDatabasesAsync(); + _connectionstringtoggle = SharedLocalizer["ShowPassword"]; + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message); + AddModuleMessage(ex.Message, MessageType.Error); + } + } - private async void ConnectionChanged(ChangeEventArgs e) - { - try - { - _connection = (string)e.Value; - if (_connection != "-" && _connection != "+") - { - _connectionstring = _connections[_connection].ToString(); - _tenant = ""; - _databasetype = ""; - var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection); - if (tenant != null) - { - _tenant = tenant.Name; + private async void ConnectionChanged(ChangeEventArgs e) + { + try + { + _connection = (string)e.Value; + if (_connection != "-" && _connection != "+") + { + _connectionstring = _connections[_connection].ToString(); + _tenant = ""; + _databasetype = ""; + var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection); + if (tenant != null) + { + _tenant = tenant.Name; // hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order _databasetype = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name; - } - } - else - { - if (_databases.Exists(item => item.IsDefault)) - { - _databasetype = _databases.Find(item => item.IsDefault).Name; - } - else - { + } + else + { + if (_connection.Contains(" (")) + { + _databasetype = _connection.Substring(_connection.LastIndexOf(" (") + 2).Replace(")", ""); + } + } + } + else + { + if (_databases.Exists(item => item.IsDefault)) + { + _databasetype = _databases.Find(item => item.IsDefault).Name; + } + else + { _databasetype = Constants.DefaultDBName; - } - _showConnectionString = false; - LoadDatabaseConfigComponent(); - } - StateHasChanged(); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message); - AddModuleMessage(ex.Message, MessageType.Error); - } - } + } + _showConnectionString = false; + LoadDatabaseConfigComponent(); + } + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message); + AddModuleMessage(ex.Message, MessageType.Error); + } + } - private void DatabaseTypeChanged(ChangeEventArgs eventArgs) - { - try - { - _databasetype = (string)eventArgs.Value; - _showConnectionString = false; - LoadDatabaseConfigComponent(); - } - catch - { - AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error); - } - } + private void DatabaseTypeChanged(ChangeEventArgs eventArgs) + { + try + { + _databasetype = (string)eventArgs.Value; + _showConnectionString = false; + LoadDatabaseConfigComponent(); + } + catch + { + AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error); + } + } - private void LoadDatabaseConfigComponent() - { - var database = _databases.SingleOrDefault(d => d.Name == _databasetype); - if (database != null) - { - _databaseConfigType = Type.GetType(database.ControlType); - DatabaseConfigComponent = builder => - { - builder.OpenComponent(0, _databaseConfigType); - builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); }); - builder.CloseComponent(); - }; - } - } + private void LoadDatabaseConfigComponent() + { + var database = _databases.SingleOrDefault(d => d.Name == _databasetype); + if (database != null) + { + _databaseConfigType = Type.GetType(database.ControlType); + DatabaseConfigComponent = builder => + { + builder.OpenComponent(0, _databaseConfigType); + builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); }); + builder.CloseComponent(); + }; + } + } - private void ShowConnectionString() - { - if (_databaseConfig is IDatabaseConfigControl databaseConfigControl) - { - _connectionstring = databaseConfigControl.GetConnectionString(); - } - _showConnectionString = !_showConnectionString; - } + private void ShowConnectionString() + { + if (_databaseConfig is IDatabaseConfigControl databaseConfigControl) + { + _connectionstring = databaseConfigControl.GetConnectionString(); + } + _showConnectionString = !_showConnectionString; + } - private async Task Add() - { - var connectionstring = _connectionstring; - if (!_showConnectionString && _databaseConfig is IDatabaseConfigControl databaseConfigControl) - { - connectionstring = databaseConfigControl.GetConnectionString(); - } - if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring)) - { - var settings = new Dictionary(); + private async Task Add() + { + var connectionstring = _connectionstring; + if (!_showConnectionString && _databaseConfig is IDatabaseConfigControl databaseConfigControl) + { + connectionstring = databaseConfigControl.GetConnectionString(); + } + if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring)) + { + _name = _name + " (" + _databasetype +")"; + var settings = new Dictionary(); settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring); await SystemService.UpdateSystemInfoAsync(settings); _connections = await SystemService.GetSystemInfoAsync("connectionstrings"); diff --git a/Oqtane.Server/Controllers/SystemController.cs b/Oqtane.Server/Controllers/SystemController.cs index ce067c2e..bc87008e 100644 --- a/Oqtane.Server/Controllers/SystemController.cs +++ b/Oqtane.Server/Controllers/SystemController.cs @@ -128,7 +128,7 @@ namespace Oqtane.Controllers } break; default: - _configManager.AddOrUpdateSetting(key, value, false); + _configManager.AddOrUpdateSetting(key, value, true); break; } } From 54d3f1c65968a02732e2f8e439834396850ec2ad Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 19 Jan 2026 11:56:54 -0500 Subject: [PATCH 23/27] include message to notify user of change in connection name format --- Oqtane.Client/Modules/Admin/Sql/Index.razor | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Oqtane.Client/Modules/Admin/Sql/Index.razor b/Oqtane.Client/Modules/Admin/Sql/Index.razor index 79da5c01..1427daa4 100644 --- a/Oqtane.Client/Modules/Admin/Sql/Index.razor +++ b/Oqtane.Client/Modules/Admin/Sql/Index.razor @@ -209,6 +209,10 @@ else { _databasetype = _connection.Substring(_connection.LastIndexOf(" (") + 2).Replace(")", ""); } + else + { + _databasetype = $"Connection Needs To Be Renamed: '{_connection} (Type)'"; + } } } else From 0446bdb970e2268001f6944118fa73e6719e55dc Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 19 Jan 2026 12:42:24 -0500 Subject: [PATCH 24/27] use primary database type if type not explicitly specified --- Oqtane.Client/Modules/Admin/Sql/Index.razor | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Sql/Index.razor b/Oqtane.Client/Modules/Admin/Sql/Index.razor index 1427daa4..dc7ff28e 100644 --- a/Oqtane.Client/Modules/Admin/Sql/Index.razor +++ b/Oqtane.Client/Modules/Admin/Sql/Index.razor @@ -211,7 +211,14 @@ else } else { - _databasetype = $"Connection Needs To Be Renamed: '{_connection} (Type)'"; + if (_databases.Exists(item => item.IsDefault)) + { + _databasetype = _databases.Find(item => item.IsDefault).Name; + } + else + { + _databasetype = Constants.DefaultDBName; + } } } } From 472e8eadec95dfb517b92e22a80340f33d843528 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 20 Jan 2026 14:44:06 -0500 Subject: [PATCH 25/27] update azuredeploy.json --- Oqtane.Client/Modules/HtmlText/Index.razor | 45 +++++++++++++++++++++- azuredeploy.json | 2 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/HtmlText/Index.razor b/Oqtane.Client/Modules/HtmlText/Index.razor index c3fa8b39..7cc2e946 100644 --- a/Oqtane.Client/Modules/HtmlText/Index.razor +++ b/Oqtane.Client/Modules/HtmlText/Index.razor @@ -5,6 +5,15 @@ @inject ISettingService SettingService @inject IStringLocalizer Localizer +@for (int i = 1; i < 6; i++) +{ + string product = $"product{i}"; +
+ + +
+} + @if (PageState.EditMode) {
@@ -24,7 +33,41 @@ @code { private string content = ""; - public override string RenderMode => RenderModes.Static; + //public override string RenderMode => RenderModes.Static; + + Dictionary _labels = new Dictionary { + { ":product1", "成为顾问 (FIA)"}, + { ":product2", "Market Shield Savings Plan (MS)"}, + { ":product3", "Global Term Life Insurance"}, + { ":product4", "Protection Advantage Universal Life Insurance"}, + { ":product5", "Summit Indexed Universal Life Insurance"} + }; + + private List _products = new List(); + + private void ProductChanged(string product) + { + if (_products.Contains(product)) + { + _products.Remove(product); + } + else + { + _products.Add(product); + } + } + + private string GetLabel(string key) + { + if (_labels.ContainsKey($"{PageState.Alias.Path}:{key}")) + { + return _labels[$"{PageState.Alias.Path}:{key}"]; + } + else + { + return ""; + } + } protected override async Task OnParametersSetAsync() { diff --git a/azuredeploy.json b/azuredeploy.json index 1aa722fa..72478428 100644 --- a/azuredeploy.json +++ b/azuredeploy.json @@ -220,7 +220,7 @@ "apiVersion": "2024-04-01", "name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]", "properties": { - "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v10.0.3/Oqtane.Framework.10.0.3.Install.zip" + "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v10.0.4/Oqtane.Framework.10.0.4.Install.zip" }, "dependsOn": [ "[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]" From 0a3f60c00778ad39455d2c87921aa8e42658a21d Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 20 Jan 2026 14:50:51 -0500 Subject: [PATCH 26/27] undo previous commit --- Oqtane.Client/Modules/HtmlText/Index.razor | 45 +--------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/Oqtane.Client/Modules/HtmlText/Index.razor b/Oqtane.Client/Modules/HtmlText/Index.razor index 7cc2e946..c3fa8b39 100644 --- a/Oqtane.Client/Modules/HtmlText/Index.razor +++ b/Oqtane.Client/Modules/HtmlText/Index.razor @@ -5,15 +5,6 @@ @inject ISettingService SettingService @inject IStringLocalizer Localizer -@for (int i = 1; i < 6; i++) -{ - string product = $"product{i}"; -
- - -
-} - @if (PageState.EditMode) {
@@ -33,41 +24,7 @@ @code { private string content = ""; - //public override string RenderMode => RenderModes.Static; - - Dictionary _labels = new Dictionary { - { ":product1", "成为顾问 (FIA)"}, - { ":product2", "Market Shield Savings Plan (MS)"}, - { ":product3", "Global Term Life Insurance"}, - { ":product4", "Protection Advantage Universal Life Insurance"}, - { ":product5", "Summit Indexed Universal Life Insurance"} - }; - - private List _products = new List(); - - private void ProductChanged(string product) - { - if (_products.Contains(product)) - { - _products.Remove(product); - } - else - { - _products.Add(product); - } - } - - private string GetLabel(string key) - { - if (_labels.ContainsKey($"{PageState.Alias.Path}:{key}")) - { - return _labels[$"{PageState.Alias.Path}:{key}"]; - } - else - { - return ""; - } - } + public override string RenderMode => RenderModes.Static; protected override async Task OnParametersSetAsync() { From d0fb8dc5fcec584596b0c9efb66ca607977d9e38 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 20 Jan 2026 14:55:32 -0500 Subject: [PATCH 27/27] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c9d5f3a..7f5c9ca3 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Oqtane is being developed based on some fundamental principles which are outline # Latest Release -[10.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.3) was released on December 24, 2025 and is a maintenance release including 19 pull requests by 2 different contributors, pushing the total number of project commits all-time to nearly 7500. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[10.0.4](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.4) was released on January 20, 2026 and is a maintenance release including 27 pull requests by 3 different contributors, pushing the total number of project commits all-time over 7500. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. # Try It Now! @@ -111,6 +111,9 @@ Connect with other developers, get support, and share ideas by joining the Oqtan # Roadmap This project is open source, and therefore is a work in progress... +[10.0.4](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.4) (Jan 20, 2026) +- [x] Stabilization improvements + [10.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.3) (Dec 24, 2025) - [x] Stabilization improvements