mirror of https://github.com/Squidex/squidex.git
Browse Source
# Conflicts: # src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs # src/Squidex/app/features/content/shared/content-item.component.html # src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html # src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss # src/Squidex/app/shared/components/saved-queries.component.ts # src/Squidex/app/shared/state/queries.spec.ts # src/Squidex/app/shared/state/ui.state.tspull/419/head
235 changed files with 4681 additions and 3042 deletions
@ -1,72 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using Orleans; |
|
||||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Commands; |
|
||||
using Squidex.Infrastructure.Orleans; |
|
||||
using Squidex.Infrastructure.Validation; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|
||||
{ |
|
||||
public sealed class AppsByNameIndexCommandMiddleware : ICommandMiddleware |
|
||||
{ |
|
||||
private readonly IAppsByNameIndex index; |
|
||||
|
|
||||
public AppsByNameIndexCommandMiddleware(IGrainFactory grainFactory) |
|
||||
{ |
|
||||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|
||||
|
|
||||
index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id); |
|
||||
} |
|
||||
|
|
||||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|
||||
{ |
|
||||
var createApp = context.Command as CreateApp; |
|
||||
|
|
||||
var isReserved = false; |
|
||||
try |
|
||||
{ |
|
||||
if (createApp != null) |
|
||||
{ |
|
||||
isReserved = await index.ReserveAppAsync(createApp.AppId, createApp.Name); |
|
||||
|
|
||||
if (!isReserved) |
|
||||
{ |
|
||||
var error = new ValidationError("An app with the same name already exists.", nameof(createApp.Name)); |
|
||||
|
|
||||
throw new ValidationException("Cannot create app.", error); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
await next(); |
|
||||
|
|
||||
if (context.IsCompleted) |
|
||||
{ |
|
||||
if (createApp != null) |
|
||||
{ |
|
||||
await index.AddAppAsync(createApp.AppId, createApp.Name); |
|
||||
} |
|
||||
else if (context.Command is ArchiveApp archiveApp) |
|
||||
{ |
|
||||
await index.RemoveAppAsync(archiveApp.AppId); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
if (isReserved) |
|
||||
{ |
|
||||
await index.RemoveReservationAsync(createApp.AppId, createApp.Name); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,80 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using Orleans; |
|
||||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Commands; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|
||||
{ |
|
||||
public sealed class AppsByUserIndexCommandMiddleware : ICommandMiddleware |
|
||||
{ |
|
||||
private readonly IGrainFactory grainFactory; |
|
||||
|
|
||||
public AppsByUserIndexCommandMiddleware(IGrainFactory grainFactory) |
|
||||
{ |
|
||||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|
||||
|
|
||||
this.grainFactory = grainFactory; |
|
||||
} |
|
||||
|
|
||||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|
||||
{ |
|
||||
if (context.IsCompleted) |
|
||||
{ |
|
||||
switch (context.Command) |
|
||||
{ |
|
||||
case CreateApp createApp: |
|
||||
await Index(GetUserId(createApp)).AddAppAsync(createApp.AppId); |
|
||||
break; |
|
||||
case AssignContributor assignContributor: |
|
||||
await Index(GetUserId(assignContributor)).AddAppAsync(assignContributor.AppId); |
|
||||
break; |
|
||||
case RemoveContributor removeContributor: |
|
||||
await Index(GetUserId(removeContributor)).RemoveAppAsync(removeContributor.AppId); |
|
||||
break; |
|
||||
case ArchiveApp archiveApp: |
|
||||
{ |
|
||||
var appState = await grainFactory.GetGrain<IAppGrain>(archiveApp.AppId).GetStateAsync(); |
|
||||
|
|
||||
foreach (var contributorId in appState.Value.Contributors.Keys) |
|
||||
{ |
|
||||
await Index(contributorId).RemoveAppAsync(archiveApp.AppId); |
|
||||
} |
|
||||
|
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
await next(); |
|
||||
} |
|
||||
|
|
||||
private static string GetUserId(CreateApp createApp) |
|
||||
{ |
|
||||
return createApp.Actor.Identifier; |
|
||||
} |
|
||||
|
|
||||
private static string GetUserId(AssignContributor assignContributor) |
|
||||
{ |
|
||||
return assignContributor.ContributorId; |
|
||||
} |
|
||||
|
|
||||
private static string GetUserId(RemoveContributor removeContributor) |
|
||||
{ |
|
||||
return removeContributor.ContributorId; |
|
||||
} |
|
||||
|
|
||||
private IAppsByUserIndex Index(string id) |
|
||||
{ |
|
||||
return grainFactory.GetGrain<IAppsByUserIndex>(id); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,286 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
using Squidex.Infrastructure.Validation; |
||||
|
using Squidex.Shared; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
||||
|
{ |
||||
|
public sealed class AppsIndex : IAppsIndex, ICommandMiddleware |
||||
|
{ |
||||
|
private readonly IGrainFactory grainFactory; |
||||
|
|
||||
|
public AppsIndex(IGrainFactory grainFactory) |
||||
|
{ |
||||
|
Guard.NotNull(grainFactory, nameof(grainFactory)); |
||||
|
|
||||
|
this.grainFactory = grainFactory; |
||||
|
} |
||||
|
|
||||
|
public async Task RebuildByContributorsAsync(Guid appId, HashSet<string> contributors) |
||||
|
{ |
||||
|
foreach (var contributorId in contributors) |
||||
|
{ |
||||
|
await Index(contributorId).AddAsync(appId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task RebuildByContributorsAsync(string contributorId, HashSet<Guid> apps) |
||||
|
{ |
||||
|
return Index(contributorId).RebuildAsync(apps); |
||||
|
} |
||||
|
|
||||
|
public Task RebuildAsync(Dictionary<string, Guid> appsByName) |
||||
|
{ |
||||
|
return Index().RebuildAsync(appsByName); |
||||
|
} |
||||
|
|
||||
|
public Task RemoveReservationAsync(string token) |
||||
|
{ |
||||
|
return Index().RemoveReservationAsync(token); |
||||
|
} |
||||
|
|
||||
|
public Task<List<Guid>> GetIdsAsync() |
||||
|
{ |
||||
|
return Index().GetIdsAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task<bool> AddAsync(string token) |
||||
|
{ |
||||
|
return Index().AddAsync(token); |
||||
|
} |
||||
|
|
||||
|
public Task<string> ReserveAsync(Guid id, string name) |
||||
|
{ |
||||
|
return Index().ReserveAsync(id, name); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<IAppEntity>> GetAppsAsync() |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<AppsIndex>()) |
||||
|
{ |
||||
|
var ids = await GetAppIdsAsync(); |
||||
|
|
||||
|
var apps = |
||||
|
await Task.WhenAll(ids |
||||
|
.Select(id => GetAppAsync(id))); |
||||
|
|
||||
|
return apps.Where(x => x != null).ToList(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<List<IAppEntity>> GetAppsForUserAsync(string userId, PermissionSet permissions) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<AppsIndex>()) |
||||
|
{ |
||||
|
var ids = |
||||
|
await Task.WhenAll( |
||||
|
GetAppIdsByUserAsync(userId), |
||||
|
GetAppIdsAsync(permissions.ToAppNames())); |
||||
|
|
||||
|
var apps = |
||||
|
await Task.WhenAll(ids |
||||
|
.SelectMany(x => x) |
||||
|
.Select(id => GetAppAsync(id))); |
||||
|
|
||||
|
return apps.Where(x => x != null).ToList(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<IAppEntity> GetAppAsync(string name) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<AppsIndex>()) |
||||
|
{ |
||||
|
var appId = await GetAppIdAsync(name); |
||||
|
|
||||
|
if (appId == default) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return await GetAppAsync(appId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<IAppEntity> GetAppAsync(Guid appId) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<AppsIndex>()) |
||||
|
{ |
||||
|
var app = await grainFactory.GetGrain<IAppGrain>(appId).GetStateAsync(); |
||||
|
|
||||
|
if (IsFound(app.Value)) |
||||
|
{ |
||||
|
return app.Value; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<List<Guid>> GetAppIdsByUserAsync(string userId) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<AppProvider>()) |
||||
|
{ |
||||
|
return await grainFactory.GetGrain<IAppsByUserIndexGrain>(userId).GetIdsAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<List<Guid>> GetAppIdsAsync() |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<AppProvider>()) |
||||
|
{ |
||||
|
return await grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id).GetIdsAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<List<Guid>> GetAppIdsAsync(string[] names) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<AppProvider>()) |
||||
|
{ |
||||
|
return await grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id).GetIdsAsync(names); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<Guid> GetAppIdAsync(string name) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<AppProvider>()) |
||||
|
{ |
||||
|
return await grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id).GetIdAsync(name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(CommandContext context, Func<Task> next) |
||||
|
{ |
||||
|
if (context.Command is CreateApp createApp) |
||||
|
{ |
||||
|
var index = Index(); |
||||
|
|
||||
|
string token = await CheckAppAsync(index, createApp); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await next(); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
if (token != null) |
||||
|
{ |
||||
|
if (context.IsCompleted) |
||||
|
{ |
||||
|
await index.AddAsync(token); |
||||
|
|
||||
|
if (createApp.Actor.IsSubject) |
||||
|
{ |
||||
|
await Index(createApp.Actor.Identifier).AddAsync(createApp.AppId); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await index.RemoveReservationAsync(token); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await next(); |
||||
|
|
||||
|
if (context.IsCompleted) |
||||
|
{ |
||||
|
switch (context.Command) |
||||
|
{ |
||||
|
case AssignContributor assignContributor: |
||||
|
await AssignContributorAsync(assignContributor); |
||||
|
break; |
||||
|
|
||||
|
case RemoveContributor removeContributor: |
||||
|
await RemoveContributorAsync(removeContributor); |
||||
|
break; |
||||
|
|
||||
|
case ArchiveApp archiveApp: |
||||
|
await ArchiveAppAsync(archiveApp); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<string> CheckAppAsync(IAppsByNameIndexGrain index, CreateApp command) |
||||
|
{ |
||||
|
var name = command.Name; |
||||
|
|
||||
|
if (name.IsSlug()) |
||||
|
{ |
||||
|
var token = await index.ReserveAsync(command.AppId, name); |
||||
|
|
||||
|
if (token == null) |
||||
|
{ |
||||
|
var error = new ValidationError("An app with this already exists."); |
||||
|
|
||||
|
throw new ValidationException("Cannot create app.", error); |
||||
|
} |
||||
|
|
||||
|
return token; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private Task AssignContributorAsync(AssignContributor command) |
||||
|
{ |
||||
|
return Index(command.ContributorId).AddAsync(command.AppId); |
||||
|
} |
||||
|
|
||||
|
private Task RemoveContributorAsync(RemoveContributor command) |
||||
|
{ |
||||
|
return Index(command.ContributorId).RemoveAsync(command.AppId); |
||||
|
} |
||||
|
|
||||
|
private async Task ArchiveAppAsync(ArchiveApp command) |
||||
|
{ |
||||
|
var appId = command.AppId; |
||||
|
|
||||
|
var app = await grainFactory.GetGrain<IAppGrain>(appId).GetStateAsync(); |
||||
|
|
||||
|
if (IsFound(app.Value)) |
||||
|
{ |
||||
|
await Index().RemoveAsync(appId); |
||||
|
} |
||||
|
|
||||
|
foreach (var contributorId in app.Value.Contributors.Keys) |
||||
|
{ |
||||
|
await Index(contributorId).RemoveAsync(appId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool IsFound(IAppEntity app) |
||||
|
{ |
||||
|
return app.Version > EtagVersion.Empty && !app.IsArchived; |
||||
|
} |
||||
|
|
||||
|
private IAppsByNameIndexGrain Index() |
||||
|
{ |
||||
|
return grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id); |
||||
|
} |
||||
|
|
||||
|
private IAppsByUserIndexGrain Index(string id) |
||||
|
{ |
||||
|
return grainFactory.GetGrain<IAppsByUserIndexGrain>(id); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,35 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
|
||||
using Orleans; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|
||||
{ |
|
||||
public interface IAppsByNameIndex : IGrainWithStringKey |
|
||||
{ |
|
||||
Task<long> CountAsync(); |
|
||||
|
|
||||
Task<bool> ReserveAppAsync(Guid appId, string name); |
|
||||
|
|
||||
Task AddAppAsync(Guid appId, string name); |
|
||||
|
|
||||
Task RemoveAppAsync(Guid appId); |
|
||||
|
|
||||
Task RebuildAsync(Dictionary<string, Guid> apps); |
|
||||
|
|
||||
Task RemoveReservationAsync(Guid appId, string name); |
|
||||
|
|
||||
Task<List<Guid>> GetAppIdsAsync(); |
|
||||
|
|
||||
Task<List<Guid>> GetAppIdsAsync(string[] names); |
|
||||
|
|
||||
Task<Guid> GetAppIdAsync(string name); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Orleans; |
||||
|
using Squidex.Infrastructure.Orleans.Indexes; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
||||
|
{ |
||||
|
public interface IAppsByUserIndexGrain : IIdsIndexGrain<Guid>, IGrainWithStringKey |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
||||
|
{ |
||||
|
public interface IAppsIndex |
||||
|
{ |
||||
|
Task<List<Guid>> GetIdsAsync(); |
||||
|
|
||||
|
Task<List<IAppEntity>> GetAppsAsync(); |
||||
|
|
||||
|
Task<List<IAppEntity>> GetAppsForUserAsync(string userId, PermissionSet permissions); |
||||
|
|
||||
|
Task<IAppEntity> GetAppAsync(string name); |
||||
|
|
||||
|
Task<IAppEntity> GetAppAsync(Guid appId); |
||||
|
|
||||
|
Task<string> ReserveAsync(Guid id, string name); |
||||
|
|
||||
|
Task<bool> AddAsync(string token); |
||||
|
|
||||
|
Task RemoveReservationAsync(string token); |
||||
|
|
||||
|
Task RebuildByContributorsAsync(string contributorId, HashSet<Guid> apps); |
||||
|
|
||||
|
Task RebuildAsync(Dictionary<string, Guid> apps); |
||||
|
|
||||
|
Task RebuildByContributorsAsync(Guid appId, HashSet<string> contributors); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public interface IAssetLoader |
||||
|
{ |
||||
|
Task<IAssetEntity> GetAsync(Guid id, long version = EtagVersion.Any); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Queries |
||||
|
{ |
||||
|
public sealed class AssetLoader : IAssetLoader |
||||
|
{ |
||||
|
private readonly IGrainFactory grainFactory; |
||||
|
|
||||
|
public AssetLoader(IGrainFactory grainFactory) |
||||
|
{ |
||||
|
Guard.NotNull(grainFactory, nameof(grainFactory)); |
||||
|
|
||||
|
this.grainFactory = grainFactory; |
||||
|
} |
||||
|
|
||||
|
public async Task<IAssetEntity> GetAsync(Guid id, long version) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<AssetLoader>()) |
||||
|
{ |
||||
|
var grain = grainFactory.GetGrain<IAssetGrain>(id); |
||||
|
|
||||
|
var content = await grain.GetStateAsync(version); |
||||
|
|
||||
|
if (content.Value == null || content.Value.Version != version) |
||||
|
{ |
||||
|
throw new DomainObjectNotFoundException(id.ToString(), typeof(IAssetEntity)); |
||||
|
} |
||||
|
|
||||
|
return content.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Comments |
||||
|
{ |
||||
|
public sealed class CommentsLoader : ICommentsLoader |
||||
|
{ |
||||
|
private readonly IGrainFactory grainFactory; |
||||
|
|
||||
|
public CommentsLoader(IGrainFactory grainFactory) |
||||
|
{ |
||||
|
this.grainFactory = grainFactory; |
||||
|
} |
||||
|
|
||||
|
public Task<CommentsResult> GetCommentsAsync(Guid id, long version = EtagVersion.Any) |
||||
|
{ |
||||
|
var grain = grainFactory.GetGrain<ICommentsGrain>(id); |
||||
|
|
||||
|
return grain.GetCommentsAsync(version); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Comments |
||||
|
{ |
||||
|
public interface ICommentsLoader |
||||
|
{ |
||||
|
Task<CommentsResult> GetCommentsAsync(Guid id, long version = EtagVersion.Any); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Orleans; |
||||
|
using Squidex.Infrastructure.Orleans.Indexes; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
||||
|
{ |
||||
|
public interface IRulesByAppIndexGrain : IIdsIndexGrain<Guid>, IGrainWithGuidKey |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -1,56 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using Orleans; |
|
||||
using Squidex.Domain.Apps.Entities.Rules.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Commands; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|
||||
{ |
|
||||
public sealed class RulesByAppIndexCommandMiddleware : ICommandMiddleware |
|
||||
{ |
|
||||
private readonly IGrainFactory grainFactory; |
|
||||
|
|
||||
public RulesByAppIndexCommandMiddleware(IGrainFactory grainFactory) |
|
||||
{ |
|
||||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|
||||
|
|
||||
this.grainFactory = grainFactory; |
|
||||
} |
|
||||
|
|
||||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|
||||
{ |
|
||||
if (context.IsCompleted) |
|
||||
{ |
|
||||
switch (context.Command) |
|
||||
{ |
|
||||
case CreateRule createRule: |
|
||||
await Index(createRule.AppId.Id).AddRuleAsync(createRule.RuleId); |
|
||||
break; |
|
||||
case DeleteRule deleteRule: |
|
||||
{ |
|
||||
var schema = await grainFactory.GetGrain<IRuleGrain>(deleteRule.RuleId).GetStateAsync(); |
|
||||
|
|
||||
await Index(schema.Value.AppId.Id).RemoveRuleAsync(deleteRule.RuleId); |
|
||||
|
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
await next(); |
|
||||
} |
|
||||
|
|
||||
private IRulesByAppIndex Index(Guid appId) |
|
||||
{ |
|
||||
return grainFactory.GetGrain<IRulesByAppIndex>(appId); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,118 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
||||
|
{ |
||||
|
public sealed class RulesIndex : ICommandMiddleware, IRulesIndex |
||||
|
{ |
||||
|
private readonly IGrainFactory grainFactory; |
||||
|
|
||||
|
public RulesIndex(IGrainFactory grainFactory) |
||||
|
{ |
||||
|
Guard.NotNull(grainFactory, nameof(grainFactory)); |
||||
|
|
||||
|
this.grainFactory = grainFactory; |
||||
|
} |
||||
|
|
||||
|
public Task RebuildAsync(Guid appId, HashSet<Guid> rues) |
||||
|
{ |
||||
|
return Index(appId).RebuildAsync(rues); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<IRuleEntity>> GetRulesAsync(Guid appId) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<RulesIndex>()) |
||||
|
{ |
||||
|
var ids = await GetRuleIdsAsync(appId); |
||||
|
|
||||
|
var rules = |
||||
|
await Task.WhenAll( |
||||
|
ids.Select(GetRuleAsync)); |
||||
|
|
||||
|
return rules.Where(x => x != null).ToList(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<IRuleEntity> GetRuleAsync(Guid id) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<RulesIndex>()) |
||||
|
{ |
||||
|
var ruleEntity = await grainFactory.GetGrain<IRuleGrain>(id).GetStateAsync(); |
||||
|
|
||||
|
if (IsFound(ruleEntity.Value)) |
||||
|
{ |
||||
|
return ruleEntity.Value; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<List<Guid>> GetRuleIdsAsync(Guid appId) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<RulesIndex>()) |
||||
|
{ |
||||
|
return await Index(appId).GetIdsAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(CommandContext context, Func<Task> next) |
||||
|
{ |
||||
|
await next(); |
||||
|
|
||||
|
if (context.IsCompleted) |
||||
|
{ |
||||
|
switch (context.Command) |
||||
|
{ |
||||
|
case CreateRule createRule: |
||||
|
await CreateRuleAsync(createRule); |
||||
|
break; |
||||
|
case DeleteRule deleteRule: |
||||
|
await DeleteRuleAsync(deleteRule); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task CreateRuleAsync(CreateRule command) |
||||
|
{ |
||||
|
await Index(command.AppId.Id).AddAsync(command.RuleId); |
||||
|
} |
||||
|
|
||||
|
private async Task DeleteRuleAsync(DeleteRule command) |
||||
|
{ |
||||
|
var id = command.RuleId; |
||||
|
|
||||
|
var rule = await grainFactory.GetGrain<IRuleGrain>(id).GetStateAsync(); |
||||
|
|
||||
|
if (IsFound(rule.Value)) |
||||
|
{ |
||||
|
await Index(rule.Value.AppId.Id).RemoveAsync(id); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private IRulesByAppIndexGrain Index(Guid appId) |
||||
|
{ |
||||
|
return grainFactory.GetGrain<IRulesByAppIndexGrain>(appId); |
||||
|
} |
||||
|
|
||||
|
private static bool IsFound(IRuleEntity rule) |
||||
|
{ |
||||
|
return rule.Version > EtagVersion.Empty && !rule.IsDeleted; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Orleans; |
||||
|
using Squidex.Infrastructure.Orleans.Indexes; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
||||
|
{ |
||||
|
public interface ISchemasByAppIndexGrain : IUniqueNameIndexGrain<Guid>, IGrainWithGuidKey |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -1,56 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using Orleans; |
|
||||
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Commands; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|
||||
{ |
|
||||
public sealed class SchemasByAppIndexCommandMiddleware : ICommandMiddleware |
|
||||
{ |
|
||||
private readonly IGrainFactory grainFactory; |
|
||||
|
|
||||
public SchemasByAppIndexCommandMiddleware(IGrainFactory grainFactory) |
|
||||
{ |
|
||||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|
||||
|
|
||||
this.grainFactory = grainFactory; |
|
||||
} |
|
||||
|
|
||||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|
||||
{ |
|
||||
if (context.IsCompleted) |
|
||||
{ |
|
||||
switch (context.Command) |
|
||||
{ |
|
||||
case CreateSchema createSchema: |
|
||||
await Index(createSchema.AppId.Id).AddSchemaAsync(createSchema.SchemaId, createSchema.Name); |
|
||||
break; |
|
||||
case DeleteSchema deleteSchema: |
|
||||
{ |
|
||||
var schema = await grainFactory.GetGrain<ISchemaGrain>(deleteSchema.SchemaId).GetStateAsync(); |
|
||||
|
|
||||
await Index(schema.Value.AppId.Id).RemoveSchemaAsync(deleteSchema.SchemaId); |
|
||||
|
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
await next(); |
|
||||
} |
|
||||
|
|
||||
private ISchemasByAppIndex Index(Guid appId) |
|
||||
{ |
|
||||
return grainFactory.GetGrain<ISchemasByAppIndex>(appId); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,181 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Infrastructure.Validation; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
||||
|
{ |
||||
|
public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex |
||||
|
{ |
||||
|
private readonly IGrainFactory grainFactory; |
||||
|
|
||||
|
public SchemasIndex(IGrainFactory grainFactory) |
||||
|
{ |
||||
|
Guard.NotNull(grainFactory, nameof(grainFactory)); |
||||
|
|
||||
|
this.grainFactory = grainFactory; |
||||
|
} |
||||
|
|
||||
|
public Task RebuildAsync(Guid appId, Dictionary<string, Guid> schemas) |
||||
|
{ |
||||
|
return Index(appId).RebuildAsync(schemas); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId, bool allowDeleted = false) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<SchemasIndex>()) |
||||
|
{ |
||||
|
var ids = await GetSchemaIdsAsync(appId); |
||||
|
|
||||
|
var schemas = |
||||
|
await Task.WhenAll( |
||||
|
ids.Select(id => GetSchemaAsync(appId, id, allowDeleted))); |
||||
|
|
||||
|
return schemas.Where(x => x != null).ToList(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name, bool allowDeleted = false) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<SchemasIndex>()) |
||||
|
{ |
||||
|
var id = await GetSchemaIdAsync(appId, name); |
||||
|
|
||||
|
if (id == default) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return await GetSchemaAsync(appId, id, allowDeleted); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<SchemasIndex>()) |
||||
|
{ |
||||
|
var schema = await grainFactory.GetGrain<ISchemaGrain>(id).GetStateAsync(); |
||||
|
|
||||
|
if (IsFound(schema.Value, allowDeleted)) |
||||
|
{ |
||||
|
return schema.Value; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<Guid> GetSchemaIdAsync(Guid appId, string name) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<SchemasIndex>()) |
||||
|
{ |
||||
|
return await Index(appId).GetIdAsync(name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<List<Guid>> GetSchemaIdsAsync(Guid appId) |
||||
|
{ |
||||
|
using (Profiler.TraceMethod<SchemasIndex>()) |
||||
|
{ |
||||
|
return await Index(appId).GetIdsAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(CommandContext context, Func<Task> next) |
||||
|
{ |
||||
|
if (context.Command is CreateSchema createSchema) |
||||
|
{ |
||||
|
var index = Index(createSchema.AppId.Id); |
||||
|
|
||||
|
string token = await CheckSchemaAsync(index, createSchema); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await next(); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
if (token != null) |
||||
|
{ |
||||
|
if (context.IsCompleted) |
||||
|
{ |
||||
|
await index.AddAsync(token); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await index.RemoveReservationAsync(token); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await next(); |
||||
|
|
||||
|
if (context.IsCompleted) |
||||
|
{ |
||||
|
if (context.Command is DeleteSchema deleteSchema) |
||||
|
{ |
||||
|
await DeleteSchemaAsync(deleteSchema); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<string> CheckSchemaAsync(ISchemasByAppIndexGrain index, CreateSchema command) |
||||
|
{ |
||||
|
var name = command.Name; |
||||
|
|
||||
|
if (name.IsSlug()) |
||||
|
{ |
||||
|
var token = await index.ReserveAsync(command.SchemaId, name); |
||||
|
|
||||
|
if (token == null) |
||||
|
{ |
||||
|
var error = new ValidationError("A schema with this name already exists."); |
||||
|
|
||||
|
throw new ValidationException("Cannot create schema.", error); |
||||
|
} |
||||
|
|
||||
|
return token; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private async Task DeleteSchemaAsync(DeleteSchema commmand) |
||||
|
{ |
||||
|
var schemaId = commmand.SchemaId; |
||||
|
|
||||
|
var schema = await grainFactory.GetGrain<ISchemaGrain>(schemaId).GetStateAsync(); |
||||
|
|
||||
|
if (IsFound(schema.Value, true)) |
||||
|
{ |
||||
|
await Index(schema.Value.AppId.Id).RemoveAsync(schemaId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private ISchemasByAppIndexGrain Index(Guid appId) |
||||
|
{ |
||||
|
return grainFactory.GetGrain<ISchemasByAppIndexGrain>(appId); |
||||
|
} |
||||
|
|
||||
|
private static bool IsFound(ISchemaEntity entity, bool allowDeleted) |
||||
|
{ |
||||
|
return entity.Version > EtagVersion.Empty && (!entity.IsDeleted || allowDeleted); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Orleans.Indexes |
||||
|
{ |
||||
|
public interface IIdsIndexGrain<T> |
||||
|
{ |
||||
|
Task<long> CountAsync(); |
||||
|
|
||||
|
Task RebuildAsync(HashSet<T> ids); |
||||
|
|
||||
|
Task AddAsync(T id); |
||||
|
|
||||
|
Task RemoveAsync(T id); |
||||
|
|
||||
|
Task ClearAsync(); |
||||
|
|
||||
|
Task<List<T>> GetIdsAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Orleans.Indexes |
||||
|
{ |
||||
|
public interface IUniqueNameIndexGrain<T> |
||||
|
{ |
||||
|
Task<string> ReserveAsync(T id, string name); |
||||
|
|
||||
|
Task<bool> AddAsync(string token); |
||||
|
|
||||
|
Task<long> CountAsync(); |
||||
|
|
||||
|
Task RemoveReservationAsync(string token); |
||||
|
|
||||
|
Task RemoveAsync(T id); |
||||
|
|
||||
|
Task RebuildAsync(Dictionary<string, T> values); |
||||
|
|
||||
|
Task ClearAsync(); |
||||
|
|
||||
|
Task<T> GetIdAsync(string name); |
||||
|
|
||||
|
Task<List<T>> GetIdsAsync(string[] names); |
||||
|
|
||||
|
Task<List<T>> GetIdsAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Tasks; |
||||
|
using Orleans; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Orleans.Indexes |
||||
|
{ |
||||
|
public class IdsIndexGrain<TState, T> : Grain, IIdsIndexGrain<T> where TState : IdsIndexState<T>, new() |
||||
|
{ |
||||
|
private readonly IGrainState<TState> state; |
||||
|
|
||||
|
public IdsIndexGrain(IGrainState<TState> state) |
||||
|
{ |
||||
|
Guard.NotNull(state, nameof(state)); |
||||
|
|
||||
|
this.state = state; |
||||
|
} |
||||
|
|
||||
|
public Task<long> CountAsync() |
||||
|
{ |
||||
|
return Task.FromResult<long>(state.Value.Ids.Count); |
||||
|
} |
||||
|
|
||||
|
public Task RebuildAsync(HashSet<T> ids) |
||||
|
{ |
||||
|
state.Value = new TState { Ids = ids }; |
||||
|
|
||||
|
return state.WriteAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task AddAsync(T id) |
||||
|
{ |
||||
|
state.Value.Ids.Add(id); |
||||
|
|
||||
|
return state.WriteAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task RemoveAsync(T id) |
||||
|
{ |
||||
|
state.Value.Ids.Remove(id); |
||||
|
|
||||
|
return state.WriteAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync() |
||||
|
{ |
||||
|
return state.ClearAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task<List<T>> GetIdsAsync() |
||||
|
{ |
||||
|
return Task.FromResult(state.Value.Ids.ToList()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Orleans.Indexes |
||||
|
{ |
||||
|
public class IdsIndexState<T> |
||||
|
{ |
||||
|
public HashSet<T> Ids { get; set; } = new HashSet<T>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,136 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Tasks; |
||||
|
using Orleans; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Orleans.Indexes |
||||
|
{ |
||||
|
public class UniqueNameIndexGrain<TState, T> : Grain, IUniqueNameIndexGrain<T> where TState : UniqueNameIndexState<T>, new() |
||||
|
{ |
||||
|
private readonly Dictionary<string, (string Name, T Id)> reservations = new Dictionary<string, (string Name, T Id)>(); |
||||
|
private readonly IGrainState<TState> state; |
||||
|
|
||||
|
public UniqueNameIndexGrain(IGrainState<TState> state) |
||||
|
{ |
||||
|
Guard.NotNull(state, nameof(state)); |
||||
|
|
||||
|
this.state = state; |
||||
|
} |
||||
|
|
||||
|
public Task<long> CountAsync() |
||||
|
{ |
||||
|
return Task.FromResult<long>(state.Value.Names.Count); |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync() |
||||
|
{ |
||||
|
reservations.Clear(); |
||||
|
|
||||
|
return state.ClearAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task RebuildAsync(Dictionary<string, T> names) |
||||
|
{ |
||||
|
state.Value = new TState { Names = names }; |
||||
|
|
||||
|
return state.WriteAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task<string> ReserveAsync(T id, string name) |
||||
|
{ |
||||
|
string token = default; |
||||
|
|
||||
|
if (!IsInUse(name) && !IsReserved(name)) |
||||
|
{ |
||||
|
token = RandomHash.Simple(); |
||||
|
|
||||
|
reservations.Add(token, (name, id)); |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult(token); |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> AddAsync(string token) |
||||
|
{ |
||||
|
if (reservations.TryGetValue(token ?? string.Empty, out var reservation)) |
||||
|
{ |
||||
|
state.Value.Names.Add(reservation.Name, reservation.Id); |
||||
|
|
||||
|
await state.WriteAsync(); |
||||
|
|
||||
|
reservations.Remove(token); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public Task RemoveReservationAsync(string token) |
||||
|
{ |
||||
|
reservations.Remove(token ?? string.Empty); |
||||
|
|
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
|
||||
|
public async Task RemoveAsync(T id) |
||||
|
{ |
||||
|
var name = state.Value.Names.FirstOrDefault(x => Equals(x.Value, id)).Key; |
||||
|
|
||||
|
if (name != null) |
||||
|
{ |
||||
|
state.Value.Names.Remove(name); |
||||
|
|
||||
|
await state.WriteAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task<List<T>> GetIdsAsync(string[] names) |
||||
|
{ |
||||
|
var result = new List<T>(); |
||||
|
|
||||
|
if (names != null) |
||||
|
{ |
||||
|
foreach (var name in names) |
||||
|
{ |
||||
|
if (state.Value.Names.TryGetValue(name, out var id)) |
||||
|
{ |
||||
|
result.Add(id); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult(result); |
||||
|
} |
||||
|
|
||||
|
public Task<T> GetIdAsync(string name) |
||||
|
{ |
||||
|
state.Value.Names.TryGetValue(name, out var id); |
||||
|
|
||||
|
return Task.FromResult(id); |
||||
|
} |
||||
|
|
||||
|
public Task<List<T>> GetIdsAsync() |
||||
|
{ |
||||
|
return Task.FromResult(state.Value.Names.Values.ToList()); |
||||
|
} |
||||
|
|
||||
|
private bool IsInUse(string name) |
||||
|
{ |
||||
|
return state.Value.Names.ContainsKey(name); |
||||
|
} |
||||
|
|
||||
|
private bool IsReserved(string name) |
||||
|
{ |
||||
|
return reservations.Values.Any(x => x.Name == name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Orleans.Indexes |
||||
|
{ |
||||
|
public class UniqueNameIndexState<T> |
||||
|
{ |
||||
|
public Dictionary<string, T> Names { get; set; } = new Dictionary<string, T>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
// tslint:disable: component-selector
|
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; |
||||
|
|
||||
|
import { EventConsumerDto, EventConsumersState } from '@app/features/administration/internal'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: '[sqxEventConsumer]', |
||||
|
template: ` |
||||
|
<tr [class.faulted]="eventConsumer.error && eventConsumer.error?.length > 0"> |
||||
|
<td class="auto-auto"> |
||||
|
<span class="truncate"> |
||||
|
<i class="faulted-icon icon icon-bug" (click)="error.emit()" [class.hidden]="!eventConsumer.error || eventConsumer.error?.length === 0"></i> |
||||
|
|
||||
|
{{eventConsumer.name}} |
||||
|
</span> |
||||
|
</td> |
||||
|
<td class="cell-auto-right"> |
||||
|
<span>{{eventConsumer.position}}</span> |
||||
|
</td> |
||||
|
<td class="cell-actions-lg"> |
||||
|
<button type="button" class="btn btn-text" (click)="reset()" *ngIf="eventConsumer.canReset" title="Reset Event Consumer"> |
||||
|
<i class="icon icon-reset"></i> |
||||
|
</button> |
||||
|
<button type="button" class="btn btn-text" (click)="start()" *ngIf="eventConsumer.canStart" title="Start Event Consumer"> |
||||
|
<i class="icon icon-play"></i> |
||||
|
</button> |
||||
|
<button type="button" class="btn btn-text" (click)="stop()" *ngIf="eventConsumer.canStop" title="Stop Event Consumer"> |
||||
|
<i class="icon icon-pause"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="spacer"></tr>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class EventConsumerComponent { |
||||
|
@Output() |
||||
|
public error = new EventEmitter(); |
||||
|
|
||||
|
@Input('sqxEventConsumer') |
||||
|
public eventConsumer: EventConsumerDto; |
||||
|
|
||||
|
constructor( |
||||
|
public readonly eventConsumersState: EventConsumersState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public start() { |
||||
|
this.eventConsumersState.start(this.eventConsumer); |
||||
|
} |
||||
|
|
||||
|
public stop() { |
||||
|
this.eventConsumersState.stop(this.eventConsumer); |
||||
|
} |
||||
|
|
||||
|
public reset() { |
||||
|
this.eventConsumersState.reset(this.eventConsumer); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
// tslint:disable: component-selector
|
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
||||
|
|
||||
|
import { UserDto, UsersState } from '@app/features/administration/internal'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: '[sqxUser]', |
||||
|
template: ` |
||||
|
<tr [routerLink]="user.id" routerLinkActive="active"> |
||||
|
<td class="cell-user"> |
||||
|
<img class="user-picture" title="{{user.displayName}}" [attr.src]="user | sqxUserDtoPicture" /> |
||||
|
</td> |
||||
|
<td class="cell-auto"> |
||||
|
<span class="user-name table-cell">{{user.displayName}}</span> |
||||
|
</td> |
||||
|
<td class="cell-auto"> |
||||
|
<span class="user-email table-cell">{{user.email}}</span> |
||||
|
</td> |
||||
|
<td class="cell-actions"> |
||||
|
<button type="button" class="btn btn-text" (click)="lock()" sqxStopClick *ngIf="user.canLock" title="Lock User"> |
||||
|
<i class="icon icon-unlocked"></i> |
||||
|
</button> |
||||
|
<button type="button" class="btn btn-text" (click)="unlock()" sqxStopClick *ngIf="user.canUnlock" title="Unlock User"> |
||||
|
<i class="icon icon-lock"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="spacer"></tr>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class UserComponent { |
||||
|
@Input('sqxUser') |
||||
|
public user: UserDto; |
||||
|
|
||||
|
constructor( |
||||
|
private readonly usersState: UsersState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public lock() { |
||||
|
this.usersState.lock(this.user); |
||||
|
} |
||||
|
|
||||
|
public unlock() { |
||||
|
this.usersState.unlock(this.user); |
||||
|
} |
||||
|
} |
||||
@ -1,4 +1,4 @@ |
|||||
<sqx-title message="{app} | API | GraphQL" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title> |
<sqx-title message="GraphQL"></sqx-title> |
||||
|
|
||||
<sqx-panel desiredWidth="*" minWidth="50rem" isFullSize="true"> |
<sqx-panel desiredWidth="*" minWidth="50rem" isFullSize="true"> |
||||
<div inner #graphiQLContainer></div> |
<div inner #graphiQLContainer></div> |
||||
|
|||||
@ -1,98 +0,0 @@ |
|||||
<td class="cell-select" sqxStopClick> |
|
||||
<ng-container *ngIf="!isReference; else referenceTemplate"> |
|
||||
<input type="checkbox" class="form-check" |
|
||||
[disabled]="!selectable" |
|
||||
[ngModel]="selected || !selectable" |
|
||||
(ngModelChange)="selectedChange.emit($event)" /> |
|
||||
</ng-container> |
|
||||
|
|
||||
<ng-template #referenceTemplate> |
|
||||
<i class="icon-drag2 drag-handle"></i> |
|
||||
</ng-template> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-actions cell-actions-left" *ngIf="!isReadOnly && !isDirty" sqxStopClick> |
|
||||
<div class="dropdown dropdown-options" *ngIf="content"> |
|
||||
<button type="button" class="btn btn-text-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions> |
|
||||
<i class="icon-dots"></i> |
|
||||
</button> |
|
||||
|
|
||||
<ng-container *sqxModal="dropdown;closeAlways:true"> |
|
||||
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade> |
|
||||
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)"> |
|
||||
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}} |
|
||||
</a> |
|
||||
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="canClone"> |
|
||||
Clone |
|
||||
</a> |
|
||||
|
|
||||
<div class="dropdown-divider"></div> |
|
||||
|
|
||||
<a class="dropdown-item dropdown-item-delete" |
|
||||
(sqxConfirmClick)="emitDelete()" |
|
||||
confirmTitle="Delete content" |
|
||||
confirmText="Do you really want to delete the content?"> |
|
||||
Delete |
|
||||
</a> |
|
||||
</div> |
|
||||
</ng-container> |
|
||||
</div> |
|
||||
</td> |
|
||||
|
|
||||
<ng-container *ngIf="isDirty"> |
|
||||
<td class="cell-actions" > |
|
||||
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick> |
|
||||
<i class="icon-close"></i> |
|
||||
</button> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-user" > |
|
||||
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick> |
|
||||
<i class="icon-checkmark"></i> |
|
||||
</button> |
|
||||
</td> |
|
||||
</ng-container> |
|
||||
|
|
||||
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty"> |
|
||||
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" /> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-auto cell-content" *ngFor="let field of schemaFields; let i = index; trackBy: trackByFieldFn" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)"> |
|
||||
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate"> |
|
||||
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor> |
|
||||
</ng-container> |
|
||||
|
|
||||
<ng-template #displayTemplate> |
|
||||
<sqx-content-value [value]="values[i]"></sqx-content-value> |
|
||||
</ng-template> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty"> |
|
||||
<sqx-content-status |
|
||||
[status]="content.status" |
|
||||
[statusColor]="content.statusColor" |
|
||||
[scheduledTo]="content.scheduleJob?.status" |
|
||||
[scheduledAt]="content.scheduleJob?.dueTime" |
|
||||
[isPending]="content.isPending"> |
|
||||
</sqx-content-status> |
|
||||
|
|
||||
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-actions" *ngIf="isReference" [sqxStopClick]="isDirty"> |
|
||||
<div class="reference-edit"> |
|
||||
<button type="button" class="btn btn-text-secondary"> |
|
||||
<i class="icon-dots"></i> |
|
||||
</button> |
|
||||
|
|
||||
<div class="reference-menu"> |
|
||||
<a class="btn btn-text-secondary" [routerLink]="['../..', schema.name, content.id]"> |
|
||||
<i class="icon-pencil"></i> |
|
||||
</a> |
|
||||
|
|
||||
<button type="button" class="btn btn-text-secondary" (click)="emitDelete()"> |
|
||||
<i class="icon-close"></i> |
|
||||
</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</td> |
|
||||
@ -0,0 +1,101 @@ |
|||||
|
<tr [routerLink]="link" routerLinkActive="active"> |
||||
|
<td class="cell-select" sqxStopClick> |
||||
|
<ng-container *ngIf="!isReference; else referenceTemplate"> |
||||
|
<input type="checkbox" class="form-check" |
||||
|
[disabled]="!selectable" |
||||
|
[ngModel]="selected || !selectable" |
||||
|
(ngModelChange)="selectedChange.emit($event)" /> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-template #referenceTemplate> |
||||
|
<i class="icon-drag2 drag-handle"></i> |
||||
|
</ng-template> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-actions cell-actions-left" *ngIf="!isReadOnly && !isDirty" sqxStopClick> |
||||
|
<div class="dropdown dropdown-options" *ngIf="content"> |
||||
|
<button type="button" class="btn btn-text-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions> |
||||
|
<i class="icon-dots"></i> |
||||
|
</button> |
||||
|
|
||||
|
<ng-container *sqxModal="dropdown;closeAlways:true"> |
||||
|
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade> |
||||
|
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)"> |
||||
|
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}} |
||||
|
</a> |
||||
|
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="canClone"> |
||||
|
Clone |
||||
|
</a> |
||||
|
|
||||
|
<div class="dropdown-divider"></div> |
||||
|
|
||||
|
<a class="dropdown-item dropdown-item-delete" |
||||
|
(sqxConfirmClick)="emitDelete()" |
||||
|
confirmTitle="Delete content" |
||||
|
confirmText="Do you really want to delete the content?"> |
||||
|
Delete |
||||
|
</a> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
</td> |
||||
|
|
||||
|
<ng-container *ngIf="isDirty"> |
||||
|
<td class="cell-actions" > |
||||
|
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick> |
||||
|
<i class="icon-close"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-user" > |
||||
|
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick> |
||||
|
<i class="icon-checkmark"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty"> |
||||
|
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" /> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-auto cell-content" *ngFor="let field of schemaFields; let i = index; trackBy: trackByFieldFn" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)"> |
||||
|
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate"> |
||||
|
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-template #displayTemplate> |
||||
|
<sqx-content-value [value]="values[i]"></sqx-content-value> |
||||
|
</ng-template> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty"> |
||||
|
<sqx-content-status |
||||
|
[status]="content.status" |
||||
|
[statusColor]="content.statusColor" |
||||
|
[scheduledTo]="content.scheduleJob?.status" |
||||
|
[scheduledAt]="content.scheduleJob?.dueTime" |
||||
|
[isPending]="content.isPending"> |
||||
|
</sqx-content-status> |
||||
|
|
||||
|
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-actions" *ngIf="isReference" [sqxStopClick]="isDirty"> |
||||
|
<div class="reference-edit"> |
||||
|
<button type="button" class="btn btn-text-secondary"> |
||||
|
<i class="icon-dots"></i> |
||||
|
</button> |
||||
|
|
||||
|
<div class="reference-menu"> |
||||
|
<a class="btn btn-text-secondary" [routerLink]="['../..', schema.name, content.id]"> |
||||
|
<i class="icon-pencil"></i> |
||||
|
</a> |
||||
|
|
||||
|
<button type="button" class="btn btn-text-secondary" (click)="emitDelete()"> |
||||
|
<i class="icon-close"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="spacer"></tr> |
||||
@ -0,0 +1,87 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
// tslint:disable: component-selector
|
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; |
||||
|
|
||||
|
import { |
||||
|
ActionsDto, |
||||
|
RuleDto, |
||||
|
RulesState, |
||||
|
TriggersDto |
||||
|
} from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: '[sqxRule]', |
||||
|
template: ` |
||||
|
<tr> |
||||
|
<td class="cell-separator"> |
||||
|
<h3>If</h3> |
||||
|
</td> |
||||
|
<td class="cell-auto"> |
||||
|
<span (click)="editTrigger.emit()"> |
||||
|
<sqx-rule-element [type]="rule.triggerType" [element]="ruleTriggers[rule.triggerType]"></sqx-rule-element> |
||||
|
</span> |
||||
|
</td> |
||||
|
<td class="cell-separator"> |
||||
|
<h3>then</h3> |
||||
|
</td> |
||||
|
<td class="cell-auto"> |
||||
|
<span (click)="editAction.emit()"> |
||||
|
<sqx-rule-element [type]="rule.actionType" [element]="ruleActions[rule.actionType]"></sqx-rule-element> |
||||
|
</span> |
||||
|
</td> |
||||
|
<td class="cell-actions"> |
||||
|
<sqx-toggle [disabled]="!rule.canDisable && !rule.canEnable" [ngModel]="rule.isEnabled" (ngModelChange)="toggle()"></sqx-toggle> |
||||
|
</td> |
||||
|
<td class="cell-actions"> |
||||
|
<button type="button" class="btn btn-text-danger" |
||||
|
[disabled]="!rule.canDelete" |
||||
|
(sqxConfirmClick)="delete()" |
||||
|
confirmTitle="Delete rule" |
||||
|
confirmText="Do you really want to delete the rule?"> |
||||
|
<i class="icon-bin2"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="spacer"></tr>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class RuleComponent { |
||||
|
@Output() |
||||
|
public editTrigger = new EventEmitter(); |
||||
|
|
||||
|
@Output() |
||||
|
public editAction = new EventEmitter(); |
||||
|
|
||||
|
@Input() |
||||
|
public ruleTriggers: TriggersDto; |
||||
|
|
||||
|
@Input() |
||||
|
public ruleActions: ActionsDto; |
||||
|
|
||||
|
@Input('sqxRule') |
||||
|
public rule: RuleDto; |
||||
|
|
||||
|
constructor( |
||||
|
private readonly rulesState: RulesState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public delete() { |
||||
|
this.rulesState.delete(this.rule); |
||||
|
} |
||||
|
|
||||
|
public toggle() { |
||||
|
if (this.rule.isEnabled) { |
||||
|
this.rulesState.disable(this.rule); |
||||
|
} else { |
||||
|
this.rulesState.enable(this.rule); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue