mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
216 changed files with 2812 additions and 2835 deletions
@ -0,0 +1,57 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Security.Claims; |
||||
|
using Jint; |
||||
|
using Jint.Native; |
||||
|
using Jint.Runtime.Interop; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Scripting.ContentWrapper; |
||||
|
using Squidex.Shared.Users; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public sealed class DefaultConverter : IObjectConverter |
||||
|
{ |
||||
|
public static readonly DefaultConverter Instance = new DefaultConverter(); |
||||
|
|
||||
|
private DefaultConverter() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public bool TryConvert(Engine engine, object value, out JsValue result) |
||||
|
{ |
||||
|
result = null; |
||||
|
|
||||
|
if (value is Enum) |
||||
|
{ |
||||
|
result = value.ToString(); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
switch (value) |
||||
|
{ |
||||
|
case IUser user: |
||||
|
result = JintUser.Create(engine, user); |
||||
|
return true; |
||||
|
case ClaimsPrincipal principal: |
||||
|
result = JintUser.Create(engine, principal); |
||||
|
return true; |
||||
|
case Instant instant: |
||||
|
result = JsValue.FromObject(engine, instant.ToDateTimeUtc()); |
||||
|
return true; |
||||
|
case NamedContentData content: |
||||
|
result = new ContentDataObject(engine, content); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,79 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Microsoft.Extensions.Configuration; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Squidex.Areas.Api.Config.Swagger; |
|
||||
using Squidex.Areas.Api.Controllers.Contents; |
|
||||
using Squidex.Areas.IdentityServer.Config; |
|
||||
using Squidex.Config; |
|
||||
using Squidex.Config.Authentication; |
|
||||
using Squidex.Config.Domain; |
|
||||
using Squidex.Config.Web; |
|
||||
using Squidex.Domain.Apps.Entities.Assets; |
|
||||
using Squidex.Domain.Apps.Entities.Contents; |
|
||||
using Squidex.Extensions.Actions.Twitter; |
|
||||
using Squidex.Infrastructure.Commands; |
|
||||
using Squidex.Infrastructure.Diagnostics; |
|
||||
using Squidex.Pipeline; |
|
||||
using Squidex.Pipeline.Robots; |
|
||||
|
|
||||
namespace Squidex |
|
||||
{ |
|
||||
public static class AppServices |
|
||||
{ |
|
||||
public static void AddAppServices(this IServiceCollection services, IConfiguration config) |
|
||||
{ |
|
||||
services.AddHttpClient(); |
|
||||
services.AddLogging(); |
|
||||
services.AddMemoryCache(); |
|
||||
services.AddOptions(); |
|
||||
|
|
||||
services.AddMyAssetServices(config); |
|
||||
services.AddMyAuthentication(config); |
|
||||
services.AddMyEntitiesServices(config); |
|
||||
services.AddMyEventPublishersServices(config); |
|
||||
services.AddMyEventStoreServices(config); |
|
||||
services.AddMyIdentityServer(); |
|
||||
services.AddMyInfrastructureServices(); |
|
||||
services.AddMyLoggingServices(config); |
|
||||
services.AddMyMigrationServices(); |
|
||||
services.AddMyMvc(); |
|
||||
services.AddMyRuleServices(); |
|
||||
services.AddMySerializers(); |
|
||||
services.AddMyStoreServices(config); |
|
||||
services.AddMySwaggerSettings(); |
|
||||
services.AddMySubscriptionServices(config); |
|
||||
|
|
||||
services.Configure<ContentOptions>( |
|
||||
config.GetSection("contents")); |
|
||||
services.Configure<AssetOptions>( |
|
||||
config.GetSection("assets")); |
|
||||
services.Configure<ReadonlyOptions>( |
|
||||
config.GetSection("mode")); |
|
||||
services.Configure<TwitterOptions>( |
|
||||
config.GetSection("twitter")); |
|
||||
services.Configure<RobotsTxtOptions>( |
|
||||
config.GetSection("robots")); |
|
||||
services.Configure<GCHealthCheckOptions>( |
|
||||
config.GetSection("healthz:gc")); |
|
||||
services.Configure<ETagOptions>( |
|
||||
config.GetSection("etags")); |
|
||||
|
|
||||
services.Configure<MyContentsControllerOptions>( |
|
||||
config.GetSection("contentsController")); |
|
||||
services.Configure<MyUrlsOptions>( |
|
||||
config.GetSection("urls")); |
|
||||
services.Configure<MyIdentityOptions>( |
|
||||
config.GetSection("identity")); |
|
||||
services.Configure<MyUIOptions>( |
|
||||
config.GetSection("ui")); |
|
||||
services.Configure<MyUsageOptions>( |
|
||||
config.GetSection("usage")); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,87 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.Extensions.Hosting; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Log; |
|
||||
using Squidex.Infrastructure.Migrations; |
|
||||
|
|
||||
namespace Squidex.Config.Domain |
|
||||
{ |
|
||||
public static class SystemExtensions |
|
||||
{ |
|
||||
public sealed class InitializeHostedService : IHostedService |
|
||||
{ |
|
||||
private readonly IEnumerable<IInitializable> targets; |
|
||||
private readonly IApplicationLifetime lifetime; |
|
||||
private readonly ISemanticLog log; |
|
||||
|
|
||||
public InitializeHostedService(IEnumerable<IInitializable> targets, IApplicationLifetime lifetime, ISemanticLog log) |
|
||||
{ |
|
||||
this.targets = targets; |
|
||||
this.lifetime = lifetime; |
|
||||
this.log = log; |
|
||||
} |
|
||||
|
|
||||
public async Task StartAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
foreach (var target in targets) |
|
||||
{ |
|
||||
await target.InitializeAsync(cancellationToken); |
|
||||
|
|
||||
log.LogInformation(w => w.WriteProperty("initializedSystem", target.GetType().Name)); |
|
||||
} |
|
||||
} |
|
||||
catch |
|
||||
{ |
|
||||
lifetime.StopApplication(); |
|
||||
throw; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public Task StopAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public sealed class MigratorHostedService : IHostedService |
|
||||
{ |
|
||||
private readonly IApplicationLifetime lifetime; |
|
||||
private readonly Migrator migrator; |
|
||||
|
|
||||
public MigratorHostedService(IApplicationLifetime lifetime, Migrator migrator) |
|
||||
{ |
|
||||
this.lifetime = lifetime; |
|
||||
this.migrator = migrator; |
|
||||
} |
|
||||
|
|
||||
public async Task StartAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
await migrator.MigrateAsync(); |
|
||||
} |
|
||||
catch |
|
||||
{ |
|
||||
lifetime.StopApplication(); |
|
||||
throw; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public Task StopAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,34 +1,113 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
// All rights reserved. Licensed under the MIT license.
|
// All rights reserved. Licensed under the MIT license.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Net; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
using Microsoft.Extensions.DependencyInjection; |
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Hosting; |
|
||||
using Orleans; |
using Orleans; |
||||
|
using Orleans.Configuration; |
||||
|
using Orleans.Hosting; |
||||
|
using Squidex.Domain.Apps.Entities.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Rules; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.UsageTracking; |
||||
|
using Squidex.Infrastructure.EventSourcing.Grains; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
|
||||
namespace Squidex.Config.Orleans |
namespace Squidex.Config.Orleans |
||||
{ |
{ |
||||
public static class OrleansServices |
public static class OrleansServices |
||||
{ |
{ |
||||
public static void AddOrleansSilo(this IServiceCollection services) |
public static IServiceProvider AddAndBuildOrleans(this IServiceCollection services, IConfiguration config) |
||||
{ |
{ |
||||
services.AddSingletonAs<SiloWrapper>() |
services.Configure<ClusterOptions>(options => |
||||
.As<IHostedService>() |
{ |
||||
.AsSelf(); |
options.Configure(); |
||||
|
}); |
||||
|
|
||||
|
services.Configure<ProcessExitHandlingOptions>(options => |
||||
|
{ |
||||
|
options.FastKillOnProcessExit = false; |
||||
|
}); |
||||
|
|
||||
services.AddServicesForSelfHostedDashboard(null, options => |
services.AddServicesForSelfHostedDashboard(null, options => |
||||
{ |
{ |
||||
options.HideTrace = true; |
options.HideTrace = true; |
||||
}); |
}); |
||||
|
|
||||
services.AddSingletonAs(c => c.GetRequiredService<IClusterClient>()) |
services.AddHostedService<SiloHost>(); |
||||
.As<IClusterClient>(); |
|
||||
|
var hostBuilder = new SiloHostBuilder() |
||||
|
.UseDashboardEx() |
||||
|
.EnableDirectClient() |
||||
|
.AddIncomingGrainCallFilter<LocalCacheFilter>() |
||||
|
.AddStartupTask<Bootstrap<IContentSchedulerGrain>>() |
||||
|
.AddStartupTask<Bootstrap<IEventConsumerManagerGrain>>() |
||||
|
.AddStartupTask<Bootstrap<IRuleDequeuerGrain>>() |
||||
|
.AddStartupTask<Bootstrap<IUsageTrackerGrain>>() |
||||
|
.ConfigureApplicationParts(builder => |
||||
|
{ |
||||
|
builder.AddMyParts(); |
||||
|
}); |
||||
|
|
||||
|
config.ConfigureByOption("orleans:clustering", new Options |
||||
|
{ |
||||
|
["MongoDB"] = () => |
||||
|
{ |
||||
|
hostBuilder.ConfigureEndpoints(Dns.GetHostName(), 11111, 40000, listenOnAnyHostAddress: true); |
||||
|
|
||||
|
var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); |
||||
|
var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); |
||||
|
|
||||
|
hostBuilder.UseMongoDBClustering(options => |
||||
|
{ |
||||
|
options.ConnectionString = mongoConfiguration; |
||||
|
options.CollectionPrefix = "Orleans_"; |
||||
|
options.DatabaseName = mongoDatabaseName; |
||||
|
}); |
||||
|
}, |
||||
|
["Development"] = () => |
||||
|
{ |
||||
|
hostBuilder.UseLocalhostClustering(gatewayPort: 40000, serviceId: Constants.OrleansClusterId, clusterId: Constants.OrleansClusterId); |
||||
|
hostBuilder.Configure<ClusterMembershipOptions>(options => options.ExpectedClusterSize = 1); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
config.ConfigureByOption("store:type", new Options |
||||
|
{ |
||||
|
["MongoDB"] = () => |
||||
|
{ |
||||
|
var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); |
||||
|
var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); |
||||
|
|
||||
|
hostBuilder.UseMongoDBReminders(options => |
||||
|
{ |
||||
|
options.ConnectionString = mongoConfiguration; |
||||
|
options.CollectionPrefix = "Orleans_"; |
||||
|
options.DatabaseName = mongoDatabaseName; |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
IServiceProvider provider = null; |
||||
|
|
||||
|
hostBuilder.UseServiceProviderFactory((siloServices) => |
||||
|
{ |
||||
|
foreach (var descriptor in services) |
||||
|
{ |
||||
|
siloServices.Add(descriptor); |
||||
|
} |
||||
|
|
||||
|
provider = siloServices.BuildServiceProvider(); |
||||
|
|
||||
|
return provider; |
||||
|
}).Build(); |
||||
|
|
||||
services.AddSingletonAs(c => c.GetRequiredService<SiloWrapper>().Client) |
return provider; |
||||
.As<IGrainFactory>(); |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,51 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Orleans.Hosting; |
||||
|
using Squidex.Config.Startup; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
|
||||
|
namespace Squidex.Config.Orleans |
||||
|
{ |
||||
|
public sealed class SiloHost : SafeHostedService |
||||
|
{ |
||||
|
private readonly ISiloHost silo; |
||||
|
|
||||
|
public SiloHost(ISiloHost silo, ISemanticLog log, IApplicationLifetime lifetime) |
||||
|
: base(lifetime, log) |
||||
|
{ |
||||
|
this.silo = silo; |
||||
|
} |
||||
|
|
||||
|
protected override async Task StartAsync(ISemanticLog log, CancellationToken ct) |
||||
|
{ |
||||
|
var watch = ValueStopwatch.StartNew(); |
||||
|
try |
||||
|
{ |
||||
|
await silo.StartAsync(ct); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
var elapsedMs = watch.Stop(); |
||||
|
|
||||
|
log.LogInformation(w => w |
||||
|
.WriteProperty("message", "Silo started") |
||||
|
.WriteProperty("elapsedMs", elapsedMs)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override async Task StopAsync(ISemanticLog log, CancellationToken ct) |
||||
|
{ |
||||
|
await silo.StopAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,42 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Microsoft.Extensions.Configuration; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Orleans.Hosting; |
|
||||
|
|
||||
namespace Squidex.Config.Orleans |
|
||||
{ |
|
||||
public static class SiloServices |
|
||||
{ |
|
||||
public static void AddAppSiloServices(this IServiceCollection services, IConfiguration config) |
|
||||
{ |
|
||||
config.ConfigureByOption("store:type", new Options |
|
||||
{ |
|
||||
["MongoDB"] = () => |
|
||||
{ |
|
||||
var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); |
|
||||
var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); |
|
||||
|
|
||||
services.AddMongoDBMembershipTable(options => |
|
||||
{ |
|
||||
options.ConnectionString = mongoConfiguration; |
|
||||
options.CollectionPrefix = "Orleans_"; |
|
||||
options.DatabaseName = mongoDatabaseName; |
|
||||
}); |
|
||||
|
|
||||
services.AddMongoDBReminders(options => |
|
||||
{ |
|
||||
options.ConnectionString = mongoConfiguration; |
|
||||
options.CollectionPrefix = "Orleans_"; |
|
||||
options.DatabaseName = mongoDatabaseName; |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,190 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Net; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.Extensions.Configuration; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Microsoft.Extensions.Hosting; |
|
||||
using Microsoft.Extensions.Logging; |
|
||||
using Orleans; |
|
||||
using Orleans.Configuration; |
|
||||
using Orleans.Hosting; |
|
||||
using Squidex.Domain.Apps.Entities.Contents; |
|
||||
using Squidex.Domain.Apps.Entities.Rules; |
|
||||
using Squidex.Domain.Apps.Entities.Rules.UsageTracking; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.EventSourcing.Grains; |
|
||||
using Squidex.Infrastructure.Log; |
|
||||
using Squidex.Infrastructure.Log.Adapter; |
|
||||
using Squidex.Infrastructure.Orleans; |
|
||||
|
|
||||
namespace Squidex.Config.Orleans |
|
||||
{ |
|
||||
public sealed class SiloWrapper : IHostedService |
|
||||
{ |
|
||||
private readonly Lazy<ISiloHost> lazySilo; |
|
||||
private readonly ISemanticLog log; |
|
||||
private readonly IApplicationLifetime lifetime; |
|
||||
private bool isStopping; |
|
||||
|
|
||||
internal sealed class Source : IConfigurationSource |
|
||||
{ |
|
||||
private readonly IConfigurationProvider configurationProvider; |
|
||||
|
|
||||
public Source(IConfigurationProvider configurationProvider) |
|
||||
{ |
|
||||
this.configurationProvider = configurationProvider; |
|
||||
} |
|
||||
|
|
||||
public IConfigurationProvider Build(IConfigurationBuilder builder) |
|
||||
{ |
|
||||
return configurationProvider; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public IClusterClient Client |
|
||||
{ |
|
||||
get { return lazySilo.Value.Services.GetRequiredService<IClusterClient>(); } |
|
||||
} |
|
||||
|
|
||||
public SiloWrapper(IConfiguration config, ISemanticLog log, IApplicationLifetime lifetime) |
|
||||
{ |
|
||||
this.lifetime = lifetime; |
|
||||
this.log = log; |
|
||||
|
|
||||
lazySilo = new Lazy<ISiloHost>(() => |
|
||||
{ |
|
||||
var hostBuilder = new SiloHostBuilder() |
|
||||
.UseDashboard(options => options.HostSelf = false) |
|
||||
.EnableDirectClient() |
|
||||
.AddIncomingGrainCallFilter<LocalCacheFilter>() |
|
||||
.AddStartupTask<InitializerStartup>() |
|
||||
.AddStartupTask<Bootstrap<IContentSchedulerGrain>>() |
|
||||
.AddStartupTask<Bootstrap<IEventConsumerManagerGrain>>() |
|
||||
.AddStartupTask<Bootstrap<IRuleDequeuerGrain>>() |
|
||||
.AddStartupTask<Bootstrap<IUsageTrackerGrain>>() |
|
||||
.Configure<ClusterOptions>(options => |
|
||||
{ |
|
||||
options.Configure(); |
|
||||
}) |
|
||||
.ConfigureApplicationParts(builder => |
|
||||
{ |
|
||||
builder.AddMyParts(); |
|
||||
}) |
|
||||
.ConfigureLogging((hostingContext, builder) => |
|
||||
{ |
|
||||
builder.AddConfiguration(hostingContext.Configuration.GetSection("logging")); |
|
||||
builder.AddSemanticLog(); |
|
||||
builder.AddFilter(); |
|
||||
}) |
|
||||
.ConfigureServices((context, services) => |
|
||||
{ |
|
||||
services.AddAppSiloServices(context.Configuration); |
|
||||
services.AddAppServices(context.Configuration); |
|
||||
|
|
||||
services.Configure<ProcessExitHandlingOptions>(options => options.FastKillOnProcessExit = false); |
|
||||
}) |
|
||||
.ConfigureAppConfiguration((hostContext, builder) => |
|
||||
{ |
|
||||
if (config is IConfigurationRoot root) |
|
||||
{ |
|
||||
foreach (var provider in root.Providers) |
|
||||
{ |
|
||||
builder.Add(new Source(provider)); |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
config.ConfigureByOption("orleans:clustering", new Options |
|
||||
{ |
|
||||
["MongoDB"] = () => |
|
||||
{ |
|
||||
hostBuilder.ConfigureEndpoints(Dns.GetHostName(), 11111, 40000, listenOnAnyHostAddress: true); |
|
||||
|
|
||||
var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); |
|
||||
var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); |
|
||||
|
|
||||
hostBuilder.UseMongoDBClustering(options => |
|
||||
{ |
|
||||
options.ConnectionString = mongoConfiguration; |
|
||||
options.CollectionPrefix = "Orleans_"; |
|
||||
options.DatabaseName = mongoDatabaseName; |
|
||||
}); |
|
||||
}, |
|
||||
["Development"] = () => |
|
||||
{ |
|
||||
hostBuilder.UseLocalhostClustering(gatewayPort: 40000, serviceId: Constants.OrleansClusterId, clusterId: Constants.OrleansClusterId); |
|
||||
hostBuilder.Configure<ClusterMembershipOptions>(options => options.ExpectedClusterSize = 1); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
config.ConfigureByOption("store:type", new Options |
|
||||
{ |
|
||||
["MongoDB"] = () => |
|
||||
{ |
|
||||
var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); |
|
||||
var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); |
|
||||
|
|
||||
hostBuilder.UseMongoDBReminders(options => |
|
||||
{ |
|
||||
options.ConnectionString = mongoConfiguration; |
|
||||
options.CollectionPrefix = "Orleans_"; |
|
||||
options.DatabaseName = mongoDatabaseName; |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var silo = hostBuilder.Build(); |
|
||||
|
|
||||
silo.Stopped.ContinueWith(x => |
|
||||
{ |
|
||||
if (!isStopping) |
|
||||
{ |
|
||||
lifetime.StopApplication(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return silo; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
public async Task StartAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
var watch = ValueStopwatch.StartNew(); |
|
||||
try |
|
||||
{ |
|
||||
await lazySilo.Value.StartAsync(cancellationToken); |
|
||||
} |
|
||||
catch |
|
||||
{ |
|
||||
lifetime.StopApplication(); |
|
||||
throw; |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
var elapsedMs = watch.Stop(); |
|
||||
|
|
||||
log.LogInformation(w => w |
|
||||
.WriteProperty("message", "Silo started") |
|
||||
.WriteProperty("elapsedMs", elapsedMs)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public async Task StopAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
if (lazySilo.IsValueCreated) |
|
||||
{ |
|
||||
isStopping = true; |
|
||||
|
|
||||
await lazySilo.Value.StopAsync(cancellationToken); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,38 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
|
||||
|
namespace Squidex.Config.Startup |
||||
|
{ |
||||
|
public sealed class InitializerHost : SafeHostedService |
||||
|
{ |
||||
|
private readonly IEnumerable<IInitializable> targets; |
||||
|
|
||||
|
public InitializerHost(IEnumerable<IInitializable> targets, IApplicationLifetime lifetime, ISemanticLog log) |
||||
|
: base(lifetime, log) |
||||
|
{ |
||||
|
this.targets = targets; |
||||
|
} |
||||
|
|
||||
|
protected override async Task StartAsync(ISemanticLog log, CancellationToken ct) |
||||
|
{ |
||||
|
foreach (var target in targets.Distinct()) |
||||
|
{ |
||||
|
await target.InitializeAsync(ct); |
||||
|
|
||||
|
log.LogInformation(w => w.WriteProperty("initializedSystem", target.GetType().Name)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Infrastructure.Migrations; |
||||
|
|
||||
|
namespace Squidex.Config.Startup |
||||
|
{ |
||||
|
public sealed class MigratorHost : SafeHostedService |
||||
|
{ |
||||
|
private readonly Migrator migrator; |
||||
|
|
||||
|
public MigratorHost(Migrator migrator, IApplicationLifetime lifetime, ISemanticLog log) |
||||
|
: base(lifetime, log) |
||||
|
{ |
||||
|
this.migrator = migrator; |
||||
|
} |
||||
|
|
||||
|
protected override Task StartAsync(ISemanticLog log, CancellationToken ct) |
||||
|
{ |
||||
|
return migrator.MigrateAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Config.Startup |
||||
|
{ |
||||
|
public abstract class SafeHostedService : IHostedService |
||||
|
{ |
||||
|
private readonly IApplicationLifetime lifetime; |
||||
|
private readonly ISemanticLog log; |
||||
|
private bool isStarted; |
||||
|
|
||||
|
protected SafeHostedService(IApplicationLifetime lifetime, ISemanticLog log) |
||||
|
{ |
||||
|
this.lifetime = lifetime; |
||||
|
|
||||
|
this.log = log; |
||||
|
} |
||||
|
|
||||
|
public async Task StartAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await StartAsync(log, cancellationToken); |
||||
|
|
||||
|
isStarted = true; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
lifetime.StopApplication(); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task StopAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
if (isStarted) |
||||
|
{ |
||||
|
await StopAsync(log, cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract Task StartAsync(ISemanticLog log, CancellationToken ct); |
||||
|
|
||||
|
protected virtual Task StopAsync(ISemanticLog log, CancellationToken ct) |
||||
|
{ |
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,5 +1,5 @@ |
|||||
<sqx-title message="{app} | API | GraphQL" parameter1="app" [value1]="appsState.appName"></sqx-title> |
<sqx-title message="{app} | API | GraphQL" parameter1="app" [value1]="appsState.appName"></sqx-title> |
||||
|
|
||||
<sqx-panel desiredWidth="*" isFullSize="true"> |
<sqx-panel desiredWidth="*" minWidth="50rem" isFullSize="true"> |
||||
<div inner #graphiQLContainer></div> |
<div inner #graphiQLContainer></div> |
||||
</sqx-panel> |
</sqx-panel> |
||||
@ -1,42 +1,83 @@ |
|||||
<div class="table-items-row" [class.invalid]="isInvalid | async"> |
<div class="row no-gutters" [class.compare]="fieldFormCompare"> |
||||
<div class="languages-buttons" *ngIf="field.isLocalizable && languages.length > 1"> |
<div [class.col-12]="!fieldFormCompare" [class.col-6]="fieldFormCompare"> |
||||
<button *ngIf="!field.properties.isComplexUI" type="button" class="btn btn-secondary btn-sm btn-text mr-1" (click)="toggleShowAll()"> |
<div class="table-items-row" [class.field-invalid]="isInvalid | async"> |
||||
{{showAllControls ? 'Single Language' : 'All Languages'}} |
<div class="languages-buttons"> |
||||
</button> |
<sqx-field-languages |
||||
|
[field]="field" |
||||
<ng-container *ngIf="field.properties.isComplexUI || !showAllControls"> |
[language]="language" |
||||
<sqx-language-selector size="sm" #buttonLanguages |
(languageChange)="languageChange.emit($event)" |
||||
[selectedLanguage]="language" |
[languages]="languages" |
||||
(selectedLanguageChange)="languageChange.emit($event)" |
[showAllControls]="showAllControls" |
||||
[languages]="languages.values"> |
(showAllControlsChange)="changeShowAllControls($event)"> |
||||
</sqx-language-selector> |
</sqx-field-languages> |
||||
|
</div> |
||||
<sqx-onboarding-tooltip helpId="languages" [for]="buttonLanguages" position="topRight" after="120000"> |
|
||||
Please remember to check all languages when you see validation errors. |
<ng-container *ngIf="showAllControls; else singleControl"> |
||||
</sqx-onboarding-tooltip> |
<div class="form-group" *ngFor="let language of languages; trackBy: trackByLanguage"> |
||||
</ng-container> |
<sqx-field-editor |
||||
|
[displaySuffix]="prefix(language)" |
||||
|
[form]="form" |
||||
|
[field]="field" |
||||
|
[language]="language" |
||||
|
[languages]="languages" |
||||
|
[isCompact]="!!fieldFormCompare" |
||||
|
[control]="fieldForm.controls[language.iso2Code]"> |
||||
|
</sqx-field-editor> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-template #singleControl> |
||||
|
<sqx-field-editor |
||||
|
[form]="form" |
||||
|
[field]="field" |
||||
|
[language]="language" |
||||
|
[languages]="languages" |
||||
|
[isCompact]="!!fieldFormCompare" |
||||
|
[control]="selectedFormControl"> |
||||
|
</sqx-field-editor> |
||||
|
</ng-template> |
||||
|
</div> |
||||
</div> |
</div> |
||||
|
|
||||
<ng-container *ngIf="showAllControls; else singleControl"> |
<div class="col-6 col-right" *ngIf="fieldFormCompare"> |
||||
<div class="form-group" *ngFor="let language of languages"> |
<button type="button" class="btn btn-primary btn-sm field-copy" (click)="copy()" *ngIf="isDifferent | async"> |
||||
<sqx-field-editor |
<i class="icon-arrow_back"></i> |
||||
[displaySuffix]="'(' + language.iso2Code + ')'" |
</button> |
||||
[form]="form" |
|
||||
[field]="field" |
|
||||
[language]="language" |
|
||||
[languages]="languages" |
|
||||
[control]="fieldForm.controls[language.iso2Code]"> |
|
||||
</sqx-field-editor> |
|
||||
</div> |
|
||||
</ng-container> |
|
||||
|
|
||||
<ng-template #singleControl> |
<div class="table-items-row"> |
||||
<sqx-field-editor |
<div class="languages-buttons"> |
||||
[form]="form" |
<sqx-field-languages |
||||
[field]="field" |
[field]="field" |
||||
[language]="language" |
[language]="language" |
||||
[languages]="languages" |
(languageChange)="languageChange.emit($event)" |
||||
[control]="selectedFormControl"> |
[languages]="languages" |
||||
</sqx-field-editor> |
[showAllControls]="showAllControls" |
||||
</ng-template> |
(showAllControlsChange)="changeShowAllControls($event)"> |
||||
|
</sqx-field-languages> |
||||
|
</div> |
||||
|
|
||||
|
<ng-container *ngIf="showAllControls; else singleControlCompare"> |
||||
|
<div class="form-group" *ngFor="let language of languages; trackBy: trackByLanguage"> |
||||
|
<sqx-field-editor |
||||
|
[displaySuffix]="prefix(language)" |
||||
|
[field]="field" |
||||
|
[language]="language" |
||||
|
[languages]="languages" |
||||
|
[isCompact]="true" |
||||
|
[control]="fieldFormCompare?.controls[language.iso2Code]"> |
||||
|
</sqx-field-editor> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-template #singleControlCompare> |
||||
|
<sqx-field-editor |
||||
|
[field]="field" |
||||
|
[language]="language" |
||||
|
[languages]="languages" |
||||
|
[isCompact]="true" |
||||
|
[control]="selectedFormControlCompare"> |
||||
|
</sqx-field-editor> |
||||
|
</ng-template> |
||||
|
</div> |
||||
|
</div> |
||||
</div> |
</div> |
||||
|
|||||
@ -0,0 +1,52 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; |
||||
|
|
||||
|
import { AppLanguageDto, RootFieldDto } from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-field-languages', |
||||
|
template: ` |
||||
|
<ng-container *ngIf="field.isLocalizable && languages.length > 1"> |
||||
|
<button *ngIf="!field.properties.isComplexUI" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="showAllControlsChange.emit(!showAllControls)"> |
||||
|
{{showAllControls ? 'Single Language' : 'All Languages'}} |
||||
|
</button> |
||||
|
|
||||
|
<ng-container *ngIf="field.properties.isComplexUI || !showAllControls"> |
||||
|
<sqx-language-selector size="sm" #buttonLanguages |
||||
|
[selectedLanguage]="language" |
||||
|
(selectedLanguageChange)="languageChange.emit($event)" |
||||
|
[languages]="languages"> |
||||
|
</sqx-language-selector> |
||||
|
|
||||
|
<sqx-onboarding-tooltip helpId="languages" [for]="buttonLanguages" position="topRight" after="120000"> |
||||
|
Please remember to check all languages when you see validation errors. |
||||
|
</sqx-onboarding-tooltip> |
||||
|
</ng-container> |
||||
|
</ng-container>`, |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FieldLanguagesComponent { |
||||
|
@Input() |
||||
|
public field: RootFieldDto; |
||||
|
|
||||
|
@Input() |
||||
|
public showAllControls: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
public language: AppLanguageDto; |
||||
|
|
||||
|
@Input() |
||||
|
public languages: AppLanguageDto[]; |
||||
|
|
||||
|
@Output() |
||||
|
public languageChange = new EventEmitter<AppLanguageDto>(); |
||||
|
|
||||
|
@Output() |
||||
|
public showAllControlsChange = new EventEmitter<AppLanguageDto>(); |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
||||
|
import { FormGroup } from '@angular/forms'; |
||||
|
|
||||
|
import { FieldDto } from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-content-item-editor', |
||||
|
template: ` |
||||
|
<div [formGroup]="form"> |
||||
|
<div [ngSwitch]="field.properties.fieldType"> |
||||
|
<div *ngSwitchCase="'Number'"> |
||||
|
<div [ngSwitch]="field.properties['editor']"> |
||||
|
<div *ngSwitchCase="'Input'"> |
||||
|
<input class="form-control" type="number" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" /> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Dropdown'"> |
||||
|
<select class="form-control" [formControlName]="field.name"> |
||||
|
<option [ngValue]="null"></option> |
||||
|
<option *ngFor="let value of field.properties['allowedValues']" [ngValue]="value">{{value}}</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'String'"> |
||||
|
<div [ngSwitch]="field.properties['editor']"> |
||||
|
<div *ngSwitchCase="'Input'"> |
||||
|
<input class="form-control" type="text" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" /> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Slug'"> |
||||
|
<input class="form-control" type="text" [formControlName]="field.name" [placeholder]="field.displayPlaceholder" sqxTransformInput="Slugify" /> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Dropdown'"> |
||||
|
<select class="form-control" [formControlName]="field.name"> |
||||
|
<option [ngValue]="null"></option> |
||||
|
<option *ngFor="let value of field.properties['allowedValues']" [ngValue]="value">{{value}}</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Boolean'"> |
||||
|
<div [ngSwitch]="field.properties['editor']"> |
||||
|
<div *ngSwitchCase="'Toggle'"> |
||||
|
<sqx-toggle [formControlName]="field.name" [threeStates]="!field.properties.isRequired"></sqx-toggle> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Checkbox'"> |
||||
|
<div class="form-check form-check-inline"> |
||||
|
<input class="form-check-input" type="checkbox" [formControlName]="field.name" sqxIndeterminateValue /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class ContentItemEditorComponent { |
||||
|
@Input() |
||||
|
public field: FieldDto; |
||||
|
|
||||
|
@Input() |
||||
|
public form: FormGroup; |
||||
|
} |
||||
@ -1,16 +1,16 @@ |
|||||
<ng-container *ngIf="selectedName"> |
<ng-container *ngIf="snapshot.selectedName"> |
||||
<span>Preview: </span> |
<span>Preview: </span> |
||||
|
|
||||
<div class="btn-group ml-1" #buttonGroup> |
<div class="btn-group ml-1" #buttonGroup> |
||||
<button type="button" class="btn btn-secondary" (click)="follow(selectedName)"> |
<button type="button" class="btn btn-secondary" (click)="follow(snapshot.selectedName)"> |
||||
<i class="icon-external-link"></i> {{selectedName}} |
<i class="icon-external-link"></i> {{snapshot.selectedName}} |
||||
</button> |
</button> |
||||
|
|
||||
<div class="btn-group" *ngIf="alternativeNames.length > 0"> |
<div class="btn-group" *ngIf="snapshot.alternativeNames.length > 0"> |
||||
<button type="button" class="btn btn-secondary dropdown-toggle" (click)="dropdown.toggle()"></button> |
<button type="button" class="btn btn-secondary dropdown-toggle" (click)="dropdown.toggle()"></button> |
||||
|
|
||||
<div class="dropdown-menu" *sqxModalView="dropdown;closeAlways:true" [sqxModalTarget]="buttonGroup" @fade> |
<div class="dropdown-menu" *sqxModalView="dropdown;closeAlways:true" [sqxModalTarget]="buttonGroup" @fade> |
||||
<a *ngFor="let name of alternativeNames" class="dropdown-item" (click)="follow(name)">{{name}}</a> |
<a *ngFor="let name of snapshot.alternativeNames" class="dropdown-item" (click)="follow(name)">{{name}}</a> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
|
|||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue