Merge remote-tracking branch 'oqtane/dev' into dev

This commit is contained in:
Leigh Pointer 2023-06-26 14:57:48 +02:00
commit 97026746f8
8 changed files with 104 additions and 98 deletions

View File

@ -119,7 +119,13 @@
<div class="row">
<div class="mx-auto text-center">
<button type="button" class="btn btn-success" @onclick="Install">@Localizer["InstallNow"]</button><br /><br />
<ModuleMessage Message="@_message" Type="MessageType.Error"></ModuleMessage>
@if (!string.IsNullOrEmpty(_message))
{
<div class="alert alert-danger alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message)
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
</div>
}
</div>
<div class="app-progress-indicator" style="@_loadingDisplay"></div>
</div>
@ -316,4 +322,9 @@
_showConnectionString = !_showConnectionString;
}
private void DismissModal()
{
_message = "";
StateHasChanged();
}
}

View File

@ -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
{

View File

@ -42,16 +42,6 @@
}
headcontent += $"<link id=\"app-favicon\" rel=\"shortcut icon\" type=\"image/{favicontype}\" href=\"{favicon}\" />\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 += "<link rel=\"stylesheet\" href=\"" + url + "\"" + (!string.IsNullOrEmpty(resource.Integrity) ? " integrity=\"" + resource.Integrity + "\"" : "") + (!string.IsNullOrEmpty(resource.CrossOrigin) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") + " type=\"text/css\"/>" + "\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<object>();
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<object>();
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");
}
}

View File

@ -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<Resource> Resources { get; } // identifies global resources for an application
}
}

View File

@ -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<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
try
{
var jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
var jobLogs = scope.ServiceProvider.GetRequiredService<IJobLogRepository>();
var tenantRepository = scope.ServiceProvider.GetRequiredService<ITenantRepository>();
var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>();
// get name of job
string jobType = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
// load jobs and find current job
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
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<IJobLogRepository>();
// 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<ITenantRepository>();
var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>();
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<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
try
{
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
@ -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<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
try
{
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
@ -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

View File

@ -57,10 +57,10 @@ namespace Oqtane.SiteTemplates
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = "<p><a href=\"https://www.oqtane.org\" target=\"_new\">Oqtane</a> is an open source <b>CMS</b> and <b>Application Framework</b> that provides advanced functionality for developing web, mobile, and desktop applications on .NET Core. It leverages the Blazor component model to compose a <b>fully dynamic</b> web development experience which can be hosted either client-side or server-side. Whether you are looking for a platform to <b>accelerate your web development</b> 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.</p>" +
"<p align=\"center\"><a href=\"https://www.oqtane.org\" target=\"_new\"><img class=\"img-fluid\" src=\"oqtane-glow.png\"></a></p><p align=\"center\"><a class=\"btn btn-primary\" href=\"https://www.oqtane.org/Community\" target=\"_new\">Join Our Community</a>&nbsp;&nbsp;<a class=\"btn btn-primary\" href=\"https://github.com/oqtane/oqtane.framework\" target=\"_new\">Clone Our Repo</a></p>" +
"<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> 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.</p>" +
"<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">.NET Core</a>, the popular cross platform web development framework from Microsoft that extends the <a href=\"https://dotnet.microsoft.com/learn/dotnet/what-is-dotnet\" target=\"_new\" >.NET developer platform</a> with tools and libraries for building web apps.</p>"
Content = "<p><a href=\"https://www.oqtane.org\" target=\"_new\">Oqtane</a> is an open source <b>CMS</b> and <b>Application Framework</b> that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a <b>fully dynamic</b> digital experience. Whether you are looking for a platform to <b>accelerate your web development</b> 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.</p>" +
"<p align=\"center\"><a href=\"https://www.oqtane.org\" target=\"_new\"><img class=\"img-fluid\" src=\"oqtane-glow.png\"></a></p><p align=\"center\"><a class=\"btn btn-primary\" href=\"https://www.oqtane.org\" target=\"_new\">Join Our Community</a>&nbsp;&nbsp;<a class=\"btn btn-primary\" href=\"https://github.com/oqtane/oqtane.framework\" target=\"_new\">Clone Our Repo</a></p>" +
"<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> 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.</p>" +
"<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">ASP.NET</a>, the popular cross platform development framework from Microsoft that provides powerful tools and libraries for building modern software applications.</p>"
},
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = PaneNames.Default,
PermissionList = new List<Permission> {

View File

@ -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<Resource>();
if (string.IsNullOrEmpty(page.ThemeType))
{
var resources = new List<Resource>();
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")

View File

@ -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).