Browse Source

Javascript action and style fixes.

pull/741/head
Sebastian 4 years ago
parent
commit
73ed3a1ff3
  1. 10
      backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs
  2. 6
      backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs
  3. 4
      backend/extensions/Squidex.Extensions/Actions/Comment/CommentAction.cs
  4. 8
      backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentAction.cs
  5. 14
      backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs
  6. 12
      backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs
  7. 16
      backend/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs
  8. 4
      backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs
  9. 14
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaAction.cs
  10. 14
      backend/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs
  11. 8
      backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs
  12. 4
      backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs
  13. 29
      backend/extensions/Squidex.Extensions/Actions/Script/ScriptAction.cs
  14. 50
      backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs
  15. 21
      backend/extensions/Squidex.Extensions/Actions/Script/ScriptPlugin.cs
  16. 4
      backend/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs
  17. 6
      backend/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs
  18. 10
      backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs
  19. 22
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EditorAttribute.cs
  20. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs
  21. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleFieldEditor.cs
  22. 46
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs
  23. 2
      backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs
  24. 11
      backend/src/Squidex/web.config
  25. 69
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistryTests.cs
  26. 4
      frontend/app/features/rules/pages/events/rule-event.component.html
  27. 41
      frontend/app/features/rules/pages/events/rule-event.component.scss
  28. 34
      frontend/app/features/rules/pages/rule/rule-page.component.html
  29. 8
      frontend/app/features/rules/pages/rule/rule-page.component.ts
  30. 2
      frontend/app/features/rules/pages/rules/rules-page.component.html
  31. 3
      frontend/app/features/rules/shared/actions/generic-action.component.html
  32. 8
      frontend/app/theme/_lists.scss

10
backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs

@ -23,29 +23,29 @@ namespace Squidex.Extensions.Actions.Algolia
{
[LocalizedRequired]
[Display(Name = "Application Id", Description = "The application ID.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string AppId { get; set; }
[LocalizedRequired]
[Display(Name = "Api Key", Description = "The API key to grant access to Squidex.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string ApiKey { get; set; }
[LocalizedRequired]
[Display(Name = "Index Name", Description = "The name of the index.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string IndexName { get; set; }
[Display(Name = "Document", Description = "The optional custom document.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Document { get; set; }
[Display(Name = "Deletion", Description = "The condition when to delete the entry.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Delete { get; set; }
}
}

6
backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs

@ -25,18 +25,18 @@ namespace Squidex.Extensions.Actions.AzureQueue
{
[LocalizedRequired]
[Display(Name = "Connection", Description = "The connection string to the storage account.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string ConnectionString { get; set; }
[LocalizedRequired]
[Display(Name = "Queue", Description = "The name of the queue.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Queue { get; set; }
[Display(Name = "Payload (Optional)", Description = "Leave it empty to use the full event as body.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Payload { get; set; }

4
backend/extensions/Squidex.Extensions/Actions/Comment/CommentAction.cs

@ -22,12 +22,12 @@ namespace Squidex.Extensions.Actions.Comment
{
[LocalizedRequired]
[Display(Name = "Text", Description = "The comment text.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Text { get; set; }
[Display(Name = "Client", Description = "An optional client name.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Client { get; set; }
}
}

8
backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentAction.cs

@ -22,21 +22,21 @@ namespace Squidex.Extensions.Actions.CreateContent
{
[LocalizedRequired]
[Display(Name = "Data", Description = "The content data.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Data { get; set; }
[LocalizedRequired]
[Display(Name = "Schema", Description = "The name of the schema.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Schema { get; set; }
[Display(Name = "Client", Description = "An optional client name.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Client { get; set; }
[Display(Name = "Publish", Description = "Publish the content.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public bool Publish { get; set; }
}
}

14
backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs

@ -25,36 +25,36 @@ namespace Squidex.Extensions.Actions.Discourse
[AbsoluteUrl]
[LocalizedRequired]
[Display(Name = "Server Url", Description = "The url to the discourse server.")]
[DataType(DataType.Url)]
[Editor(RuleFieldEditor.Url)]
public Uri Url { get; set; }
[LocalizedRequired]
[Display(Name = "Api Key", Description = "The api key to authenticate to your discourse server.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string ApiKey { get; set; }
[LocalizedRequired]
[Display(Name = "Api User", Description = "The api username to authenticate to your discourse server.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string ApiUsername { get; set; }
[LocalizedRequired]
[Display(Name = "Text", Description = "The text as markdown.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Text { get; set; }
[Display(Name = "Title", Description = "The optional title when creating new topics.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Title { get; set; }
[Display(Name = "Topic", Description = "The optional topic id.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public int? Topic { get; set; }
[Display(Name = "Category", Description = "The optional category id.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public int? Category { get; set; }
}
}

12
backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs

@ -25,30 +25,30 @@ namespace Squidex.Extensions.Actions.ElasticSearch
[AbsoluteUrl]
[LocalizedRequired]
[Display(Name = "Server Url", Description = "The url to the elastic search instance or cluster.")]
[DataType(DataType.Url)]
[Editor(RuleFieldEditor.Url)]
public Uri Host { get; set; }
[LocalizedRequired]
[Display(Name = "Index Name", Description = "The name of the index.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string IndexName { get; set; }
[Display(Name = "Username", Description = "The optional username.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Username { get; set; }
[Display(Name = "Password", Description = "The optional password.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Password { get; set; }
[Display(Name = "Document", Description = "The optional custom document.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Document { get; set; }
[Display(Name = "Deletion", Description = "The condition when to delete the document.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Delete { get; set; }
}
}

16
backend/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs

@ -23,46 +23,46 @@ namespace Squidex.Extensions.Actions.Email
{
[LocalizedRequired]
[Display(Name = "Server Host", Description = "The IP address or host to the SMTP server.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string ServerHost { get; set; }
[LocalizedRequired]
[Display(Name = "Server Port", Description = "The port to the SMTP server.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public int ServerPort { get; set; }
[LocalizedRequired]
[Display(Name = "Username", Description = "The username for the SMTP server.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string ServerUsername { get; set; }
[LocalizedRequired]
[Display(Name = "Password", Description = "The password for the SMTP server.")]
[DataType(DataType.Password)]
[Editor(RuleFieldEditor.Password)]
public string ServerPassword { get; set; }
[LocalizedRequired]
[Display(Name = "From Address", Description = "The email sending address.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string MessageFrom { get; set; }
[LocalizedRequired]
[Display(Name = "To Address", Description = "The email message will be sent to.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string MessageTo { get; set; }
[LocalizedRequired]
[Display(Name = "Subject", Description = "The subject line for this email message.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string MessageSubject { get; set; }
[LocalizedRequired]
[Display(Name = "Body", Description = "The message body.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string MessageBody { get; set; }
}

4
backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs

@ -23,12 +23,12 @@ namespace Squidex.Extensions.Actions.Fastly
{
[LocalizedRequired]
[Display(Name = "Api Key", Description = "The API key to grant access to Squidex.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string ApiKey { get; set; }
[LocalizedRequired]
[Display(Name = "Service Id", Description = "The ID of the fastly service.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string ServiceId { get; set; }
}
}

14
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaAction.cs

@ -23,36 +23,36 @@ namespace Squidex.Extensions.Actions.Kafka
{
[LocalizedRequired]
[Display(Name = "Topic Name", Description = "The name of the topic.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string TopicName { get; set; }
[Display(Name = "Payload (Optional)", Description = "Leave it empty to use the full event as body.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Payload { get; set; }
[Display(Name = "Key", Description = "The message key, commonly used for partitioning.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Key { get; set; }
[Display(Name = "Partition Key", Description = "The partition key, only used when we don't want to define partiontionig with key.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string PartitionKey { get; set; }
[Display(Name = "Partition Count", Description = "Define the number of partitions for specific topic.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public int PartitionCount { get; set; }
[Display(Name = "Headers (Optional)", Description = "The message headers in the format '[Key]=[Value]', one entry per line.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Headers { get; set; }
[Display(Name = "Schema (Optional)", Description = "Define a specific AVRO schema in JSON format.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
public string Schema { get; set; }
}
}

14
backend/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs

@ -23,37 +23,37 @@ namespace Squidex.Extensions.Actions.Medium
{
[LocalizedRequired]
[Display(Name = "Access Token", Description = "The self issued access token.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string AccessToken { get; set; }
[LocalizedRequired]
[Display(Name = "Title", Description = "The title, used for the url.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Title { get; set; }
[LocalizedRequired]
[Display(Name = "Content", Description = "The content, either html or markdown.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Content { get; set; }
[Display(Name = "Canonical Url", Description = "The original home of this content, if it was originally published elsewhere.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string CanonicalUrl { get; set; }
[Display(Name = "Tags", Description = "The optional comma separated list of tags.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Tags { get; set; }
[Display(Name = "Publication Id", Description = "Optional publication id.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string PublicationId { get; set; }
[Display(Name = "Is Html", Description = "Indicates whether the content is markdown or html.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public bool IsHtml { get; set; }
}
}

8
backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs

@ -22,22 +22,22 @@ namespace Squidex.Extensions.Actions.Notification
{
[LocalizedRequired]
[Display(Name = "User", Description = "The user id or email.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string User { get; set; }
[LocalizedRequired]
[Display(Name = "Title", Description = "The text to send.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Text { get; set; }
[Display(Name = "Url", Description = "The optional url to attach to the notification.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Url { get; set; }
[Display(Name = "Client", Description = "An optional client name.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Client { get; set; }
}
}

4
backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs

@ -23,13 +23,13 @@ namespace Squidex.Extensions.Actions.Prerender
{
[LocalizedRequired]
[Display(Name = "Token", Description = "The prerender token from your account.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Token { get; set; }
[LocalizedRequired]
[Display(Name = "Url", Description = "The url to recache.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Url { get; set; }
}
}

29
backend/extensions/Squidex.Extensions/Actions/Script/ScriptAction.cs

@ -0,0 +1,29 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure.Validation;
namespace Squidex.Extensions.Actions.Script
{
[RuleAction(
Title = "Script",
IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><path d='M112.155 67.644h84.212v236.019c0 106.375-50.969 143.497-132.414 143.497-19.944 0-45.429-3.324-62.052-8.864l9.419-68.146c11.635 3.878 26.594 6.648 43.214 6.648 35.458 0 57.621-16.068 57.621-73.687V67.644zM269.484 354.634c22.161 11.635 57.62 23.27 93.632 23.27 38.783 0 59.282-16.066 59.282-40.998 0-22.715-17.729-36.565-62.606-52.079-62.053-22.162-103.05-56.512-103.05-111.36 0-63.715 53.741-111.917 141.278-111.917 42.662 0 73.132 8.313 95.295 18.838l-18.839 67.592c-14.404-7.201-41.553-17.729-77.562-17.729-36.567 0-54.297 17.175-54.297 36.013 0 23.824 20.499 34.349 69.256 53.188 65.928 24.378 96.4 58.728 96.4 111.915 0 62.606-47.647 115.794-150.143 115.794-42.662 0-84.77-11.636-105.82-23.27l17.174-69.257z'/></svg>",
IconColor = "#f0be25",
Display = "Run a Script",
Description = "Runs a custom Javascript")]
public sealed record ScriptAction : RuleAction
{
[LocalizedRequired]
[Display(Name = "Script", Description = "The script to render.")]
[Editor(RuleFieldEditor.Javascript)]
[Formattable]
public string Script { get; set; }
}
}

50
backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs

@ -0,0 +1,50 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting;
namespace Squidex.Extensions.Actions.Script
{
public sealed class ScriptActionHandler : RuleActionHandler<ScriptAction, ScriptJob>
{
private readonly IScriptEngine scriptEngine;
public ScriptActionHandler(RuleEventFormatter formatter, IScriptEngine scriptEngine)
: base(formatter)
{
this.scriptEngine = scriptEngine;
}
protected override Task<(string Description, ScriptJob Data)> CreateJobAsync(EnrichedEvent @event, ScriptAction action)
{
var job = new ScriptJob { Script = action.Script, Event = @event };
return Task.FromResult(($"Run a script", job));
}
protected override async Task<Result> ExecuteJobAsync(ScriptJob job, CancellationToken ct = default)
{
var result = await scriptEngine.ExecuteAsync(new ScriptVars
{
["event"] = job.Event
}, job.Script, ct: ct);
return Result.Success(result?.ToString());
}
}
public sealed class ScriptJob
{
public EnrichedEvent Event { get; set; }
public string Script { get; set; }
}
}

21
backend/extensions/Squidex.Extensions/Actions/Script/ScriptPlugin.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure.Plugins;
namespace Squidex.Extensions.Actions.Script
{
public sealed class ScriptPlugin : IPlugin
{
public void ConfigureServices(IServiceCollection services, IConfiguration config)
{
services.AddRuleAction<ScriptAction, ScriptActionHandler>();
}
}
}

4
backend/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs

@ -25,12 +25,12 @@ namespace Squidex.Extensions.Actions.Slack
[AbsoluteUrl]
[LocalizedRequired]
[Display(Name = "Webhook Url", Description = "The slack webhook url.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public Uri WebhookUrl { get; set; }
[LocalizedRequired]
[Display(Name = "Text", Description = "The text that is sent as message to slack.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Text { get; set; }
}

6
backend/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs

@ -23,17 +23,17 @@ namespace Squidex.Extensions.Actions.Twitter
{
[LocalizedRequired]
[Display(Name = "Access Token", Description = " The generated access token.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string AccessToken { get; set; }
[LocalizedRequired]
[Display(Name = "Access Secret", Description = " The generated access secret.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string AccessSecret { get; set; }
[LocalizedRequired]
[Display(Name = "Text", Description = "The text that is sent as tweet to twitter.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Text { get; set; }
}

10
backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs

@ -24,7 +24,7 @@ namespace Squidex.Extensions.Actions.Webhook
{
[LocalizedRequired]
[Display(Name = "Url", Description = "The url to the webhook.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public Uri Url { get; set; }
@ -33,21 +33,21 @@ namespace Squidex.Extensions.Actions.Webhook
public WebhookMethod Method { get; set; }
[Display(Name = "Payload (Optional)", Description = "Leave it empty to use the full event as body.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Payload { get; set; }
[Display(Name = "Payload Type", Description = "The mime type of the payload.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string PayloadType { get; set; }
[Display(Name = "Headers (Optional)", Description = "The message headers in the format '[Key]=[Value]', one entry per line.")]
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Headers { get; set; }
[Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the payload signature.")]
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string SharedSecret { get; set; }
}

22
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EditorAttribute.cs

@ -0,0 +1,22 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Core.HandleRules
{
[AttributeUsage(AttributeTargets.Property)]
public sealed class EditorAttribute : Attribute
{
public RuleFieldEditor Editor { get; }
public EditorAttribute(RuleFieldEditor editor)
{
Editor = editor;
}
}
}

2
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs

@ -9,7 +9,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
public sealed class RuleActionProperty
{
public RuleActionPropertyEditor Editor { get; set; }
public RuleFieldEditor Editor { get; set; }
public string Name { get; set; }

3
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs → backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleFieldEditor.cs

@ -7,11 +7,12 @@
namespace Squidex.Domain.Apps.Core.HandleRules
{
public enum RuleActionPropertyEditor
public enum RuleFieldEditor
{
Checkbox,
Dropdown,
Email,
Javascript,
Number,
Password,
Text,

46
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs

@ -119,40 +119,19 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var values = Enum.GetNames(type);
actionProperty.Options = values;
actionProperty.Editor = RuleActionPropertyEditor.Dropdown;
actionProperty.Editor = RuleFieldEditor.Dropdown;
}
else if (IsBoolean(type))
{
actionProperty.Editor = RuleFieldEditor.Checkbox;
}
else if (IsNumericType(type))
{
actionProperty.Editor = RuleFieldEditor.Number;
}
else
{
var dataType = GetDataAttribute<DataTypeAttribute>(property)?.DataType;
if (IsBoolean(type))
{
actionProperty.Editor = RuleActionPropertyEditor.Checkbox;
}
else if (IsNumericType(type))
{
actionProperty.Editor = RuleActionPropertyEditor.Number;
}
else if (dataType == DataType.Url)
{
actionProperty.Editor = RuleActionPropertyEditor.Url;
}
else if (dataType == DataType.Password)
{
actionProperty.Editor = RuleActionPropertyEditor.Password;
}
else if (dataType == DataType.EmailAddress)
{
actionProperty.Editor = RuleActionPropertyEditor.Email;
}
else if (dataType == DataType.MultilineText)
{
actionProperty.Editor = RuleActionPropertyEditor.TextArea;
}
else
{
actionProperty.Editor = RuleActionPropertyEditor.Text;
}
actionProperty.Editor = GetEditor(property);
}
definition.Properties.Add(actionProperty);
@ -171,6 +150,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return result;
}
private static RuleFieldEditor GetEditor(PropertyInfo property)
{
return property.GetCustomAttribute<EditorAttribute>()?.Editor ?? RuleFieldEditor.Text;
}
private static bool IsNullable(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);

2
backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs

@ -16,7 +16,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
/// The html editor.
/// </summary>
[LocalizedRequired]
public RuleActionPropertyEditor Editor { get; set; }
public RuleFieldEditor Editor { get; set; }
/// <summary>
/// The name of the editor.

11
backend/src/Squidex/web.config

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="bin\Debug\net5.0\Squidex.exe" arguments="" stdoutLogEnabled="false" hostingModel="InProcess" />
</system.webServer>
</location>
</configuration>

69
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistryTests.cs

@ -28,19 +28,6 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
}
}
[RuleAction(
Title = "Invalid",
IconImage = "<svg></svg>",
IconColor = "#1e5470",
Display = "Action display",
Description = "Action description.",
ReadMore = "https://www.readmore.com/")]
public sealed record MyInvalidRuleAction : RuleAction
{
[DataType(DataType.Custom)]
public string Custom { get; set; }
}
public enum ActionEnum
{
Yes,
@ -58,38 +45,38 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{
[LocalizedRequired]
[Display(Name = "Url Name", Description = "Url Description")]
[DataType(DataType.Url)]
[Editor(RuleFieldEditor.Url)]
[Formattable]
public Uri Url { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Editor(RuleFieldEditor.Javascript)]
public string Script { get; set; }
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public string Text { get; set; }
[DataType(DataType.MultilineText)]
[Editor(RuleFieldEditor.TextArea)]
public string TextMultiline { get; set; }
[DataType(DataType.Password)]
[Editor(RuleFieldEditor.Password)]
public string Password { get; set; }
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public ActionEnum Enum { get; set; }
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public ActionEnum? EnumOptional { get; set; }
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public bool Boolean { get; set; }
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public bool? BooleanOptional { get; set; }
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public int Number { get; set; }
[DataType(DataType.Text)]
[Editor(RuleFieldEditor.Text)]
public int? NumberOptional { get; set; }
}
@ -112,17 +99,17 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "url",
Display = "Url Name",
Description = "Url Description",
Editor = RuleActionPropertyEditor.Url,
Editor = RuleFieldEditor.Url,
IsFormattable = true,
IsRequired = true
});
expected.Properties.Add(new RuleActionProperty
{
Name = "email",
Display = "Email",
Name = "script",
Display = "Script",
Description = null,
Editor = RuleActionPropertyEditor.Email,
Editor = RuleFieldEditor.Javascript,
IsRequired = false
});
@ -131,7 +118,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "text",
Display = "Text",
Description = null,
Editor = RuleActionPropertyEditor.Text,
Editor = RuleFieldEditor.Text,
IsRequired = false
});
@ -140,7 +127,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "textMultiline",
Display = "TextMultiline",
Description = null,
Editor = RuleActionPropertyEditor.TextArea,
Editor = RuleFieldEditor.TextArea,
IsRequired = false
});
@ -149,7 +136,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "password",
Display = "Password",
Description = null,
Editor = RuleActionPropertyEditor.Password,
Editor = RuleFieldEditor.Password,
IsRequired = false
});
@ -158,7 +145,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "enum",
Display = "Enum",
Description = null,
Editor = RuleActionPropertyEditor.Dropdown,
Editor = RuleFieldEditor.Dropdown,
IsRequired = false,
Options = new[] { "Yes", "No" }
});
@ -168,7 +155,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "enumOptional",
Display = "EnumOptional",
Description = null,
Editor = RuleActionPropertyEditor.Dropdown,
Editor = RuleFieldEditor.Dropdown,
IsRequired = false,
Options = new[] { "Yes", "No" }
});
@ -178,7 +165,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "boolean",
Display = "Boolean",
Description = null,
Editor = RuleActionPropertyEditor.Checkbox,
Editor = RuleFieldEditor.Checkbox,
IsRequired = false
});
@ -187,7 +174,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "booleanOptional",
Display = "BooleanOptional",
Description = null,
Editor = RuleActionPropertyEditor.Checkbox,
Editor = RuleFieldEditor.Checkbox,
IsRequired = false
});
@ -196,7 +183,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "number",
Display = "Number",
Description = null,
Editor = RuleActionPropertyEditor.Number,
Editor = RuleFieldEditor.Number,
IsRequired = true
});
@ -205,7 +192,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Name = "numberOptional",
Display = "NumberOptional",
Description = null,
Editor = RuleActionPropertyEditor.Number,
Editor = RuleFieldEditor.Number,
IsRequired = false
});
@ -215,11 +202,5 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
currentDefinition.Should().BeEquivalentTo(expected);
}
[Fact]
public void Should_throw_exception_if_validation_attribute_used_incorrectly()
{
Assert.Throws<InvalidOperationException>(() => sut.Add<MyInvalidRuleAction>());
}
}
}

4
frontend/app/features/rules/pages/events/rule-event.component.html

@ -1,4 +1,4 @@
<tr [class.expanded]="expanded">
<tr [class.expanded]="expanded" class="table-items-row" [class.expanded]>
<td class="cell-label">
<span class="badge rounded-pill badge-{{jobResultClass}}">{{event.jobResult}}</span>
</td>
@ -18,7 +18,7 @@
</td>
</tr>
<tr *ngIf="expanded">
<td colspan="5">
<td colspan="5" class="details">
<div class="event-header">
<h4>{{ 'rules.ruleEvents.lastInvokedLabel' | sqxTranslate }}</h4>
</div>

41
frontend/app/features/rules/pages/events/rule-event.component.scss

@ -1,36 +1,43 @@
h4 {
margin-bottom: 1rem;
td {
&.details {
border-top: 0;
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
padding: 0 !important;
}
}
.expanded {
border-bottom: 0;
.table-items-row {
&.expanded {
td {
border-bottom: 0;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
}
}
$color-dark-onboarding:#273039;
.event {
&-stats {
font-size: $font-smallest;
}
&-dump {
margin-top: 1rem;
&-header,
&-dump,
&-stats {
padding: .75rem 1.25rem;
}
&-header {
background: $color-white;
background: $color-border-light;
border: 0;
margin: -.75rem -1.25rem;
margin-bottom: 1rem;
border-bottom: 2px solid $color-border;
padding: .75rem 1.25rem;
position: relative;
&::before {
@include caret-top($color-white);
@include absolute(-1.1rem, 1.8rem, auto, auto);
}
h3 {
h4 {
font-size: 1rem;
font-weight: 500;
margin: 0;
}
}

34
frontend/app/features/rules/pages/rule/rule-page.component.html

@ -14,19 +14,31 @@
</ng-container>
<ng-container menu>
<div class="btn btn-outline-secondary btn-enabled" *ngIf="rule">
<span class="me-2" *ngIf="isEnabled">
{{ 'common.enabled' | sqxTranslate }}
</span>
<span class="me-2" *ngIf="!isEnabled">
{{ 'common.disabled' | sqxTranslate }}
</span>
<ng-container *ngIf="isManual; else notManual">
<button class="btn btn-secondary" [disabled]="!rule?.canTrigger"
(sqxConfirmClick)="trigger()"
confirmTitle="i18n:rules.triggerConfirmTitle"
confirmText="i18n:rules.triggerConfirmText"
confirmRememberKey="triggerRule">
<i class="icon-play-line"></i>
</button>
</ng-container>
<ng-template #notManual>
<div class="btn btn-outline-secondary btn-enabled" *ngIf="rule">
<span class="me-2" *ngIf="isEnabled">
{{ 'common.enabled' | sqxTranslate }}
</span>
<sqx-toggle [(ngModel)]="isEnabled" [ngModelOptions]="{ standalone: true }" [disabled]="!isEditable"></sqx-toggle>
</div>
<span class="me-2" *ngIf="!isEnabled">
{{ 'common.disabled' | sqxTranslate }}
</span>
<sqx-toggle [(ngModel)]="isEnabled" [ngModelOptions]="{ standalone: true }" [disabled]="!isEditable"></sqx-toggle>
</div>
</ng-template>
<button type="button" class="btn btn-primary me-2" (click)="save()">
<button type="button" class="btn btn-primary ms-2" (click)="save()">
{{ 'common.save' | sqxTranslate }}
</button>
</ng-container>

8
frontend/app/features/rules/pages/rule/rule-page.component.ts

@ -29,6 +29,10 @@ export class RulePageComponent extends ResourceOwner implements OnInit {
public isEnabled = false;
public isEditable = false;
public get isManual() {
return this.rule?.triggerType === 'Manual';
}
public get actionElement() {
return this.supportedActions[this.currentAction?.type || ''];
}
@ -109,6 +113,10 @@ export class RulePageComponent extends ResourceOwner implements OnInit {
this.currentTrigger = undefined;
}
public trigger() {
this.rulesState.trigger(this.rule!);
}
public save() {
if (!this.isEditable || !this.currentAction || !this.currentTrigger) {
return;

2
frontend/app/features/rules/pages/rules/rules-page.component.html

@ -6,7 +6,7 @@
<i class="icon-reset"></i> {{ 'common.refresh' | sqxTranslate }}
</button>
<a class="btn btn-success me-2" routerLink="new" title="i18n:rules.createTooltip" shortcut="CTRL + U" *ngIf="rulesState.canCreate | async">
<a class="btn btn-success ms-2" routerLink="new" title="i18n:rules.createTooltip" shortcut="CTRL + U" *ngIf="rulesState.canCreate | async">
<i class="icon-plus"></i> {{ 'rules.create' | sqxTranslate }}
</a>
</ng-container>

3
frontend/app/features/rules/shared/actions/generic-action.component.html

@ -14,6 +14,9 @@
<ng-container *ngSwitchCase="'TextArea'">
<sqx-formattable-input [formControlName]="property.name" type="Code" [formattable]="property.isFormattable"></sqx-formattable-input>
</ng-container>
<ng-container *ngSwitchCase="'Javascript'">
<sqx-code-editor [formControlName]="property.name" [height]="350"></sqx-code-editor>
</ng-container>
<ng-container *ngSwitchCase="'Checkbox'">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{property.name}}" [formControlName]="property.name">

8
frontend/app/theme/_lists.scss

@ -67,13 +67,15 @@
vertical-align: middle;
&:first-child {
border-bottom-left-radius: $border-radius;
border-left: 1px solid $color-border;
border-radius: $border-radius 0 0 $border-radius;
border-top-left-radius: $border-radius;
}
&:last-child {
border-radius: 0 $border-radius $border-radius 0;
border-bottom-right-radius: $border-radius;
border-right: 1px solid $color-border;
border-top-right-radius: $border-radius;
}
}
@ -173,7 +175,7 @@
tbody {
&:last-child {
.spacer {
display: none
display: none;
}
}
}

Loading…
Cancel
Save