diff --git a/Oqtane.Client/Installer/Installer.razor b/Oqtane.Client/Installer/Installer.razor index da720228..a3969946 100644 --- a/Oqtane.Client/Installer/Installer.razor +++ b/Oqtane.Client/Installer/Installer.razor @@ -119,7 +119,13 @@


- + @if (!string.IsNullOrEmpty(_message)) + { + + }
@@ -316,4 +322,9 @@ _showConnectionString = !_showConnectionString; } + private void DismissModal() + { + _message = ""; + StateHasChanged(); + } } diff --git a/Oqtane.Client/Themes/Controls/Theme/MenuBase.cs b/Oqtane.Client/Themes/Controls/Theme/MenuBase.cs index dd697ef0..689b7a04 100644 --- a/Oqtane.Client/Themes/Controls/Theme/MenuBase.cs +++ b/Oqtane.Client/Themes/Controls/Theme/MenuBase.cs @@ -19,12 +19,7 @@ namespace Oqtane.Themes.Controls { if (page.IsClickable) { - var url = string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url; - if (PageState.QueryString.ContainsKey("method")) - { - url += ((url.Contains("?")) ? "&" : "?") + "method=" + PageState.QueryString["method"]; - } - return url; + return string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url; } else { diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor index 6ee92baa..0719a546 100644 --- a/Oqtane.Client/UI/ThemeBuilder.razor +++ b/Oqtane.Client/UI/ThemeBuilder.razor @@ -42,16 +42,6 @@ } headcontent += $"\n"; - if (PageState.QueryString.ContainsKey("method") && PageState.QueryString["method"] == "new") - { - // stylesheets - foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) - { - var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; - headcontent += "" + "\n"; - } - } - // head content AddHeadContent(headcontent, PageState.Site.HeadContent); if (!string.IsNullOrEmpty(PageState.Site.HeadContent)) @@ -106,27 +96,24 @@ } } - if (!PageState.QueryString.ContainsKey("method") || (PageState.QueryString.ContainsKey("method") && PageState.QueryString["method"] == "old")) + // style sheets + if (PageState.Page.Resources != null && PageState.Page.Resources.Exists(item => item.ResourceType == ResourceType.Stylesheet)) { - // style sheets - if (PageState.Page.Resources != null && PageState.Page.Resources.Exists(item => item.ResourceType == ResourceType.Stylesheet)) + var interop = new Interop(JSRuntime); + string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"); + var links = new List(); + foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) { - var interop = new Interop(JSRuntime); - string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"); - var links = new List(); - foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) - { - var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower(); - var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; - links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix }); - } - if (links.Any()) - { - await interop.IncludeLinks(links.ToArray()); - } - await interop.RemoveElementsById("app-stylesheet-page-", "", "app-stylesheet-page-" + batch + "-00"); - await interop.RemoveElementsById("app-stylesheet-module-", "", "app-stylesheet-module-" + batch + "-00"); + var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower(); + var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; + links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix }); } + if (links.Any()) + { + await interop.IncludeLinks(links.ToArray()); + } + await interop.RemoveElementsById("app-stylesheet-page-", "", "app-stylesheet-page-" + batch + "-00"); + await interop.RemoveElementsById("app-stylesheet-module-", "", "app-stylesheet-module-" + batch + "-00"); } } diff --git a/Oqtane.Server/Infrastructure/Interfaces/IHostResources.cs b/Oqtane.Server/Infrastructure/Interfaces/IHostResources.cs index f22c53f0..1abb0845 100644 --- a/Oqtane.Server/Infrastructure/Interfaces/IHostResources.cs +++ b/Oqtane.Server/Infrastructure/Interfaces/IHostResources.cs @@ -1,11 +1,12 @@ using Oqtane.Models; +using System; using System.Collections.Generic; namespace Oqtane.Infrastructure { - // this interface is for declaring global resources and is useful for scenarios where you want to declare resources in a single location for the entire application public interface IHostResources { + [Obsolete("IHostResources is deprecated. Use module or theme scoped Resources in conjunction with ResourceLevel.Site instead.", false)] List Resources { get; } // identifies global resources for an application } } diff --git a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs index f3f70ebe..1c9eb377 100644 --- a/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs +++ b/Oqtane.Server/Infrastructure/Jobs/HostedServiceBase.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; @@ -38,17 +39,23 @@ namespace Oqtane.Infrastructure { await Task.Yield(); // required so that this method does not block startup - try - { - while (!stoppingToken.IsCancellationRequested) + while (!stoppingToken.IsCancellationRequested) + { + using (var scope = _serviceScopeFactory.CreateScope()) { - using (var scope = _serviceScopeFactory.CreateScope()) + ILogger _filelogger = scope.ServiceProvider.GetRequiredService>(); + + try { + var jobs = scope.ServiceProvider.GetRequiredService(); + var jobLogs = scope.ServiceProvider.GetRequiredService(); + var tenantRepository = scope.ServiceProvider.GetRequiredService(); + var tenantManager = scope.ServiceProvider.GetRequiredService(); + // get name of job string jobType = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); // load jobs and find current job - IJobRepository jobs = scope.ServiceProvider.GetRequiredService(); Job job = jobs.GetJobs().Where(item => item.JobType == jobType).FirstOrDefault(); if (job != null && job.IsEnabled && !job.IsExecuting) { @@ -73,7 +80,9 @@ namespace Oqtane.Infrastructure // determine if the job should be run if (NextExecution <= DateTime.UtcNow && (job.EndDate == null || job.EndDate >= DateTime.UtcNow)) { - IJobLogRepository jobLogs = scope.ServiceProvider.GetRequiredService(); + // update the job to indicate it is running + job.IsExecuting = true; + jobs.UpdateJob(job); // create a job log entry JobLog log = new JobLog(); @@ -84,16 +93,10 @@ namespace Oqtane.Infrastructure log.Notes = ""; log = jobLogs.AddJobLog(log); - // update the job to indicate it is running - job.IsExecuting = true; - jobs.UpdateJob(job); - // execute the job try { var notes = ""; - var tenantRepository = scope.ServiceProvider.GetRequiredService(); - var tenantManager = scope.ServiceProvider.GetRequiredService(); foreach (var tenant in tenantRepository.GetTenants()) { // set tenant and execute job @@ -133,16 +136,19 @@ namespace Oqtane.Infrastructure } } } - - // wait 1 minute - await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + catch (Exception ex) + { + // can occur during the initial installation because the database has not yet been created + if (!ex.Message.Contains("No database provider has been configured for this DbContext")) + { + _filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Executing Scheduled Job: {Name} - {ex}")); + } + } } - } - catch - { - // can occur during the initial installation as there is no DBContext - } + // wait 1 minute + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + } } private DateTime CalculateNextExecution(DateTime nextExecution, Job job) @@ -191,9 +197,11 @@ namespace Oqtane.Infrastructure public Task StartAsync(CancellationToken cancellationToken) { - try + using (var scope = _serviceScopeFactory.CreateScope()) { - using (var scope = _serviceScopeFactory.CreateScope()) + ILogger _filelogger = scope.ServiceProvider.GetRequiredService>(); + + try { string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); IJobRepository jobs = scope.ServiceProvider.GetRequiredService(); @@ -207,7 +215,7 @@ namespace Oqtane.Infrastructure } else { - // auto registration - job will not run on initial installation but will run after restart + // auto registration - job will not run on initial installation due to no DBContext but will run after restart job = new Job { JobType = jobTypeName }; // optional HostedServiceBase properties @@ -233,17 +241,21 @@ namespace Oqtane.Infrastructure jobs.AddJob(job); } } - - _executingTask = ExecuteAsync(_cancellationTokenSource.Token); - - if (_executingTask.IsCompleted) + catch (Exception ex) { - return _executingTask; + // can occur during the initial installation because the database has not yet been created + if (!ex.Message.Contains("No database provider has been configured for this DbContext")) + { + _filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Starting Scheduled Job: {Name} - {ex}")); + } } } - catch + + _executingTask = ExecuteAsync(_cancellationTokenSource.Token); + + if (_executingTask.IsCompleted) { - // can occur during the initial installation because this method is called during startup and the database has not yet been created + return _executingTask; } return Task.CompletedTask; @@ -251,9 +263,11 @@ namespace Oqtane.Infrastructure public async Task StopAsync(CancellationToken cancellationToken) { - try + using (var scope = _serviceScopeFactory.CreateScope()) { - using (var scope = _serviceScopeFactory.CreateScope()) + ILogger _filelogger = scope.ServiceProvider.GetRequiredService>(); + + try { string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); IJobRepository jobs = scope.ServiceProvider.GetRequiredService(); @@ -266,10 +280,11 @@ namespace Oqtane.Infrastructure jobs.UpdateJob(job); } } - } - catch - { - // error updating the job + catch (Exception ex) + { + // error updating the job + _filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Stopping Scheduled Job: {Name} - {ex}")); + } } // stop called without start diff --git a/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs b/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs index 7ff17805..6a20b984 100644 --- a/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs +++ b/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs @@ -57,10 +57,10 @@ namespace Oqtane.SiteTemplates new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.Edit, RoleNames.Admin, true) }, - Content = "

Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET Core. It leverages the Blazor component model to compose a fully dynamic web development experience which can be hosted either client-side or server-side. Whether you are looking for a platform to accelerate your web development efforts, or simply interested in exploring the anatomy of a large-scale Blazor application, Oqtane provides a solid foundation based on proven enterprise architectural principles.

" + - "

Join Our Community  Clone Our Repo

" + - "

Blazor is an open source and cross-platform web UI framework for building single-page applications using .NET and C#. Blazor applications can be hosted in a variety of ways. Blazor Server uses SignalR (WebSockets) to host your application on a web server and provide a responsive and robust development experience. Blazor WebAssembly relies on Wasm, an open web standard that does not require plugins in order for applications to run natively in a web browser. Blazor Hybrid is part of .NET MAUI and uses a Web View to render components natively on mobile and desktop devices. Razor components can be used with all of the hosting models without any modification.

" + - "

Blazor is a feature of .NET Core, the popular cross platform web development framework from Microsoft that extends the .NET developer platform with tools and libraries for building web apps.

" + Content = "

Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a fully dynamic digital experience. Whether you are looking for a platform to accelerate your web development efforts, or simply interested in exploring the anatomy of a large-scale Blazor application, Oqtane provides a solid foundation based on proven enterprise architectural principles and patterns.

" + + "

Join Our Community  Clone Our Repo

" + + "

Blazor is an open source and cross-platform web UI framework for building single-page applications using .NET and C#. Blazor applications can be hosted in a variety of ways. Blazor Server uses SignalR (WebSockets) to host your application on a web server and provide a responsive and robust development experience. Blazor WebAssembly relies on Wasm, an open web standard that does not require plugins in order for applications to run natively in a web browser. Blazor Hybrid is part of .NET MAUI and uses a Web View to render components natively on mobile and desktop devices. Razor components can be shared across all of the hosting models without any modification.

" + + "

Blazor is a feature of ASP.NET, the popular cross platform development framework from Microsoft that provides powerful tools and libraries for building modern software applications.

" }, new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = PaneNames.Default, PermissionList = new List { diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 55ddb127..4b44e341 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -167,29 +167,26 @@ namespace Oqtane.Pages } // stylesheets - if (!HttpContext.Request.Query.ContainsKey("method") || (HttpContext.Request.Query.ContainsKey("method") && HttpContext.Request.Query["method"] == "old")) + var resources = new List(); + if (string.IsNullOrEmpty(page.ThemeType)) { - var resources = new List(); - if (string.IsNullOrEmpty(page.ThemeType)) - { - page.ThemeType = site.DefaultThemeType; - } - var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType)); - if (theme?.Resources != null) - { - resources.AddRange(theme.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet).ToList()); - } - var type = Type.GetType(page.ThemeType); - if (type != null) - { - var obj = Activator.CreateInstance(type) as IThemeControl; - if (obj?.Resources != null) - { - resources.AddRange(obj.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet).ToList()); - } - } - ManageStyleSheets(resources, alias, theme.ThemeName); + page.ThemeType = site.DefaultThemeType; } + var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType)); + if (theme?.Resources != null) + { + resources.AddRange(theme.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet).ToList()); + } + var type = Type.GetType(page.ThemeType); + if (type != null) + { + var obj = Activator.CreateInstance(type) as IThemeControl; + if (obj?.Resources != null) + { + resources.AddRange(obj.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet).ToList()); + } + } + ManageStyleSheets(resources, alias, theme.ThemeName); // scripts if (Runtime == "Server") diff --git a/README.md b/README.md index b2d30007..cd8e5080 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ![Oqtane](https://github.com/oqtane/framework/blob/master/oqtane.png?raw=true "Oqtane") -Oqtane is a CMS and Application Framework. It leverages Blazor, an open source and cross-platform web UI framework for building single-page apps using .NET and C# instead of JavaScript. Blazor apps are composed of reusable web UI components implemented using C#, HTML, and CSS. Both client and server code is written in C#, allowing you to share code and libraries. +Oqtane is a CMS and Application Framework. It leverages Blazor, an open source and cross-platform web UI framework for building modern apps using .NET and C# instead of JavaScript. Blazor apps are composed of reusable web UI components implemented using C#, HTML, and CSS. Both client and server code is written in C#, allowing you to share code and libraries. Oqtane is being developed based on some fundamental principles which are outlined in the [Oqtane Philosophy](https://www.oqtane.org/blog/!/20/oqtane-philosophy).