diff --git a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs index cef0f0aba..c6952b152 100644 --- a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs +++ b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs @@ -22,14 +22,20 @@ namespace Squidex.Extensions.Actions.Algolia { [Required] [Display(Name = "Application Id", Description = "The application ID.")] + [DataType(DataType.Text)] + [Formattable] public string AppId { get; set; } [Required] [Display(Name = "Api Key", Description = "The API key to grant access to Squidex.")] + [DataType(DataType.Text)] + [Formattable] public string ApiKey { get; set; } [Required] [Display(Name = "Index Name", Description = "The name of the index.")] + [DataType(DataType.Text)] + [Formattable] public string IndexName { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs index 2acc7ec7c..45f176d9f 100644 --- a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs +++ b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs @@ -25,10 +25,14 @@ namespace Squidex.Extensions.Actions.AzureQueue { [Required] [Display(Name = "Connection String", Description = "The connection string to the storage account.")] + [DataType(DataType.Text)] + [Formattable] public string ConnectionString { get; set; } [Required] [Display(Name = "Queue", Description = "The name of the queue.")] + [DataType(DataType.Text)] + [Formattable] public string Queue { get; set; } protected override IEnumerable CustomValidate() diff --git a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs index a73f1f493..97f6b6538 100644 --- a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs +++ b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs @@ -24,28 +24,37 @@ namespace Squidex.Extensions.Actions.Discourse { [AbsoluteUrl] [Required] - [Display(Name = "Url", Description = "he url to the discourse server.")] + [Display(Name = "Url", Description = "The url to the discourse server.")] + [DataType(DataType.Url)] public Uri Url { get; set; } [Required] [Display(Name = "Api Key", Description = "The api key to authenticate to your discourse server.")] + [DataType(DataType.Text)] public string ApiKey { get; set; } [Required] [Display(Name = "Api Username", Description = "The api username to authenticate to your discourse server.")] + [DataType(DataType.Text)] public string ApiUsername { get; set; } [Required] [Display(Name = "Text", Description = "The text as markdown.")] + [DataType(DataType.MultilineText)] + [Formattable] public string Text { get; set; } [Display(Name = "Title", Description = "The optional title when creating new topics.")] + [DataType(DataType.Text)] + [Formattable] public string Title { get; set; } [Display(Name = "Topic", Description = "The optional topic id.")] + [DataType(DataType.Custom)] public int? Topic { get; set; } [Display(Name = "Category", Description = "The optional category id.")] + [DataType(DataType.Custom)] public int? Category { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs index 2bf262e82..44f0ce8c2 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs @@ -25,20 +25,29 @@ namespace Squidex.Extensions.Actions.ElasticSearch [AbsoluteUrl] [Required] [Display(Name = "Host", Description = "The hostname of the elastic search instance or cluster.")] + [DataType(DataType.Url)] public Uri Host { get; set; } [Required] [Display(Name = "Index Name", Description = "The name of the index.")] + [DataType(DataType.Text)] + [Formattable] public string IndexName { get; set; } [Required] [Display(Name = "Index Type", Description = "The name of the index type.")] + [DataType(DataType.Text)] + [Formattable] public string IndexType { get; set; } [Display(Name = "Username", Description = "The optional username.")] + [DataType(DataType.Text)] + [Formattable] public string Username { get; set; } [Display(Name = "Password", Description = "The optional password.")] + [DataType(DataType.Password)] + [Formattable] public string Password { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs index 067e5fbce..be24d3166 100644 --- a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs +++ b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs @@ -22,38 +22,51 @@ namespace Squidex.Extensions.Actions.Email { [Required] [Display(Name = "ServerHost", Description = "The IP address or host to the SMTP server.")] + [DataType(DataType.Text)] public string ServerHost { get; set; } [Required] [Display(Name = "ServerPort", Description = "The port to the SMTP server.")] + [DataType(DataType.Custom)] public int ServerPort { get; set; } [Required] [Display(Name = "ServerUseSsl", Description = "Specify whether the SMPT client uses Secure Sockets Layer (SSL) to encrypt the connection.")] + [DataType(DataType.Custom)] public bool ServerUseSsl { get; set; } [Required] [Display(Name = "ServerUsername", Description = "The username for the SMTP server.")] + [DataType(DataType.Text)] public string ServerUsername { get; set; } [Required] [Display(Name = "ServerPassword", Description = "The password for the SMTP server.")] + [DataType(DataType.Password)] public string ServerPassword { get; set; } [Required] [Display(Name = "MessageFrom", Description = "The email sending address.")] + [DataType(DataType.Text)] + [Formattable] public string MessageFrom { get; set; } [Required] [Display(Name = "MessageTo", Description = "The email message will be sent to.")] + [DataType(DataType.Text)] + [Formattable] public string MessageTo { get; set; } [Required] [Display(Name = "MessageSubject", Description = "The subject line for this email message.")] + [DataType(DataType.Text)] + [Formattable] public string MessageSubject { get; set; } [Required] [Display(Name = "MessageBody", Description = "The message body.")] + [DataType(DataType.MultilineText)] + [Formattable] public string MessageBody { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs b/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs index d1840782d..e4e056ba7 100644 --- a/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs +++ b/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs @@ -22,10 +22,12 @@ namespace Squidex.Extensions.Actions.Fastly { [Required] [Display(Name = "Api Key", Description = "The API key to grant access to Squidex.")] + [DataType(DataType.Text)] public string ApiKey { get; set; } [Required] [Display(Name = "Service Id", Description = "The ID of the fastly service.")] + [DataType(DataType.Text)] public string ServiceId { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs index 6bbf1bb30..65eddcefd 100644 --- a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs +++ b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs @@ -22,26 +22,36 @@ namespace Squidex.Extensions.Actions.Medium { [Required] [Display(Name = "Access Token", Description = "The self issued access token.")] + [DataType(DataType.Text)] public string AccessToken { get; set; } [Required] [Display(Name = "Title", Description = "The title, used for the url.")] + [DataType(DataType.Text)] + [Formattable] public string Title { get; set; } [Required] [Display(Name = "Content", Description = "The content, either html or markdown.")] + [DataType(DataType.MultilineText)] + [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)] + [Formattable] public string CanonicalUrl { get; set; } [Display(Name = "PublicationId", Description = "Optional publication id.")] + [DataType(DataType.Text)] public string PublicationId { get; set; } [Display(Name = "Tags", Description = "The optional comma separated list of tags.")] + [DataType(DataType.Text)] public string Tags { get; set; } [Display(Name = "Is Html", Description = "Indicates whether the content is markdown or html.")] + [DataType(DataType.Custom)] public bool IsHtml { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs index 4672a8d61..0978a79a4 100644 --- a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs +++ b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs @@ -22,10 +22,13 @@ namespace Squidex.Extensions.Actions.Prerender { [Required] [Display(Name = "Token", Description = "The prerender token from your account.")] + [DataType(DataType.Text)] + [Formattable] public string Token { get; set; } [Required] [Display(Name = "Url", Description = "The url to recache.")] + [DataType(DataType.Text)] public string Url { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs index 4e2863f8a..e85c19f32 100644 --- a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs +++ b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs @@ -25,10 +25,14 @@ namespace Squidex.Extensions.Actions.Slack [AbsoluteUrl] [Required] [Display(Name = "Webhook Url", Description = "The slack webhook url.")] + [DataType(DataType.Text)] + [Formattable] public Uri WebhookUrl { get; set; } [Required] [Display(Name = "Text", Description = "The text that is sent as message to slack.")] + [DataType(DataType.MultilineText)] + [Formattable] public string Text { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs index cc0fa3d17..c23b3dd98 100644 --- a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs +++ b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs @@ -22,14 +22,17 @@ namespace Squidex.Extensions.Actions.Twitter { [Required] [Display(Name = "Access Token", Description = " The generated access token.")] + [DataType(DataType.Text)] public string AccessToken { get; set; } [Required] [Display(Name = "Access Secret", Description = " The generated access secret.")] + [DataType(DataType.Text)] public string AccessSecret { get; set; } [Required] [Display(Name = "Text", Description = "The text that is sent as tweet to twitter.")] + [DataType(DataType.MultilineText)] public string Text { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs index 0877c37c9..cdd50c429 100644 --- a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs +++ b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs @@ -24,10 +24,13 @@ namespace Squidex.Extensions.Actions.Webhook { [AbsoluteUrl] [Required] - [Display(Name = "Url", Description = "he url to the webhook.")] + [Display(Name = "Url", Description = "The url to the webhook.")] + [DataType(DataType.Text)] + [Formattable] public Uri Url { get; set; } [Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the signature.")] + [DataType(DataType.Text)] public string SharedSecret { get; set; } } } diff --git a/extensions/Squidex.Extensions/SquidexExtensions.cs b/extensions/Squidex.Extensions/SquidexExtensions.cs new file mode 100644 index 000000000..9803ae4ce --- /dev/null +++ b/extensions/Squidex.Extensions/SquidexExtensions.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Reflection; + +namespace Squidex.Extensions +{ + public static class SquidexExtensions + { + public static readonly Assembly Assembly = typeof(SquidexExtensions).Assembly; + } +} diff --git a/src/Squidex/Config/Web/WebServices.cs b/src/Squidex/Config/Web/WebServices.cs index 19c375aa0..315f01547 100644 --- a/src/Squidex/Config/Web/WebServices.cs +++ b/src/Squidex/Config/Web/WebServices.cs @@ -6,17 +6,19 @@ // ========================================================================== using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Config.Domain; using Squidex.Infrastructure.DependencyInjection; using Squidex.Pipeline; +using Squidex.Pipeline.Plugins; using Squidex.Pipeline.Robots; namespace Squidex.Config.Web { public static class WebServices { - public static void AddMyMvc(this IServiceCollection services) + public static void AddMyMvc(this IServiceCollection services, IConfiguration config) { services.AddSingletonAs() .AsSelf(); @@ -47,7 +49,9 @@ namespace Squidex.Config.Web options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); - }).AddMySerializers(); + }) + .AddMySerializers() + .AddMyPlugins(config); services.AddCors(); services.AddRouting(); diff --git a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs index 94de2338e..f2397b7ed 100644 --- a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs +++ b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs @@ -8,9 +8,11 @@ using System; using System.Reflection; using McMaster.NETCore.Plugins; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Squidex.Extensions; using Squidex.Infrastructure.Plugins; namespace Squidex.Pipeline.Plugins @@ -19,14 +21,14 @@ namespace Squidex.Pipeline.Plugins { private static readonly Type[] SharedTypes = { typeof(IPlugin) }; - public static void AddPlugins(IMvcBuilder mvcBuilder, IConfiguration configuration) + public static void AddMyPlugins(this IMvcBuilder mvcBuilder, IConfiguration configuration) { + var pluginManager = new PluginManager(); + var options = configuration.Get(); if (options.Plugins != null) { - var pluginManager = new PluginManager(); - foreach (var pluginPath in options.Plugins) { PluginLoader plugin = null; @@ -58,9 +60,19 @@ namespace Squidex.Pipeline.Plugins pluginManager.Add(pluginAssembly); } } - - mvcBuilder.Services.AddSingleton(pluginManager); } + + pluginManager.Add(SquidexExtensions.Assembly); + pluginManager.ConfigureServices(mvcBuilder.Services, configuration); + + mvcBuilder.Services.AddSingleton(pluginManager); + } + + public static void UsePlugins(this IApplicationBuilder app) + { + var pluginManager = app.ApplicationServices.GetRequiredService(); + + pluginManager.Configure(app); } private static void AddParts(IMvcBuilder mvcBuilder, Assembly assembly) diff --git a/src/Squidex/WebStartup.cs b/src/Squidex/WebStartup.cs index 61dd0a38d..486c5ea25 100644 --- a/src/Squidex/WebStartup.cs +++ b/src/Squidex/WebStartup.cs @@ -31,6 +31,7 @@ using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.Translations; using Squidex.Pipeline; +using Squidex.Pipeline.Plugins; using Squidex.Pipeline.Robots; namespace Squidex @@ -62,7 +63,7 @@ namespace Squidex services.AddMyInfrastructureServices(config); services.AddMyLoggingServices(config); services.AddMyMigrationServices(); - services.AddMyMvc(); + services.AddMyMvc(config); services.AddMyRuleServices(); services.AddMySerializers(); services.AddMyStoreServices(config); @@ -124,6 +125,8 @@ namespace Squidex app.ConfigureOrleansDashboard(); app.ConfigureIdentityServer(); app.ConfigureFrontend(); + + app.UsePlugins(); } } } diff --git a/src/Squidex/app/features/rules/pages/rules/rule-element.component.html b/src/Squidex/app/features/rules/pages/rules/rule-element.component.html index 6ad970abc..977302dab 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-element.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rule-element.component.html @@ -1,7 +1,11 @@
- + + + + +
@@ -14,7 +18,11 @@
-
+ + + + +
diff --git a/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss b/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss index fca4de50b..b22df1cd7 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss +++ b/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss @@ -27,6 +27,10 @@ height: 3rem; } + .icon { + font-size: 20px; + } + .svg-icon { width: 20px; } @@ -56,6 +60,7 @@ } &-icon { + color: $color-dark-foreground; display: inline-block; margin-right: .5rem; position: relative; @@ -63,6 +68,10 @@ line-height: 1px; } + .icon { + font-size: 30px; + } + .svg-icon { width: 30px; } diff --git a/src/Squidex/app/features/rules/pages/rules/rules-page.component.ts b/src/Squidex/app/features/rules/pages/rules/rules-page.component.ts index 849f26c94..3c21249db 100644 --- a/src/Squidex/app/features/rules/pages/rules/rules-page.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/rules-page.component.ts @@ -9,6 +9,7 @@ import { Component, OnInit } from '@angular/core'; import { onErrorResumeNext } from 'rxjs/operators'; import { + ALL_TRIGGERS, AppsState, DialogModel, RuleDto, @@ -30,7 +31,7 @@ export class RulesPageComponent implements OnInit { public wizardRule: RuleDto | null; public ruleActions: { [name: string]: RuleElementDto }; - public ruleTriggers: { [name: string]: RuleElementDto }; + public ruleTriggers = ALL_TRIGGERS; constructor( public readonly appsState: AppsState, @@ -48,11 +49,6 @@ export class RulesPageComponent implements OnInit { this.ruleActions = actions; }); - this.rulesService.getTriggers() - .subscribe(triggers => { - this.ruleTriggers = triggers; - }); - this.schemasState.load().pipe(onErrorResumeNext()).subscribe(); } diff --git a/src/Squidex/app/shared/services/rules.service.spec.ts b/src/Squidex/app/shared/services/rules.service.spec.ts index f765ab29d..c5d6fe99b 100644 --- a/src/Squidex/app/shared/services/rules.service.spec.ts +++ b/src/Squidex/app/shared/services/rules.service.spec.ts @@ -77,45 +77,8 @@ describe('RulesService', () => { }); expect(actions!).toEqual({ - 'action1': new RuleElementDto('display1', 'description1', '#111', '', 'link1'), - 'action2': new RuleElementDto('display2', 'description2', '#222', '', 'link2') - }); - })); - - it('should make get request to get triggers', - inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { - - let triggers: { [ name: string ]: RuleElementDto }; - - rulesService.getTriggers().subscribe(result => { - triggers = result; - }); - - const req = httpMock.expectOne('http://service/p/api/rules/triggers'); - - expect(req.request.method).toEqual('GET'); - expect(req.request.headers.get('If-Match')).toBeNull(); - - req.flush({ - 'trigger2': { - display: 'display2', - description: 'description2', - iconColor: '#222', - iconImage: '', - readMore: 'link2' - }, - 'trigger1': { - display: 'display1', - description: 'description1', - iconColor: '#111', - iconImage: '', - readMore: 'link1' - } - }); - - expect(triggers!).toEqual({ - 'trigger1': new RuleElementDto('display1', 'description1', '#111', '', 'link1'), - 'trigger2': new RuleElementDto('display2', 'description2', '#222', '', 'link2') + 'action1': new RuleElementDto('display1', 'description1', '#111', '', null, 'link1'), + 'action2': new RuleElementDto('display2', 'description2', '#222', '', null, 'link2') }); })); diff --git a/src/Squidex/app/shared/services/rules.service.ts b/src/Squidex/app/shared/services/rules.service.ts index 6f395ef87..cb2eae816 100644 --- a/src/Squidex/app/shared/services/rules.service.ts +++ b/src/Squidex/app/shared/services/rules.service.ts @@ -21,12 +21,39 @@ import { Versioned } from '@app/framework'; +export const ALL_TRIGGERS = { + 'ContentChanged': { + description: 'For content changes like created, updated, published, unpublished...', + display: 'Content changed', + iconColor: '#3389ff', + iconCode: 'contents' + }, + 'AssetChanged': { + description: 'For asset changes like uploaded, updated (reuploaded), renamed, deleted...', + display: 'Asset changed', + iconColor: '#3389ff', + iconCode: 'assets' + }, + 'SchemaChanged': { + description: 'When a schema definition has been created, updated, published or deleted...', + display: 'Schema changed', + iconColor: '#3389ff', + iconCode: 'schemas'}, + 'Usage': { + description: 'When monthly API calls exceed a specified limit for one time a month...', + display: 'Usage exceeded', + iconColor: '#3389ff', + iconCode: 'dashboard' + } +}; + export class RuleElementDto { constructor( public readonly display: string, public readonly description: string, public readonly iconColor: string, public readonly iconImage: string, + public readonly iconCode: string | null, public readonly readMore: string ) { } @@ -120,27 +147,7 @@ export class RulesService { for (let key of Object.keys(items).sort()) { const value = items[key]; - result[key] = new RuleElementDto(value.display, value.description, value.iconColor, value.iconImage, value.readMore); - } - - return result; - }), - pretifyError('Failed to load Rules. Please reload.')); - } - - public getTriggers(): Observable<{ [name: string]: RuleElementDto }> { - const url = this.apiUrl.buildUrl('api/rules/triggers'); - - return HTTP.getVersioned(this.http, url).pipe( - map(response => { - const items: { [name: string]: any } = response.payload.body; - - const result: { [name: string]: RuleElementDto } = {}; - - for (let key of Object.keys(items).sort()) { - const value = items[key]; - - result[key] = new RuleElementDto(value.display, value.description, value.iconColor, value.iconImage, value.readMore); + result[key] = new RuleElementDto(value.display, value.description, value.iconColor, value.iconImage, null, value.readMore); } return result;