diff --git a/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs b/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs index a45752c60..5af9b4e4b 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs b/backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs index af80dec42..a6e4f3b13 100644 --- a/backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs +++ b/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; } diff --git a/backend/extensions/Squidex.Extensions/Actions/Comment/CommentAction.cs b/backend/extensions/Squidex.Extensions/Actions/Comment/CommentAction.cs index e24bfc7a5..599bc8a51 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Comment/CommentAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentAction.cs b/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentAction.cs index f2a1fc41e..9d7cfa6d8 100644 --- a/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs b/backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs index 79a7c90bf..28ef2ac0f 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs b/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs index deca80d1a..d576c842a 100644 --- a/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs b/backend/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs index 431a53d3c..5274c0a76 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs +++ b/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; } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs b/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs index 979bd2154..3743f9613 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaAction.cs b/backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaAction.cs index 52752dfe9..59b0b4f0f 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs b/backend/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs index 6e6fd09c1..12658840c 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs index 5201584f1..9fed53b13 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs b/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs index 171a6c817..287b7c386 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs +++ b/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; } } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Script/ScriptAction.cs b/backend/extensions/Squidex.Extensions/Actions/Script/ScriptAction.cs new file mode 100644 index 000000000..68640c58e --- /dev/null +++ b/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 = "", + 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; } + } +} diff --git a/backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs new file mode 100644 index 000000000..f91c071d6 --- /dev/null +++ b/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 + { + 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 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; } + } +} diff --git a/backend/extensions/Squidex.Extensions/Actions/Script/ScriptPlugin.cs b/backend/extensions/Squidex.Extensions/Actions/Script/ScriptPlugin.cs new file mode 100644 index 000000000..6ea680e28 --- /dev/null +++ b/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(); + } + } +} diff --git a/backend/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs b/backend/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs index bc0b2f12f..0384290b7 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs +++ b/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; } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs b/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs index 98b784da2..7aa967149 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs +++ b/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; } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs index 7916defce..71b690d2a 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs +++ b/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; } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EditorAttribute.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EditorAttribute.cs new file mode 100644 index 000000000..687437b96 --- /dev/null +++ b/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; + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs index 61793f8bc..e360d603b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs +++ b/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; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleFieldEditor.cs similarity index 91% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs rename to backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleFieldEditor.cs index 0371def5f..92f38840e 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs +++ b/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, diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs index e83d8809d..c676d6ee0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs +++ b/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(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()?.Editor ?? RuleFieldEditor.Text; + } + private static bool IsNullable(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs index 529540b1a..b4ef3188e 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs @@ -16,7 +16,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models /// The html editor. /// [LocalizedRequired] - public RuleActionPropertyEditor Editor { get; set; } + public RuleFieldEditor Editor { get; set; } /// /// The name of the editor. diff --git a/backend/src/Squidex/web.config b/backend/src/Squidex/web.config new file mode 100644 index 000000000..30a037d95 --- /dev/null +++ b/backend/src/Squidex/web.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistryTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistryTests.cs index 9a5011f8a..ec656b163 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistryTests.cs +++ b/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 = "", - 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(() => sut.Add()); - } } } diff --git a/frontend/app/features/rules/pages/events/rule-event.component.html b/frontend/app/features/rules/pages/events/rule-event.component.html index 924daf2ee..acb4e2f61 100644 --- a/frontend/app/features/rules/pages/events/rule-event.component.html +++ b/frontend/app/features/rules/pages/events/rule-event.component.html @@ -1,4 +1,4 @@ - + {{event.jobResult}} @@ -18,7 +18,7 @@ - +

{{ 'rules.ruleEvents.lastInvokedLabel' | sqxTranslate }}

diff --git a/frontend/app/features/rules/pages/events/rule-event.component.scss b/frontend/app/features/rules/pages/events/rule-event.component.scss index fbf08624c..79e8dd057 100644 --- a/frontend/app/features/rules/pages/events/rule-event.component.scss +++ b/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; } } diff --git a/frontend/app/features/rules/pages/rule/rule-page.component.html b/frontend/app/features/rules/pages/rule/rule-page.component.html index 146520fd7..5f65f7722 100644 --- a/frontend/app/features/rules/pages/rule/rule-page.component.html +++ b/frontend/app/features/rules/pages/rule/rule-page.component.html @@ -14,19 +14,31 @@ -
- - {{ 'common.enabled' | sqxTranslate }} - - - - {{ 'common.disabled' | sqxTranslate }} - + + + + + +
+ + {{ 'common.enabled' | sqxTranslate }} + - -
+ + {{ 'common.disabled' | sqxTranslate }} + + + +
+ -
diff --git a/frontend/app/features/rules/pages/rule/rule-page.component.ts b/frontend/app/features/rules/pages/rule/rule-page.component.ts index df618597e..f76022b6c 100644 --- a/frontend/app/features/rules/pages/rule/rule-page.component.ts +++ b/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; diff --git a/frontend/app/features/rules/pages/rules/rules-page.component.html b/frontend/app/features/rules/pages/rules/rules-page.component.html index 5796e3539..c68fd441c 100644 --- a/frontend/app/features/rules/pages/rules/rules-page.component.html +++ b/frontend/app/features/rules/pages/rules/rules-page.component.html @@ -6,7 +6,7 @@ {{ 'common.refresh' | sqxTranslate }} - + {{ 'rules.create' | sqxTranslate }} diff --git a/frontend/app/features/rules/shared/actions/generic-action.component.html b/frontend/app/features/rules/shared/actions/generic-action.component.html index 968c846fe..8f7a31cb2 100644 --- a/frontend/app/features/rules/shared/actions/generic-action.component.html +++ b/frontend/app/features/rules/shared/actions/generic-action.component.html @@ -14,6 +14,9 @@ + + +
diff --git a/frontend/app/theme/_lists.scss b/frontend/app/theme/_lists.scss index 740e14bea..fa8bfd1ca 100644 --- a/frontend/app/theme/_lists.scss +++ b/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; } } }