Merge branch 'feature/rtl-interface-direction' of https://github.com/ONLYOFFICE/DocSpace into feature/rtl-interface-direction

This commit is contained in:
MrSubhonbek 2023-06-09 14:15:31 +03:00
commit 1299c93e55
141 changed files with 7177 additions and 1175 deletions

View File

@ -71,7 +71,7 @@ public static class DbIPLookupExtension
.IsRequired()
.HasColumnName("addr_type")
.HasColumnType("enum('ipv4','ipv6')");
entity.Property(e => e.IPStart)
.IsRequired()
.HasColumnName("ip_start")
@ -150,6 +150,91 @@ public static class DbIPLookupExtension
}
public static void PgSqlAddDbIPLookup(this ModelBuilder modelBuilder)
{
throw new NotImplementedException();
modelBuilder.Entity<DbIPLookup>(entity =>
{
entity.ToTable("dbip_lookup")
.HasCharSet("utf8mb4");
entity.HasKey(nameof(DbIPLookup.AddrType), nameof(DbIPLookup.IPStart));
entity.Property(e => e.AddrType)
.IsRequired()
.HasColumnName("addr_type")
.HasColumnType("enum('ipv4','ipv6')");
entity.Property(e => e.IPStart)
.IsRequired()
.HasColumnName("ip_start")
.HasColumnType("varbinary(16)");
entity.Property(e => e.IPEnd)
.IsRequired()
.HasColumnName("ip_end")
.HasColumnType("varbinary(16)");
entity.Property(e => e.Continent)
.IsRequired()
.HasColumnName("continent")
.HasColumnType("char(2)");
entity.Property(e => e.Country)
.IsRequired()
.HasColumnName("country")
.HasColumnType("char(2)");
entity.Property(e => e.StateProvCode)
.HasColumnName("stateprov_code")
.HasColumnType("varchar(15)");
entity.Property(e => e.StateProv)
.IsRequired()
.HasColumnName("stateprov")
.HasColumnType("varchar(80)");
entity.Property(e => e.District)
.IsRequired()
.HasColumnName("district")
.HasColumnType("varchar(80)");
entity.Property(e => e.City)
.IsRequired()
.HasColumnName("city")
.HasColumnType("varchar(80)");
entity.Property(e => e.ZipCode)
.HasColumnName("zipcode")
.HasColumnType("varchar(20)");
entity.Property(e => e.Latitude)
.IsRequired()
.HasColumnName("latitude")
.HasColumnType("float");
entity.Property(e => e.Longitude)
.IsRequired()
.HasColumnName("longitude")
.HasColumnType("float");
entity.Property(e => e.GeonameId)
.IsRequired(false)
.HasColumnName("geoname_id")
.HasColumnType("int(10)");
entity.Property(e => e.TimezoneOffset)
.IsRequired()
.HasColumnType("float")
.HasColumnName("timezone_offset");
entity.Property(e => e.TimezoneName)
.IsRequired()
.HasColumnName("timezone_name")
.HasColumnType("varchar(64)");
entity.Property(e => e.WeatherCode)
.IsRequired()
.HasColumnName("weather_code")
.HasColumnType("varchar(10)");
});
}
}

View File

@ -26,7 +26,7 @@
using AutoMapper;
namespace ASC.Webhooks.Core;
namespace ASC.Webhooks.Core;
[Scope]
public class DbWorker
@ -63,12 +63,12 @@ public class DbWorker
_mapper = mapper;
}
public async Task<WebhooksConfig> AddWebhookConfig(string uri, string secretKey)
public async Task<WebhooksConfig> AddWebhookConfig(string uri, string name, string secretKey, bool? enabled, bool? ssl)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var objForCreate = await webhooksDbContext.WebhooksConfigs
.Where(it => it.TenantId == Tenant && it.Uri == uri)
.Where(it => it.TenantId == Tenant && it.Uri == uri && it.Name == name)
.FirstOrDefaultAsync();
if (objForCreate != null)
@ -80,22 +80,32 @@ public class DbWorker
{
TenantId = Tenant,
Uri = uri,
SecretKey = secretKey
SecretKey = secretKey,
Name = name,
Enabled = enabled ?? true,
SSL = ssl ?? true
};
toAdd = await webhooksDbContext.AddOrUpdateAsync(r => r.WebhooksConfigs, toAdd);
await webhooksDbContext.SaveChangesAsync();
return toAdd;
}
public async IAsyncEnumerable<WebhooksConfig> GetTenantWebhooks()
}
public async IAsyncEnumerable<WebhooksConfigWithStatus> GetTenantWebhooksWithStatus()
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var q = webhooksDbContext.WebhooksConfigs
.AsNoTracking()
.Where(it => it.TenantId == Tenant)
.GroupJoin(webhooksDbContext.WebhooksLogs, c => c.Id, l => l.ConfigId, (configs, logs) => new { configs, logs })
.Select(it =>
new WebhooksConfigWithStatus
{
WebhooksConfig = it.configs,
Status = it.logs.OrderBy(it => it.Delivery).LastOrDefault().Status
})
.AsAsyncEnumerable();
await foreach (var webhook in q)
@ -113,7 +123,7 @@ public class DbWorker
.AsAsyncEnumerable();
}
public async Task<WebhooksConfig> UpdateWebhookConfig(int id, string uri, string key, bool? enabled)
public async Task<WebhooksConfig> UpdateWebhookConfig(int id, string name, string uri, string key, bool? enabled, bool? ssl)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
@ -123,6 +133,11 @@ public class DbWorker
if (updateObj != null)
{
if (!string.IsNullOrEmpty(name))
{
updateObj.Name = name;
}
if (!string.IsNullOrEmpty(uri))
{
updateObj.Uri = uri;
@ -138,6 +153,11 @@ public class DbWorker
updateObj.Enabled = enabled.Value;
}
if (ssl.HasValue)
{
updateObj.SSL = ssl.Value;
}
webhooksDbContext.WebhooksConfigs.Update(updateObj);
await webhooksDbContext.SaveChangesAsync();
}
@ -164,29 +184,9 @@ public class DbWorker
return removeObj;
}
public IAsyncEnumerable<WebhooksLog> ReadJournal(int startIndex, int limit, DateTime? delivery, string hookUri, int hookId)
{
var webhooksDbContext = _dbContextFactory.CreateDbContext();
var q = webhooksDbContext.WebhooksLogs
.AsNoTracking()
.Where(r => r.TenantId == Tenant);
if (delivery.HasValue)
{
var date = delivery.Value;
q = q.Where(r => r.Delivery == date);
}
if (!string.IsNullOrEmpty(hookUri))
{
q = q.Where(r => r.Config.Uri == hookUri);
}
if (hookId != 0)
{
q = q.Where(r => r.WebhookId == hookId);
}
public IAsyncEnumerable<DbWebhooks> ReadJournal(int startIndex, int limit, DateTime? deliveryFrom, DateTime? deliveryTo, string hookUri, int? hookId, int? configId, int? eventId, WebhookGroupStatus? webhookGroupStatus)
{
var q = GetQueryForJournal(deliveryFrom, deliveryTo, hookUri, hookId, configId, eventId, webhookGroupStatus);
if (startIndex != 0)
{
@ -198,17 +198,30 @@ public class DbWorker
q = q.Take(limit);
}
return q.OrderByDescending(t => t.Id).AsAsyncEnumerable();
return q.AsAsyncEnumerable();
}
public async Task<int> GetTotalByQuery(DateTime? deliveryFrom, DateTime? deliveryTo, string hookUri, int? hookId, int? configId, int? eventId, WebhookGroupStatus? webhookGroupStatus)
{
return await GetQueryForJournal(deliveryFrom, deliveryTo, hookUri, hookId, configId, eventId, webhookGroupStatus).CountAsync();
}
public async Task<WebhooksLog> ReadJournal(int id)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
return await webhooksDbContext.WebhooksLogs
var fromDb = await webhooksDbContext.WebhooksLogs
.AsNoTracking()
.Where(it => it.Id == id)
.FirstOrDefaultAsync();
.Join(webhooksDbContext.WebhooksConfigs, r => r.ConfigId, r => r.Id, (log, config) => new DbWebhooks { Log = log, Config = config })
.FirstOrDefaultAsync();
if (fromDb != null)
{
fromDb.Log.Config = fromDb.Config;
}
return fromDb.Log;
}
public async Task<WebhooksLog> WriteToJournal(WebhooksLog webhook)
@ -228,15 +241,21 @@ public class DbWorker
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var webhook = await webhooksDbContext.WebhooksLogs.Where(t => t.Id == id).FirstOrDefaultAsync();
webhook.Status = status;
webhook.RequestHeaders = requestHeaders;
webhook.ResponsePayload = responsePayload;
webhook.ResponseHeaders = responseHeaders;
webhook.Delivery = delivery;
webhooksDbContext.WebhooksLogs.Update(webhook);
await webhooksDbContext.SaveChangesAsync();
var webhook = await webhooksDbContext.WebhooksLogs
.Where(t => t.Id == id)
.FirstOrDefaultAsync();
if (webhook != null)
{
webhook.Status = status;
webhook.RequestHeaders = requestHeaders;
webhook.ResponsePayload = responsePayload;
webhook.ResponseHeaders = responseHeaders;
webhook.Delivery = delivery;
webhooksDbContext.WebhooksLogs.Update(webhook);
await webhooksDbContext.SaveChangesAsync();
}
return webhook;
}
@ -288,5 +307,90 @@ public class DbWorker
.FirstOrDefaultAsync();
return _mapper.Map<DbWebhook, Webhook>(webHook);
}
private IQueryable<DbWebhooks> GetQueryForJournal(DateTime? deliveryFrom, DateTime? deliveryTo, string hookUri, int? hookId, int? configId, int? eventId, WebhookGroupStatus? webhookGroupStatus)
{
var webhooksDbContext = _dbContextFactory.CreateDbContext();
var q = webhooksDbContext.WebhooksLogs
.AsNoTracking()
.OrderByDescending(t => t.Id)
.Where(r => r.TenantId == Tenant)
.Join(webhooksDbContext.WebhooksConfigs.AsNoTracking(), r => r.ConfigId, r => r.Id, (log, config) => new DbWebhooks { Log = log, Config = config });
if (deliveryFrom.HasValue)
{
var from = deliveryFrom.Value;
q = q.Where(r => r.Log.Delivery >= from);
}
if (deliveryTo.HasValue)
{
var to = deliveryTo.Value;
q = q.Where(r => r.Log.Delivery <= to);
}
if (!string.IsNullOrEmpty(hookUri))
{
q = q.Where(r => r.Config.Uri == hookUri);
}
if (hookId != null)
{
q = q.Where(r => r.Log.WebhookId == hookId);
}
if (configId != null)
{
q = q.Where(r => r.Log.ConfigId == configId);
}
if (eventId != null)
{
q = q.Where(r => r.Log.Id == eventId);
}
if (webhookGroupStatus != null && webhookGroupStatus != WebhookGroupStatus.None)
{
if ((webhookGroupStatus & WebhookGroupStatus.NotSent) != WebhookGroupStatus.NotSent)
{
q = q.Where(r => r.Log.Status != 0);
}
if ((webhookGroupStatus & WebhookGroupStatus.Status2xx) != WebhookGroupStatus.Status2xx)
{
q = q.Where(r => r.Log.Status < 200 || r.Log.Status >= 300);
}
if ((webhookGroupStatus & WebhookGroupStatus.Status3xx) != WebhookGroupStatus.Status3xx)
{
q = q.Where(r => r.Log.Status < 300 || r.Log.Status >= 400);
}
if ((webhookGroupStatus & WebhookGroupStatus.Status4xx) != WebhookGroupStatus.Status4xx)
{
q = q.Where(r => r.Log.Status < 400 || r.Log.Status >= 500);
}
if ((webhookGroupStatus & WebhookGroupStatus.Status5xx) != WebhookGroupStatus.Status5xx)
{
q = q.Where(r => r.Log.Status < 500);
}
}
return q;
}
}
public class DbWebhooks
{
public WebhooksLog Log { get; set; }
public WebhooksConfig Config { get; set; }
}
[Flags]
public enum WebhookGroupStatus
{
None = 0,
NotSent = 1,
Status2xx = 2,
Status3xx = 4,
Status4xx = 8,
Status5xx = 16
}

View File

@ -29,10 +29,12 @@ namespace ASC.Webhooks.Core.EF.Model;
public class WebhooksConfig : BaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string SecretKey { get; set; }
public int TenantId { get; set; }
public string Uri { get; set; }
public bool Enabled { get; set; }
public bool SSL { get; set; }
public override object[] GetKeys()
{
@ -71,18 +73,30 @@ public static class WebhooksConfigExtension
.HasColumnType("int unsigned");
entity.Property(e => e.Uri)
.HasMaxLength(50)
.HasColumnName("uri")
.HasDefaultValueSql("''");
.HasDefaultValueSql("''")
.HasColumnType("text")
.HasCharSet("utf8")
.UseCollation("utf8_general_ci");
entity.Property(e => e.SecretKey)
.HasMaxLength(50)
.HasColumnName("secret_key")
.HasDefaultValueSql("''");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasColumnName("name")
.IsRequired();
entity.Property(e => e.Enabled)
.HasColumnName("enabled")
.HasDefaultValueSql("'1'")
.HasColumnType("tinyint(1)");
entity.Property(e => e.SSL)
.HasColumnName("ssl")
.HasDefaultValueSql("'1'")
.HasColumnType("tinyint(1)");
});
}
@ -108,7 +122,6 @@ public static class WebhooksConfigExtension
.HasColumnType("int unsigned");
entity.Property(e => e.Uri)
.HasMaxLength(50)
.HasColumnName("uri")
.HasDefaultValueSql("''");
@ -117,9 +130,24 @@ public static class WebhooksConfigExtension
.HasColumnName("secret_key")
.HasDefaultValueSql("''");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasColumnName("name")
.IsRequired();
entity.Property(e => e.Enabled)
.HasColumnName("enabled")
.HasDefaultValueSql("true");
entity.Property(e => e.SSL)
.HasColumnName("ssl")
.HasDefaultValueSql("true");
});
}
}
public class WebhooksConfigWithStatus
{
public WebhooksConfig WebhooksConfig { get; set; }
public int? Status { get; set; }
}

View File

@ -47,9 +47,7 @@ public class WebhooksLog
public static class WebhooksPayloadExtension
{
public static ModelBuilderWrapper AddWebhooksLog(this ModelBuilderWrapper modelBuilder)
{
modelBuilder.Entity<WebhooksLog>().Navigation(e => e.Config).AutoInclude();
{
modelBuilder
.Add(MySqlAddWebhooksLog, Provider.MySql)
.Add(PgSqlAddWebhooksLog, Provider.PostgreSql);

View File

@ -27,15 +27,16 @@
global using System.Text.Json.Serialization;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.Mapping;
global using ASC.Core;
global using ASC.Core.Common.EF;
global using ASC.Core.Common.EF.Model;
global using ASC.Core.Common.Settings;
global using ASC.Web.Webhooks;
global using ASC.EventBus.Abstractions;
global using ASC.EventBus.Events;
global using ASC.Webhooks.Core.EF.Context;
global using ASC.Webhooks.Core.EF.Model;
global using ASC.Webhooks.Core.IntegrationEvents.Events;
global using ASC.Webhooks.Core.Resources;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore;

View File

@ -24,46 +24,23 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using ProtoBuf;
namespace ASC.Webhooks;
[Scope]
public class SslHelper
namespace ASC.Webhooks.Core.IntegrationEvents.Events;
[ProtoContract]
public record WebhookRequestIntegrationEvent : IntegrationEvent
{
private readonly SettingsManager _settingsManager;
public SslHelper(
SettingsManager settingsManager
)
public WebhookRequestIntegrationEvent() : base()
{
_settingsManager = settingsManager;
}
public bool ValidateCertificate(SslPolicyErrors sslPolicyErrors)
public WebhookRequestIntegrationEvent(Guid createBy, int tenantId) :
base(createBy, tenantId)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
var settings = _settingsManager.Load<WebHooksSettings>();
if (!settings.EnableSSLVerification)
{
return true;
}
throw new SslException(sslPolicyErrors);
}
}
public class SslException : Exception
{
public SslPolicyErrors Errors { get; set; }
public override string Message { get => Errors.ToString(); }
public SslException(SslPolicyErrors errors)
{
Errors = errors;
}
[ProtoMember(1)]
public int WebhookId { get; set; }
}

View File

@ -29,8 +29,7 @@ namespace ASC.Webhooks.Core;
[Serializable]
public class WebHooksSettings : ISettings<WebHooksSettings>
{
public bool EnableSSLVerification { get; set; }
{
public List<int> Ids { get; set; }
[JsonIgnore]
@ -38,7 +37,6 @@ public class WebHooksSettings : ISettings<WebHooksSettings>
public WebHooksSettings GetDefault() => new WebHooksSettings()
{
EnableSSLVerification = true,
Ids = new List<int> { }
};
}

View File

@ -30,14 +30,20 @@ namespace ASC.Webhooks.Core;
public class WebhookPublisher : IWebhookPublisher
{
private readonly DbWorker _dbWorker;
private readonly ICacheNotify<WebhookRequest> _webhookNotify;
private readonly IEventBus _eventBus;
private readonly SecurityContext _securityContext;
private readonly TenantManager _tenantManager;
public WebhookPublisher(
DbWorker dbWorker,
ICacheNotify<WebhookRequest> webhookNotify)
IEventBus eventBus,
SecurityContext securityContext,
TenantManager tenantManager)
{
_dbWorker = dbWorker;
_webhookNotify = webhookNotify;
_eventBus = eventBus;
_securityContext = securityContext;
_tenantManager = tenantManager;
}
public async Task PublishAsync(int webhookId, string requestPayload)
@ -72,12 +78,12 @@ public class WebhookPublisher : IWebhookPublisher
var webhook = await _dbWorker.WriteToJournal(webhooksLog);
var request = new WebhookRequest
_eventBus.Publish(new WebhookRequestIntegrationEvent(
_securityContext.CurrentAccount.ID,
_tenantManager.GetCurrentTenant().Id)
{
Id = webhook.Id
};
_webhookNotify.Publish(request, CacheNotifyAction.Update);
WebhookId = webhook.Id
});
return webhook;
}

View File

@ -19,34 +19,34 @@ namespace ASC.Migrations.MySql.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Route")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("varchar(200)")
.HasColumnName("route");
b.Property<string>("Method")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("varchar(10)")
.HasColumnName("method");
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Route")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("varchar(200)")
.HasColumnName("route");
b.Property<string>("Method")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("varchar(10)")
.HasColumnName("method");
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
{

View File

@ -1,29 +1,29 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
@ -36,24 +36,24 @@ public partial class WebhooksDbContextMigrate : Migration
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "webhooks",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
route = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8"),
method = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8")
},
constraints: table =>
{
table.PrimaryKey("PRIMARY", x => x.id);
})
.Annotation("MySql:CharSet", "utf8");
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "webhooks",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
route = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8"),
method = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8")
},
constraints: table =>
{
table.PrimaryKey("PRIMARY", x => x.id);
})
.Annotation("MySql:CharSet", "utf8");
migrationBuilder.CreateTable(
name: "webhooks_config",
@ -83,7 +83,7 @@ public partial class WebhooksDbContextMigrate : Migration
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
config_id = table.Column<int>(type: "int", nullable: false),
creation_time = table.Column<DateTime>(type: "datetime", nullable: false),
creation_time = table.Column<DateTime>(type: "datetime", nullable: false),
webhook_id = table.Column<int>(type: "int", nullable: false),
request_headers = table.Column<string>(type: "json", nullable: true)
.Annotation("MySql:CharSet", "utf8"),
@ -130,7 +130,7 @@ public partial class WebhooksDbContextMigrate : Migration
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "webhooks");
name: "webhooks");
migrationBuilder.DropTable(
name: "webhooks_logs");

View File

@ -0,0 +1,194 @@
// <auto-generated />
using System;
using ASC.Webhooks.Core.EF.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace ASC.Migrations.MySql.Migrations.WebhooksDb
{
[DbContext(typeof(WebhooksDbContext))]
[Migration("20230607172539_WebhooksDbContext_Upgrade1")]
partial class WebhooksDbContextUpgrade1
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Method")
.ValueGeneratedOnAdd()
.HasMaxLength(10)
.HasColumnType("varchar(10)")
.HasColumnName("method")
.HasDefaultValueSql("''");
b.Property<string>("Route")
.ValueGeneratedOnAdd()
.HasMaxLength(200)
.HasColumnType("varchar(200)")
.HasColumnName("route")
.HasDefaultValueSql("''");
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<bool>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasColumnName("enabled")
.HasDefaultValueSql("'1'");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("name");
b.Property<bool>("SSL")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasColumnName("ssl")
.HasDefaultValueSql("'1'");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("secret_key")
.HasDefaultValueSql("''");
b.Property<uint>("TenantId")
.HasColumnType("int unsigned")
.HasColumnName("tenant_id");
b.Property<string>("Uri")
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasColumnName("uri")
.HasDefaultValueSql("''")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.HasKey("Id")
.HasName("PRIMARY");
b.HasIndex("TenantId")
.HasDatabaseName("tenant_id");
b.ToTable("webhooks_config", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<int>("ConfigId")
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime")
.HasColumnName("creation_time");
b.Property<DateTime?>("Delivery")
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
b.Property<string>("RequestPayload")
.IsRequired()
.HasColumnType("text")
.HasColumnName("request_payload")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("ResponseHeaders")
.HasColumnType("json")
.HasColumnName("response_headers");
b.Property<string>("ResponsePayload")
.HasColumnType("text")
.HasColumnName("response_payload")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<int>("Status")
.HasColumnType("int")
.HasColumnName("status");
b.Property<uint>("TenantId")
.HasColumnType("int unsigned")
.HasColumnName("tenant_id");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("varchar(36)")
.HasColumnName("uid")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<int>("WebhookId")
.HasColumnType("int")
.HasColumnName("webhook_id");
b.HasKey("Id")
.HasName("PRIMARY");
b.HasIndex("ConfigId");
b.HasIndex("TenantId")
.HasDatabaseName("tenant_id");
b.ToTable("webhooks_logs", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b =>
{
b.HasOne("ASC.Webhooks.Core.EF.Model.WebhooksConfig", "Config")
.WithMany()
.HasForeignKey("ConfigId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Config");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,157 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ASC.Migrations.MySql.Migrations.WebhooksDb
{
/// <inheritdoc />
public partial class WebhooksDbContextUpgrade1 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "uri",
table: "webhooks_config",
type: "text",
nullable: true,
defaultValueSql: "''",
collation: "utf8_general_ci",
oldClrType: typeof(string),
oldType: "varchar(50)",
oldMaxLength: 50,
oldNullable: true,
oldDefaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8")
.OldAnnotation("MySql:CharSet", "utf8");
migrationBuilder.AddColumn<bool>(
name: "ssl",
table: "webhooks_config",
type: "tinyint(1)",
nullable: false,
defaultValueSql: "'1'");
migrationBuilder.AlterColumn<string>(
name: "route",
table: "webhooks",
type: "varchar(200)",
maxLength: 200,
nullable: true,
defaultValueSql: "''",
oldClrType: typeof(string),
oldType: "varchar(50)",
oldMaxLength: 50,
oldDefaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8")
.OldAnnotation("MySql:CharSet", "utf8");
migrationBuilder.AlterColumn<string>(
name: "method",
table: "webhooks",
type: "varchar(10)",
maxLength: 10,
nullable: true,
defaultValueSql: "''",
oldClrType: typeof(string),
oldType: "varchar(10)",
oldMaxLength: 10,
oldDefaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8")
.OldAnnotation("MySql:CharSet", "utf8");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ssl",
table: "webhooks_config");
migrationBuilder.AlterColumn<string>(
name: "uri",
table: "webhooks_config",
type: "varchar(50)",
maxLength: 50,
nullable: true,
defaultValueSql: "''",
oldClrType: typeof(string),
oldType: "text",
oldNullable: true,
oldDefaultValueSql: "''",
oldCollation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8")
.OldAnnotation("MySql:CharSet", "utf8");
migrationBuilder.UpdateData(
table: "webhooks",
keyColumn: "route",
keyValue: null,
column: "route",
value: "");
migrationBuilder.AlterColumn<string>(
name: "route",
table: "webhooks",
type: "varchar(50)",
maxLength: 50,
nullable: false,
defaultValueSql: "''",
oldClrType: typeof(string),
oldType: "varchar(200)",
oldMaxLength: 200,
oldNullable: true,
oldDefaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8")
.OldAnnotation("MySql:CharSet", "utf8");
migrationBuilder.UpdateData(
table: "webhooks",
keyColumn: "method",
keyValue: null,
column: "method",
value: "");
migrationBuilder.AlterColumn<string>(
name: "method",
table: "webhooks",
type: "varchar(10)",
maxLength: 10,
nullable: false,
defaultValueSql: "''",
oldClrType: typeof(string),
oldType: "varchar(10)",
oldMaxLength: 10,
oldNullable: true,
oldDefaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8")
.OldAnnotation("MySql:CharSet", "utf8");
}
}
}

View File

@ -31,16 +31,14 @@ namespace ASC.Migrations.MySql.Migrations
.HasMaxLength(10)
.HasColumnType("varchar(10)")
.HasColumnName("method")
.HasDefaultValueSql("''")
.IsRequired();
.HasDefaultValueSql("''");
b.Property<string>("Route")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasMaxLength(200)
.HasColumnType("varchar(200)")
.HasColumnName("route")
.HasDefaultValueSql("''")
.IsRequired();
.HasDefaultValueSql("''");
b.HasKey("Id")
.HasName("PRIMARY");
@ -63,6 +61,18 @@ namespace ASC.Migrations.MySql.Migrations
.HasColumnName("enabled")
.HasDefaultValueSql("'1'");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("name");
b.Property<bool>("SSL")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasColumnName("ssl")
.HasDefaultValueSql("'1'");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
@ -76,10 +86,11 @@ namespace ASC.Migrations.MySql.Migrations
b.Property<string>("Uri")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnType("text")
.HasColumnName("uri")
.HasDefaultValueSql("''");
.HasDefaultValueSql("''")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.HasKey("Id")
.HasName("PRIMARY");
@ -111,11 +122,6 @@ namespace ASC.Migrations.MySql.Migrations
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
@ -137,10 +143,6 @@ namespace ASC.Migrations.MySql.Migrations
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<int>("ConfigId")
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<int>("Status")
.HasColumnType("int")
.HasColumnName("status");
@ -156,6 +158,10 @@ namespace ASC.Migrations.MySql.Migrations
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<int>("WebhookId")
.HasColumnType("int")
.HasColumnName("webhook_id");
b.HasKey("Id")
.HasName("PRIMARY");

View File

@ -21,34 +21,34 @@ namespace ASC.Migrations.PostgreSql.Migrations
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Route")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("route");
b.Property<string>("Method")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("method");
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Route")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("route");
b.Property<string>("Method")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("method");
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
{

View File

@ -1,31 +1,31 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Microsoft.EntityFrameworkCore.Migrations;
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
@ -35,7 +35,7 @@ namespace ASC.Migrations.PostgreSql.Migrations;
public partial class WebhooksDbContextMigrate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
{
migrationBuilder.CreateTable(
name: "webhooks",
columns: table => new
@ -48,7 +48,7 @@ public partial class WebhooksDbContextMigrate : Migration
constraints: table =>
{
table.PrimaryKey("PRIMARY", x => x.id);
});
});
migrationBuilder.CreateTable(
name: "webhooks_config",
@ -115,7 +115,7 @@ public partial class WebhooksDbContextMigrate : Migration
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "webhooks");
name: "webhooks");
migrationBuilder.DropTable(
name: "webhooks_logs");

View File

@ -0,0 +1,186 @@
// <auto-generated />
using System;
using ASC.Webhooks.Core.EF.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb
{
[DbContext(typeof(WebhooksDbContext))]
[Migration("20230607172539_WebhooksDbContext_Upgrade1")]
partial class WebhooksDbContextUpgrade1
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "7.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("Method")
.ValueGeneratedOnAdd()
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("method")
.HasDefaultValueSql("''");
b.Property<string>("Route")
.ValueGeneratedOnAdd()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("route")
.HasDefaultValueSql("''");
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<bool>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasColumnName("enabled")
.HasDefaultValueSql("true");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b.Property<bool>("SSL")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasColumnName("ssl")
.HasDefaultValueSql("true");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("secret_key")
.HasDefaultValueSql("''");
b.Property<int>("TenantId")
.HasColumnType("int unsigned")
.HasColumnName("tenant_id");
b.Property<string>("Uri")
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasColumnName("uri")
.HasDefaultValueSql("''");
b.HasKey("Id")
.HasName("PRIMARY");
b.HasIndex("TenantId")
.HasDatabaseName("tenant_id");
b.ToTable("webhooks_config", (string)null);
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("ConfigId")
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime")
.HasColumnName("creation_time");
b.Property<DateTime?>("Delivery")
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
b.Property<string>("RequestPayload")
.IsRequired()
.HasColumnType("text")
.HasColumnName("request_payload");
b.Property<string>("ResponseHeaders")
.HasColumnType("json")
.HasColumnName("response_headers");
b.Property<string>("ResponsePayload")
.HasColumnType("text")
.HasColumnName("response_payload");
b.Property<int>("Status")
.HasColumnType("int")
.HasColumnName("status");
b.Property<int>("TenantId")
.HasColumnType("int unsigned")
.HasColumnName("tenant_id");
b.Property<string>("Uid")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar")
.HasColumnName("uid");
b.Property<int>("WebhookId")
.HasColumnType("int")
.HasColumnName("webhook_id");
b.HasKey("Id")
.HasName("PRIMARY");
b.HasIndex("ConfigId");
b.HasIndex("TenantId")
.HasDatabaseName("tenant_id");
b.ToTable("webhooks_logs", (string)null);
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b =>
{
b.HasOne("ASC.Webhooks.Core.EF.Model.WebhooksConfig", "Config")
.WithMany()
.HasForeignKey("ConfigId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Config");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,65 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb
{
/// <inheritdoc />
public partial class WebhooksDbContextUpgrade1 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "uri",
table: "webhooks_config",
type: "text",
nullable: true,
defaultValueSql: "''",
oldClrType: typeof(string),
oldType: "character varying(50)",
oldMaxLength: 50,
oldNullable: true,
oldDefaultValueSql: "''");
migrationBuilder.AddColumn<string>(
name: "name",
table: "webhooks_config",
type: "character varying(50)",
maxLength: 50,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<bool>(
name: "ssl",
table: "webhooks_config",
type: "boolean",
nullable: false,
defaultValueSql: "true");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "name",
table: "webhooks_config");
migrationBuilder.DropColumn(
name: "ssl",
table: "webhooks_config");
migrationBuilder.AlterColumn<string>(
name: "uri",
table: "webhooks_config",
type: "character varying(50)",
maxLength: 50,
nullable: true,
defaultValueSql: "''",
oldClrType: typeof(string),
oldType: "text",
oldNullable: true,
oldDefaultValueSql: "''");
}
}
}

View File

@ -63,6 +63,18 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnName("enabled")
.HasDefaultValueSql("true");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b.Property<bool>("SSL")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasColumnName("ssl")
.HasDefaultValueSql("true");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
@ -76,8 +88,7 @@ namespace ASC.Migrations.PostgreSql.Migrations
b.Property<string>("Uri")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnType("text")
.HasColumnName("uri")
.HasDefaultValueSql("''");
@ -102,10 +113,6 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<int>("WebhookId")
.HasColumnType("int")
.HasColumnName("webhook_id");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime")
.HasColumnName("creation_time");
@ -145,6 +152,10 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("varchar")
.HasColumnName("uid");
b.Property<int>("WebhookId")
.HasColumnType("int")
.HasColumnName("webhook_id");
b.HasKey("Id")
.HasName("PRIMARY");

View File

@ -0,0 +1,60 @@
{
"Webhook": "Webhook",
"Webhooks": "Webhooks",
"CreateWebhook": "Create webhook",
"WebhooksInfo": "Use webhooks to perform custom actions on the side of any application or website you are using based on various events in ONLYOFFICE Docspace.\r\nHere, you can create and manage all your webhooks, configure them, and browse history of every webhook to audit their performance.",
"WebhooksGuide": "Webhooks Guide",
"WebhookCreationHint": "This webhook will be assigned to all events in DocSpace",
"WebhookName": "Webhook name",
"EnterWebhookName": "Enter webhook name",
"PayloadUrl": "Payload URL",
"EnterUrl": "Enter URL",
"SSLVerification": "SSL verification",
"SSLHint": "By default, we verify SSL certificates when delivering payloads.",
"EnableSSL": "Enable SSL verification",
"DisableSSL": "Disable (not recommended)",
"EnterSecretKey": "Enter secret key",
"SecretKey": "Secret key",
"SecretKeyHint": "Setting a webhook secret allows you to verify requests sent to the payload URL.",
"ReadMore": "Read more",
"SecretKeyWarning": "You cannot retrieve your secret key again once it has been saved. If you've lost or forgotten this secret key, you can reset it, but all integrations using this secret will need to be updated.",
"ResetKey": "Reset key",
"Generate": "Generate",
"DeleteHint": "The webhook will be deleted permanently.\r\nYou will not be able to undo this action.",
"NotSent": "Not sent",
"WebhookEditedSuccessfully": "Webhook configuration edited successfully",
"WebhookRemoved": "Webhook removed",
"WebhookHistory": "Webhook history",
"DeleteWebhook": "Delete webhook",
"SettingsWebhook": "Settings webhook",
"DeleteWebhookForeverQuestion": "Delete Webhook forever?",
"URL": "URL",
"State": "State",
"WebhookRedilivered": "Webhook redelivered",
"WebhookDetails": "Webhook details",
"Request": "Request",
"Response": "Response",
"FailedToConnect": "We couldnt deliver this payload: failed to connect to host",
"RequestPostHeader": "Request post header",
"RequestPostBody": "Request post body",
"RequestHeaderCopied": "Request post header successfully copied to clipboard",
"RequestBodyCopied": "Request post body successfully copied to clipboard",
"ResponsePostHeader": "Response post header",
"ResponsePostBody": "Response post body",
"ResponseHeaderCopied": "Response post header successfully copied to clipboard",
"ResponseBodyCopied": "Response post body successfully copied to clipboard",
"PayloadIsTooLarge": "Payload is too large to display.",
"ViewRawPayload": "View raw payload",
"Retry": "Retry",
"UnselectAll": "Unselect all",
"EventHint": "Deliveries are automatically deleted after 15 days",
"NoResultsMatched": "No results match this filter. Try a different one or clear filter to view all items.",
"SelectDate": "Select date",
"SelectDeliveryTime": "Select Delivery time",
"DeliveryDate": "Delivery date",
"From": "From",
"Before": "Before",
"EventID": "Event ID",
"Delivery": "Delivery",
"Add": "Add"
}

View File

@ -28,6 +28,7 @@ import MainBar from "./components/MainBar";
import { Portal } from "@docspace/components";
import indexedDbHelper from "@docspace/common/utils/indexedDBHelper";
import { IndexedDBStores } from "@docspace/common/constants";
import { isMobile as isMobileUtils } from "@docspace/components/utils/device";
import queryString from "query-string";
const Shell = ({ items = [], page = "home", ...rest }) => {
@ -340,7 +341,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
<Layout>
{toast}
<ReactSmartBanner t={t} ready={ready} />
{isEditor || !isMobileOnly ? <></> : <NavMenu />}
{isEditor ? <></> : <NavMenu />}
{isMobileOnly && <MainBar />}
<IndicatorLoader />
<ScrollToTop />

View File

@ -2,6 +2,8 @@ import React from "react";
import styled, { css } from "styled-components";
import { isIOS, isFirefox, isMobileOnly } from "react-device-detect";
import { mobile } from "@docspace/components/utils/device";
const StyledMain = styled.main`
height: ${isIOS && !isFirefox ? "calc(var(--vh, 1vh) * 100)" : "100vh"};
width: 100vw;
@ -19,6 +21,15 @@ const StyledMain = styled.main`
box-sizing: border-box;
}
${!isMobileOnly &&
css`
@media ${mobile} {
height: ${isIOS && !isFirefox
? "calc(var(--vh, 1vh) * 100)"
: "calc(100vh - 64px)"};
}
`}
${isMobileOnly &&
css`
height: auto;

View File

@ -1,6 +1,11 @@
import React from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import {
isMobileOnly,
isDesktop as isDesktopDevice,
} from "react-device-detect";
import { isMobile as isMobileUtils } from "@docspace/components/utils/device";
import Backdrop from "@docspace/components/backdrop";
import Aside from "@docspace/components/aside";
@ -12,7 +17,7 @@ import { useNavigate, useLocation } from "react-router-dom";
import Loaders from "@docspace/common/components/Loaders";
import { LayoutContextConsumer } from "../Layout/context";
import { isMobileOnly } from "react-device-detect";
import { inject, observer } from "mobx-react";
import i18n from "./i18n";
import PreparationPortalDialog from "../dialogs/PreparationPortalDialog";
@ -61,6 +66,10 @@ const NavMenu = (props) => {
const navigate = useNavigate();
const location = useLocation();
const [showNavMenu, setShowNavMenu] = React.useState(
isMobileOnly || isMobileUtils()
);
const [isBackdropVisible, setIsBackdropVisible] = React.useState(
props.isBackdropVisible
);
@ -115,6 +124,16 @@ const NavMenu = (props) => {
setIsNavHoverEnabled(false);
};
const onResize = React.useCallback(() => {
setShowNavMenu(isMobileUtils() || isMobileOnly);
}, []);
React.useEffect(() => {
if (isDesktopDevice) window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
const {
isAuthenticated,
isLoaded,
@ -128,6 +147,8 @@ const NavMenu = (props) => {
const isAsideAvailable = !!asideContent;
const hideHeader = isDesktop || (!showHeader && isFrame);
if (!showNavMenu) return <></>;
const isPreparationPortal = location.pathname === "/preparation-portal";
return (
<LayoutContextConsumer>

View File

@ -36,7 +36,7 @@ const StyledNav = styled.nav`
`}
@media ${mobile} {
padding: 0 0 0 16px;
padding: 0 16px 0 16px;
}
${isMobileOnly &&

View File

@ -5,7 +5,7 @@ import PropTypes from "prop-types";
import styled from "styled-components";
import { Link as LinkWithoutRedirect } from "react-router-dom";
import { isMobileOnly, isMobile } from "react-device-detect";
import { useLocation } from "react-router-dom";
import { useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { isDesktop, tablet, mobile } from "@docspace/components/utils/device";
import { combineUrl } from "@docspace/common/utils";

View File

@ -350,7 +350,7 @@ class FilesTableHeader extends React.Component {
setIsLoading(true);
window.DocSpace.navigate(
`${window.DocSpace.location.pathname}?${newFilter.toUrlParams}`
`${window.DocSpace.location.pathname}?${newFilter.toUrlParams()}`
);
};

View File

@ -6,6 +6,11 @@ import { inject, observer } from "mobx-react";
import Section from "@docspace/common/components/Section";
import withLoading from "SRC_DIR/HOCs/withLoading";
//import commonIconsStyles from "@docspace/components/utils/common-icons-style";
import { useParams } from "react-router-dom";
import HistoryHeader from "../categories/developer-tools/Webhooks/WebhookHistory/sub-components/HistoryHeader";
import DetailsNavigationHeader from "../categories/developer-tools/Webhooks/WebhookEventDetails/sub-components/DetailsNavigationHeader";
const ArticleSettings = React.memo(() => {
return (
<Article>
@ -20,23 +25,29 @@ const ArticleSettings = React.memo(() => {
);
});
const Layout = ({
currentProductId,
setCurrentProductId,
language,
children,
addUsers,
}) => {
const Layout = ({ currentProductId, setCurrentProductId, language, children, addUsers }) => {
useEffect(() => {
currentProductId !== "settings" && setCurrentProductId("settings");
}, [language, currentProductId, setCurrentProductId]);
const { id, eventId } = useParams();
const webhookHistoryPath = `/portal-settings/developer-tools/webhooks/${id}`;
const webhookDetailsPath = `/portal-settings/developer-tools/webhooks/${id}/${eventId}`;
const currentPath = window.location.pathname;
return (
<>
<ArticleSettings />
<Section withBodyScroll={true} settingsStudio={true}>
<Section.SectionHeader>
<SectionHeaderContent />
{currentPath === webhookHistoryPath ? (
<HistoryHeader />
) : currentPath === webhookDetailsPath ? (
<DetailsNavigationHeader />
) : (
<SectionHeaderContent />
)}
</Section.SectionHeader>
<Section.SectionBody>{children}</Section.SectionBody>

View File

@ -0,0 +1,434 @@
import React, { useState, useEffect } from "react";
import { withTranslation } from "react-i18next";
import styled from "styled-components";
import Box from "@docspace/components/box";
import TextInput from "@docspace/components/text-input";
import Textarea from "@docspace/components/textarea";
import Label from "@docspace/components/label";
import Checkbox from "@docspace/components/checkbox";
import Button from "@docspace/components/button";
import ComboBox from "@docspace/components/combobox";
import Heading from "@docspace/components/heading";
import { tablet } from "@docspace/components/utils/device";
import { objectToGetParams, loadScript } from "@docspace/common/utils";
import { inject, observer } from "mobx-react";
import { isMobile } from "react-device-detect";
import BreakpointWarning from "SRC_DIR/components/BreakpointWarning";
import { SortByFieldName } from "../../../../../helpers/constants";
const Controls = styled(Box)`
width: 500px;
display: flex;
flex-direction: column;
gap: 16px;
@media ${tablet} {
width: 100%;
}
.label {
min-width: fit-content;
}
`;
const ControlsGroup = styled(Box)`
display: flex;
flex-direction: column;
gap: 8px;
`;
const Frame = styled(Box)`
margin-top: 16px;
> div {
border: 1px dashed gray;
border-radius: 3px;
min-width: 100%;
min-height: 400px;
}
`;
const Buttons = styled(Box)`
margin-top: 16px;
button {
margin-right: 16px;
}
`;
const Container = styled(Box)`
width: 100%;
display: flex;
gap: 16px;
`;
const Preview = styled(Box)`
width: 50%;
flex-direction: row;
.frameStyle {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
`;
const PortalIntegration = (props) => {
const { t, setDocumentTitle } = props;
setDocumentTitle(t("JavascriptSdk"));
const scriptUrl = `${window.location.origin}/static/scripts/api.js`;
const dataSortBy = [
{
key: SortByFieldName.ModifiedDate,
label: t("Common:LastModifiedDate"),
default: true,
},
{ key: SortByFieldName.Name, label: t("Common:Title") },
{ key: SortByFieldName.Type, label: t("Common:Type") },
{ key: SortByFieldName.Size, label: t("Common:Size") },
{ key: SortByFieldName.CreationDate, label: t("Files:ByCreation") },
{ key: SortByFieldName.Author, label: t("Files:ByAuthor") },
];
const dataSortOrder = [
{ key: "descending", label: t("Descending"), default: true },
{ key: "ascending", label: t("Ascending") },
];
const [config, setConfig] = useState({
width: "100%",
height: "400px",
frameId: "ds-frame",
showHeader: false,
showTitle: true,
showArticle: false,
showFilter: false,
});
const [sortBy, setSortBy] = useState(dataSortBy[0]);
const [sortOrder, setSortOrder] = useState(dataSortOrder[0]);
const [withSubfolders, setWithSubfolders] = useState(false);
const params = objectToGetParams(config);
const frameId = config.frameId || "ds-frame";
const destroyFrame = () => {
DocSpace.destroyFrame();
};
const loadFrame = () => {
const script = document.getElementById("integration");
if (script) {
destroyFrame();
script.remove();
}
const params = objectToGetParams(config);
loadScript(`${scriptUrl}${params}`, "integration");
};
const onChangeWidth = (e) => {
setConfig((config) => {
return { ...config, width: e.target.value };
});
};
const onChangeHeight = (e) => {
setConfig((config) => {
return { ...config, height: e.target.value };
});
};
const onChangeFolderId = (e) => {
setConfig((config) => {
return { ...config, folder: e.target.value };
});
};
const onChangeFrameId = (e) => {
setConfig((config) => {
return { ...config, frameId: e.target.value };
});
};
const onChangeWithSubfolders = (e) => {
setConfig((config) => {
return { ...config, withSubfolders: !withSubfolders };
});
setWithSubfolders(!withSubfolders);
};
const onChangeSortBy = (item) => {
setConfig((config) => {
return { ...config, sortby: item.key };
});
setSortBy(item);
};
const onChangeSortOrder = (item) => {
setConfig((config) => {
return { ...config, sortorder: item.key };
});
setSortOrder(item);
};
const onChangeFilterType = (item) => {
setConfig((config) => {
return { ...config, filterType: item.key };
});
setFilterType(item);
};
const onChangeDisplayType = (item) => {
setConfig((config) => {
return { ...config, viewAs: item.key };
});
setDisplayType(item);
};
const onChangeShowHeader = (e) => {
setConfig((config) => {
return { ...config, showHeader: !config.showHeader };
});
};
const onChangeShowTitle = () => {
setConfig((config) => {
return { ...config, showTitle: !config.showTitle };
});
};
const onChangeShowArticle = (e) => {
setConfig((config) => {
return { ...config, showArticle: !config.showArticle };
});
};
const onChangeShowFilter = (e) => {
setConfig((config) => {
return { ...config, showFilter: !config.showFilter };
});
};
const onChangeCount = (e) => {
setConfig((config) => {
return { ...config, count: e.target.value };
});
};
const onChangePage = (e) => {
setConfig((config) => {
return { ...config, page: e.target.value };
});
};
const onChangeSearch = (e) => {
setConfig((config) => {
return { ...config, search: e.target.value };
});
};
const onChangeAuthor = (e) => {
setConfig((config) => {
return { ...config, authorType: e.target.value };
});
};
const codeBlock = `<div id="${frameId}">Fallback text</div>\n<script src="${scriptUrl}${params}"></script>`;
return (
<>
{isMobile ? (
<BreakpointWarning sectionName={t("JavascriptSdk")} />
) : (
<Container>
<Controls>
<Heading level={1} size="small">
{t("WindowParameters")}
</Heading>
<ControlsGroup>
<Label className="label" text={t("FrameId")} />
<TextInput
scale={true}
onChange={onChangeFrameId}
placeholder={t("EnterId")}
value={config.frameId}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Width")} />
<TextInput
scale={true}
onChange={onChangeWidth}
placeholder={t("EnterWidth")}
value={config.width}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Height")} />
<TextInput
scale={true}
onChange={onChangeHeight}
placeholder={t("EnterHeight")}
value={config.height}
/>
</ControlsGroup>
<Checkbox
label={t("Header")}
onChange={onChangeShowHeader}
isChecked={config.showHeader}
/>
<Checkbox
label={t("Common:Title")}
onChange={onChangeShowTitle}
isChecked={config.showTitle}
/>
<Checkbox
label={t("Menu")}
onChange={onChangeShowArticle}
isChecked={config.showArticle}
/>
<Checkbox
label={t("Files:Filter")}
onChange={onChangeShowFilter}
isChecked={config.showFilter}
/>
<Heading level={1} size="small">
{t("DataDisplay")}
</Heading>
<ControlsGroup>
<Label className="label" text={t("FolderId")} />
<TextInput
scale={true}
onChange={onChangeFolderId}
placeholder={t("EnterId")}
value={config.folder}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("ItemsCount")} />
<TextInput
scale={true}
onChange={onChangeCount}
placeholder={t("EnterCount")}
value={config.count}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("Page")} />
<TextInput
scale={true}
onChange={onChangePage}
placeholder={t("EnterPage")}
value={config.page}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("SearchTerm")} />
<Box
style={{ flexDirection: "row", display: "flex", gap: "16px" }}
>
<TextInput
scale={true}
onChange={onChangeSearch}
placeholder={t("Common:Search")}
value={config.search}
/>
<Checkbox
label={t("Files:WithSubfolders")}
onChange={onChangeWithSubfolders}
isChecked={withSubfolders}
/>
</Box>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("Files:ByAuthor")} />
<TextInput
scale={true}
onChange={onChangeAuthor}
placeholder={t("Common:EnterName")}
value={config.authorType}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("Common:SortBy")} />
<ComboBox
onSelect={onChangeSortBy}
options={dataSortBy}
scaled={true}
selectedOption={sortBy}
displaySelectedOption
directionY="top"
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("SortOrder")} />
<ComboBox
onSelect={onChangeSortOrder}
options={dataSortOrder}
scaled={true}
selectedOption={sortOrder}
displaySelectedOption
directionY="top"
/>
</ControlsGroup>
</Controls>
<Preview>
<Frame>
<Box id={frameId} className="frameStyle">
{t("Common:Preview")}
</Box>
</Frame>
<Buttons>
<Button
primary
size="normal"
label={t("Common:Preview")}
onClick={loadFrame}
/>
<Button
primary
size="normal"
label={t("Destroy")}
onClick={destroyFrame}
/>
</Buttons>
<Heading level={1} size="xsmall">
{t("CopyWindowCode")}
</Heading>
<Textarea value={codeBlock} />
</Preview>
</Container>
)}
</>
);
};
export default inject(({ setup, auth }) => {
const { settingsStore, setDocumentTitle } = auth;
const { theme } = settingsStore;
return {
theme,
setDocumentTitle,
};
})(
withTranslation(["JavascriptSdk", "Files", "EmbeddingPanel", "Common"])(
observer(PortalIntegration)
)
);

View File

@ -0,0 +1,52 @@
import React, { useEffect, useTransition, Suspense } from "react";
import styled from "styled-components";
import { useParams } from "react-router-dom";
import { inject, observer } from "mobx-react";
import Text from "@docspace/components/text";
import DetailsBar from "./sub-components/DetailsBar";
import MessagesDetails from "./sub-components/MessagesDetails";
import { WebhookDetailsLoader } from "../sub-components/Loaders";
const DetailsWrapper = styled.div`
width: 100%;
`;
const EventDetailsHeader = styled.header`
padding: 20px 0;
`;
const WebhookEventDetails = (props) => {
const { fetchEventData } = props;
const { id, eventId } = useParams();
const [isPending, startTransition] = useTransition();
useEffect(() => {
startTransition(() => {
fetchEventData(eventId);
});
}, []);
return (
<Suspense fallback={WebhookDetailsLoader}>
<DetailsWrapper>
<main>
<EventDetailsHeader>
<Text fontWeight={600}>Webhook {id}</Text>
<DetailsBar />
</EventDetailsHeader>
<MessagesDetails />
</main>
</DetailsWrapper>
</Suspense>
);
};
export default inject(({ webhooksStore }) => {
const { fetchEventData } = webhooksStore;
return { fetchEventData };
})(observer(WebhookEventDetails));

View File

@ -0,0 +1,99 @@
import React from "react";
import moment from "moment";
import styled from "styled-components";
import Text from "@docspace/components/text";
import StatusBadge from "../../sub-components/StatusBadge";
import { inject, observer } from "mobx-react";
import { Base } from "@docspace/components/themes";
const BarWrapper = styled.div`
width: 100%;
max-width: 1200px;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 24px;
background: ${(props) => (props.theme.isBase ? "#f8f9f9" : "#3D3D3D")};
border-radius: 3px;
flex-wrap: wrap;
.barItemHeader {
margin-bottom: 10px;
}
`;
BarWrapper.defaultProps = { theme: Base };
const BarItem = styled.div`
box-sizing: border-box;
height: 76px;
padding: 16px;
flex-basis: 25%;
@media (max-width: 1300px) {
flex-basis: 50%;
}
@media (max-width: 560px) {
flex-basis: 100%;
}
`;
const BarItemHeader = ({ children }) => (
<Text as="h3" color="#A3A9AE" fontSize="12px" fontWeight={600} className="barItemHeader">
{children}
</Text>
);
const FlexWrapper = styled.div`
display: flex;
align-items: center;
`;
const DetailsBar = ({ eventDetails }) => {
const formatDate = (date) => {
return moment(date).format("MMM D, YYYY, h:mm:ss A") + " UTC";
};
const formattedDelivery = formatDate(eventDetails.delivery);
const formattedCreationTime = formatDate(eventDetails.creationTime);
return (
<BarWrapper>
<BarItem>
<BarItemHeader>Status</BarItemHeader>
<FlexWrapper>
<StatusBadge status={eventDetails.status} />
</FlexWrapper>
</BarItem>
<BarItem>
<BarItemHeader>Event ID</BarItemHeader>
<Text isInline fontWeight={600}>
{eventDetails.id}
</Text>
</BarItem>
<BarItem>
<BarItemHeader>Event time</BarItemHeader>
<Text isInline fontWeight={600}>
{formattedCreationTime}
</Text>
</BarItem>
<BarItem>
<BarItemHeader>Delivery time</BarItemHeader>
<Text isInline fontWeight={600}>
{formattedDelivery}
</Text>
</BarItem>
</BarWrapper>
);
};
export default inject(({ webhooksStore }) => {
const { eventDetails } = webhooksStore;
return { eventDetails };
})(observer(DetailsBar));

View File

@ -0,0 +1,83 @@
import React from "react";
import styled from "styled-components";
import { inject, observer } from "mobx-react";
import { NoBoxShadowToast } from "../../styled-components";
import toastr from "@docspace/components/toast/toastr";
import { useNavigate } from "react-router-dom";
import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
import RetryIcon from "PUBLIC_DIR/images/refresh.react.svg?url";
import Headline from "@docspace/common/components/Headline";
import IconButton from "@docspace/components/icon-button";
import { tablet } from "@docspace/components/utils/device";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
const HeaderContainer = styled.div`
position: sticky;
top: 0;
background-color: ${(props) => props.theme.backgroundColor};
z-index: 310;
display: flex;
align-items: center;
max-width: calc(100vw - 32px);
min-height: 69px;
.arrow-button {
margin-right: 18.5px;
@media ${tablet} {
padding: 8px 0 8px 8px;
margin-left: -8px;
}
}
.headline {
font-size: 18px;
margin-right: 16px;
}
`;
const DetailsNavigationHeader = (props) => {
const { retryWebhookEvent } = props;
const { eventId } = useParams();
const { t } = useTranslation(["Webhooks", "Common"]);
const navigate = useNavigate();
const onBack = () => {
navigate(-1);
};
const handleRetryEvent = async () => {
await retryWebhookEvent(eventId);
toastr.success(t("WebhookRedilivered"), <b>{t("Common:Done")}</b>);
};
return (
<HeaderContainer>
<IconButton
iconName={ArrowPathReactSvgUrl}
size="17"
isFill={true}
onClick={onBack}
className="arrow-button"
/>
<Headline type="content" truncate={true} className="headline">
{t("WebhookDetails")}
</Headline>
<IconButton iconName={RetryIcon} size="17" isFill={true} onClick={handleRetryEvent} />
<NoBoxShadowToast />
</HeaderContainer>
);
};
export default inject(({ webhooksStore }) => {
const { retryWebhookEvent } = webhooksStore;
return { retryWebhookEvent };
})(observer(DetailsNavigationHeader));

View File

@ -0,0 +1,48 @@
import React from "react";
import styled from "styled-components";
import Submenu from "@docspace/components/submenu";
import RequestDetails from "./RequestDetails";
import ResponseDetails from "./ResponseDetails";
import { useTranslation } from "react-i18next";
import { isMobileOnly } from "react-device-detect";
import { inject, observer } from "mobx-react";
const SubmenuWrapper = styled.div`
.sticky {
z-index: 3;
top: ${() => (isMobileOnly ? "68px" : "0px")};
}
`;
const MessagesDetails = ({ eventDetails }) => {
const { t } = useTranslation(["Webhooks"]);
const menuData = [
{
id: "webhookRequest",
name: t("Request"),
content: <RequestDetails />,
},
];
if (eventDetails.status >= 200 && eventDetails.status < 500) {
menuData.push({
id: "webhookResponse",
name: t("Response"),
content: <ResponseDetails />,
});
}
return (
<SubmenuWrapper>
<Submenu data={menuData} startSelect={0} />
</SubmenuWrapper>
);
};
export default inject(({ webhooksStore }) => {
const { eventDetails } = webhooksStore;
return { eventDetails };
})(observer(MessagesDetails));

View File

@ -0,0 +1,110 @@
import React from "react";
import styled from "styled-components";
import Text from "@docspace/components/text";
import Textarea from "@docspace/components/textarea";
import { inject, observer } from "mobx-react";
import DangerIcon from "PUBLIC_DIR/images/danger.toast.react.svg?url";
import { useTranslation } from "react-i18next";
const DetailsWrapper = styled.div`
width: 100%;
.textareaBody {
height: 50vh !important;
}
.mt-7 {
margin-top: 7px;
}
.mt-16 {
margin-top: 16px;
}
.mb-4 {
margin-bottom: 4px;
}
`;
const ErrorMessageTooltip = styled.div`
box-sizing: border-box;
width: 100%;
max-width: 1200px;
padding: 8px 12px;
background: #f7cdbe;
box-shadow: 0px 5px 20px rgba(4, 15, 27, 0.07);
border-radius: 6px;
display: flex;
align-items: center;
margin-bottom: 16px;
color: black;
img {
margin-right: 8px;
}
`;
function isJSON(jsonString) {
try {
const parsedJson = JSON.parse(jsonString);
return parsedJson && typeof parsedJson === "object";
} catch (e) {}
return false;
}
const RequestDetails = ({ eventDetails }) => {
const { t } = useTranslation(["Webhooks"]);
return (
<DetailsWrapper>
{eventDetails.status === 0 && (
<ErrorMessageTooltip>
<img src={DangerIcon} alt="danger icon" />
{t("FailedToConnect")}
</ErrorMessageTooltip>
)}
<Text as="h3" fontWeight={600} className="mb-4 mt-7">
{t("RequestPostHeader")}
</Text>
{!eventDetails.requestHeaders ? (
<Textarea isDisabled />
) : (
<Textarea
value={eventDetails.requestHeaders}
enableCopy
hasNumeration
isFullHeight
isJSONField
copyInfoText={t("RequestHeaderCopied")}
/>
)}
<Text as="h3" fontWeight={600} className="mb-4 mt-16">
{t("RequestPostBody")}
</Text>
{isJSON(eventDetails.requestPayload) ? (
<Textarea
value={eventDetails.requestPayload}
isJSONField
enableCopy
hasNumeration
isFullHeight
copyInfoText={t("RequestBodyCopied")}
/>
) : (
<Textarea value={eventDetails.requestPayload} heightScale className="textareaBody" />
)}
</DetailsWrapper>
);
};
export default inject(({ webhooksStore }) => {
const { eventDetails } = webhooksStore;
return { eventDetails };
})(observer(RequestDetails));

View File

@ -0,0 +1,147 @@
import React from "react";
import styled, { css } from "styled-components";
import Textarea from "@docspace/components/textarea";
import Button from "@docspace/components/button";
import Text from "@docspace/components/text";
import { inject, observer } from "mobx-react";
import json_beautifier from "csvjson-json_beautifier";
import { isMobileOnly } from "react-device-detect";
import { useTranslation } from "react-i18next";
const DetailsWrapper = styled.div`
width: 100%;
.textareaBody {
height: 50vh !important;
}
.mt-7 {
margin-top: 7px;
}
.mt-16 {
margin-top: 16px;
}
.mb-4 {
margin-bottom: 4px;
}
`;
const LargePayloadStub = styled.div`
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
max-width: 1200px;
padding: 12px 10px;
margin-top: 4px;
background: #f8f9f9;
border: 1px solid #eceef1;
border-radius: 3px;
${isMobileOnly &&
css`
justify-content: flex-start;
flex-wrap: wrap;
row-gap: 16px;
`}
`;
function isJSON(jsonString) {
try {
const parsedJson = JSON.parse(jsonString);
return parsedJson && typeof parsedJson === "object";
} catch (e) {}
return false;
}
const ResponseDetails = ({ eventDetails }) => {
const responsePayload = eventDetails.responsePayload?.trim();
const { t } = useTranslation(["Webhooks"]);
const beautifiedJSON = isJSON(responsePayload)
? json_beautifier(JSON.parse(responsePayload), {
inlineShortArrays: true,
})
: responsePayload;
const numberOfLines = isJSON(responsePayload)
? beautifiedJSON.split("\n").length
: responsePayload.split("\n").length;
const openRawPayload = () => {
const rawPayload = window.open("");
isJSON(responsePayload)
? rawPayload.document.write(beautifiedJSON.replace(/(?:\r\n|\r|\n)/g, "<br/>"))
: rawPayload.document.write(responsePayload);
rawPayload.focus();
};
return (
<DetailsWrapper>
<Text as="h3" fontWeight={600} className="mb-4 mt-7">
{t("ResponsePostHeader")}
</Text>
{isJSON(eventDetails.responseHeaders) ? (
<Textarea
value={eventDetails.responseHeaders}
enableCopy
hasNumeration
isFullHeight
isJSONField
copyInfoText={t("ResponseHeaderCopied")}
/>
) : (
<Textarea value={eventDetails.responseHeaders} heightScale className="textareaBody" />
)}
<Text as="h3" fontWeight={600} className="mb-4 mt-16">
{t("ResponsePostBody")}
</Text>
{responsePayload.length > 4000 || numberOfLines > 100 ? (
<LargePayloadStub>
<Text fontWeight={600} color="#657077">
{t("PayloadIsTooLarge")}
</Text>
<Button
size="small"
onClick={openRawPayload}
label={t("ViewRawPayload")}
scale={isMobileOnly}
/>
</LargePayloadStub>
) : responsePayload === "" ? (
<Textarea isDisabled />
) : isJSON(responsePayload) ? (
<Textarea
value={responsePayload}
isJSONField
enableCopy
hasNumeration
isFullHeight
copyInfoText={t("ResponseBodyCopied")}
/>
) : (
<Textarea
value={responsePayload}
enableCopy
heightScale
className="textareaBody"
copyInfoText={t("ResponseBodyCopied")}
/>
)}
</DetailsWrapper>
);
};
export default inject(({ webhooksStore }) => {
const { eventDetails } = webhooksStore;
return { eventDetails };
})(observer(ResponseDetails));

View File

@ -0,0 +1,116 @@
import React, { useState, useEffect, useTransition, Suspense } from "react";
import moment from "moment";
import styled from "styled-components";
import HistoryFilterHeader from "./sub-components/HistoryFilterHeader";
import WebhookHistoryTable from "./sub-components/WebhookHistoryTable";
import { WebhookHistoryLoader } from "../sub-components/Loaders";
import { inject, observer } from "mobx-react";
import { useParams } from "react-router-dom";
import EmptyFilter from "./sub-components/EmptyFilter";
const WebhookWrapper = styled.div`
width: 100%;
`;
const parseUrl = (url) => {
const urlObj = new URL(url);
const searchParams = urlObj.searchParams;
const params = {};
for (const [key, value] of searchParams) {
params[key] = value;
}
params.deliveryDate =
params.deliveryDate === "null" ? null : moment(params.deliveryDate, "YYYY-MM-DD");
params.deliveryFrom = moment(params.deliveryFrom, "HH:mm");
params.deliveryTo = moment(params.deliveryTo, "HH:mm");
params.status = JSON.parse(params.status);
return params;
};
function hasNoSearchParams(url) {
const urlObj = new URL(url);
return urlObj.search === "";
}
const WebhookHistory = (props) => {
const {
historyItems,
fetchHistoryItems,
emptyCheckedIds,
clearHistoryFilters,
setHistoryFilters,
formatFilters,
} = props;
const [isFetchFinished, setIsFetchFinished] = useState(false);
const [isPending, startTransition] = useTransition();
const { id } = useParams();
const fetchItems = async () => {
if (hasNoSearchParams(window.location)) {
await fetchHistoryItems({
configId: id,
});
} else {
const parsedParams = parseUrl(window.location);
setHistoryFilters(parsedParams);
await fetchHistoryItems({
...formatFilters(parsedParams),
configId: id,
});
}
setIsFetchFinished(true);
};
useEffect(() => {
startTransition(fetchItems);
return clearHistoryFilters;
}, []);
const applyFilters = async ({ deliveryFrom, deliveryTo, groupStatus }) => {
emptyCheckedIds();
const params = { configId: id, deliveryFrom, deliveryTo, groupStatus };
await fetchHistoryItems(params);
};
return (
<WebhookWrapper>
<Suspense fallback={<WebhookHistoryLoader />}>
<main>
<HistoryFilterHeader applyFilters={applyFilters} />
{historyItems.length === 0 && isFetchFinished ? (
<EmptyFilter applyFilters={applyFilters} />
) : (
<WebhookHistoryTable />
)}
</main>
</Suspense>
</WebhookWrapper>
);
};
export default inject(({ webhooksStore }) => {
const {
historyItems,
fetchHistoryItems,
emptyCheckedIds,
clearHistoryFilters,
setHistoryFilters,
formatFilters,
} = webhooksStore;
return {
historyItems,
fetchHistoryItems,
emptyCheckedIds,
clearHistoryFilters,
setHistoryFilters,
formatFilters,
};
})(observer(WebhookHistory));

View File

@ -0,0 +1,88 @@
import React from "react";
import styled from "styled-components";
import EmptyFilterImg from "PUBLIC_DIR/images/empty_filter.react.svg?url";
import EmptyFilterDarkImg from "PUBLIC_DIR/images/empty_filter_dark.react.svg?url";
import ClearEmptyFilterIcon from "PUBLIC_DIR/images/clear.empty.filter.svg?url";
import Text from "@docspace/components/text";
import Link from "@docspace/components/link";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
const EmptyFilterWrapper = styled.div`
width: 100%;
display: flex;
justify-content: center;
margin-top: 149px;
`;
const EmptyFilterContent = styled.div`
display: flex;
.emptyFilterText {
margin-left: 40px;
}
.clearFilter {
display: block;
margin-top: 26px;
cursor: pointer;
}
.clearFilterIcon {
margin-right: 8px;
}
.emptyFilterHeading {
margin-bottom: 8px;
}
`;
const EmptyFilter = (props) => {
const { applyFilters, formatFilters, clearHistoryFilters, theme } = props;
const { t } = useTranslation(["Webhooks", "Common"]);
const clearFilters = () => {
clearHistoryFilters(null);
applyFilters(
formatFilters({
deliveryDate: null,
status: [],
}),
);
};
return (
<EmptyFilterWrapper>
<EmptyFilterContent>
<img src={theme.isBase ? EmptyFilterImg : EmptyFilterDarkImg} alt="Empty filter" />
<div className="emptyFilterText">
<Text fontSize="16px" fontWeight={700} as="p" className="emptyFilterHeading">
{t("Common:NotFoundTitle")}
</Text>
<Text fontSize="12px" color={theme.isBase ? "#555F65" : "rgba(255, 255, 255, 0.6)"}>
{t("NoResultsMatched")}
</Text>
<span className="clearFilter" onClick={clearFilters}>
<img src={ClearEmptyFilterIcon} alt={t("ClearFilter")} className="clearFilterIcon" />
<Link
color={theme.isBase ? "#657077" : "inherit"}
isHovered
fontWeight={600}
type="action">
{t("Common:ClearFilter")}
</Link>
</span>
</div>
</EmptyFilterContent>
</EmptyFilterWrapper>
);
};
export default inject(({ webhooksStore, auth }) => {
const { formatFilters, clearHistoryFilters } = webhooksStore;
const { theme } = auth.settingsStore;
return { formatFilters, clearHistoryFilters, theme };
})(observer(EmptyFilter));

View File

@ -0,0 +1,225 @@
import React, { useState, useEffect, useRef } from "react";
import moment from "moment";
import { inject, observer } from "mobx-react";
import styled, { css } from "styled-components";
import Text from "@docspace/components/text";
import SelectorAddButton from "@docspace/components/selector-add-button";
import SelectedItem from "@docspace/components/selected-item";
import Calendar from "@docspace/components/calendar";
import TimePicker from "@docspace/components/time-picker";
import { isMobileOnly } from "react-device-detect";
import { useTranslation } from "react-i18next";
const TimePickerCell = styled.span`
margin-left: 8px;
display: inline-flex;
align-items: center;
.timePickerItem {
display: inline-flex;
align-items: center;
margin-right: 16px;
}
`;
const StyledCalendar = styled(Calendar)`
position: absolute;
${(props) =>
props.isMobile &&
css`
position: fixed;
bottom: 0;
left: 0;
`}
`;
const DeliveryDatePicker = ({
Selectors,
filters,
setFilters,
isApplied,
setIsApplied,
isTimeOpen,
setIsTimeOpen,
}) => {
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
const { t } = useTranslation(["Webhooks"]);
const calendarRef = useRef();
const selectorRef = useRef();
const setDeliveryDate = (date) => {
setFilters((prevFilters) => ({ ...prevFilters, deliveryDate: date }));
};
const setDeliveryFrom = (date) => {
setFilters((prevFilters) => ({ ...prevFilters, deliveryFrom: date }));
};
const setDeliveryTo = (date) => {
setFilters((prevFilters) => ({ ...prevFilters, deliveryTo: date }));
};
const toggleCalendar = () => setIsCalendarOpen((prevIsCalendarOpen) => !prevIsCalendarOpen);
const closeCalendar = () => {
setIsApplied(false);
setIsCalendarOpen(false);
};
const showTimePicker = () => setIsTimeOpen(true);
const deleteSelectedDate = (e) => {
e.stopPropagation();
setFilters((prevFilters) => ({
deliveryDate: null,
deliveryFrom: moment().startOf("day"),
deliveryTo: moment().endOf("day"),
status: prevFilters.status,
}));
setIsTimeOpen(false);
setIsApplied(false);
};
const handleClick = (e) => {
!selectorRef?.current?.contains(e.target) &&
!calendarRef?.current?.contains(e.target) &&
setIsCalendarOpen(false);
};
useEffect(() => {
document.addEventListener("click", handleClick, { capture: true });
return () => document.removeEventListener("click", handleClick, { capture: true });
}, []);
const CalendarElement = () => (
<StyledCalendar
selectedDate={filters.deliveryDate}
setSelectedDate={setDeliveryDate}
onChange={closeCalendar}
isMobile={isMobileOnly}
forwardedRef={calendarRef}
/>
);
const DateSelector = () => (
<div>
<SelectorAddButton title={t("Add")} onClick={toggleCalendar} style={{ marginRight: "8px" }} />
<Text isInline fontWeight={600} color="#A3A9AE">
{t("SelectDate")}
</Text>
{isCalendarOpen && <CalendarElement />}
</div>
);
const SelectedDate = () => (
<SelectedItem
onClose={deleteSelectedDate}
text={moment(filters.deliveryDate).format("DD MMM YYYY")}
/>
);
const SelectedDateWithCalendar = () => (
<div>
<SelectedItem
onClose={deleteSelectedDate}
text={moment(filters.deliveryDate).format("DD MMM YYYY")}
onClick={toggleCalendar}
/>
{isCalendarOpen && <CalendarElement />}
</div>
);
const SelectedDateTime = () => (
<div>
<SelectedItem
onClose={deleteSelectedDate}
text={
moment(filters.deliveryDate).format("DD MMM YYYY") +
" " +
moment(filters.deliveryFrom).format("HH:mm") +
" - " +
moment(filters.deliveryTo).format("HH:mm")
}
onClick={toggleCalendar}
/>
{isCalendarOpen && <CalendarElement />}
</div>
);
const TimeSelectorAdder = () => (
<TimePickerCell>
<SelectorAddButton title={t("Add")} onClick={showTimePicker} style={{ marginRight: "8px" }} />
<Text isInline fontWeight={600} color="#A3A9AE">
{t("SelectDeliveryTime")}
</Text>
</TimePickerCell>
);
const isEqualDates = (firstDate, secondDate) => {
return firstDate.format() === secondDate.format();
};
const isTimeEqual =
isEqualDates(filters.deliveryFrom, filters.deliveryFrom.clone().startOf("day")) &&
isEqualDates(filters.deliveryTo, filters.deliveryTo.clone().endOf("day"));
const isTimeValid = filters.deliveryTo > filters.deliveryFrom;
return (
<>
<Text fontWeight={600} fontSize="15px">
{t("DeliveryDate")}
</Text>
<Selectors ref={selectorRef}>
{filters.deliveryDate === null ? (
<DateSelector />
) : isApplied ? (
isTimeEqual ? (
<SelectedDateWithCalendar />
) : (
<SelectedDateTime />
)
) : (
<SelectedDate />
)}
{filters.deliveryDate !== null &&
!isApplied &&
(isTimeOpen ? (
<TimePickerCell>
<span className="timePickerItem">
<Text isInline fontWeight={600} color="#A3A9AE" style={{ marginRight: "8px" }}>
{t("From")}
</Text>
<TimePicker
date={filters.deliveryFrom}
setDate={setDeliveryFrom}
hasError={!isTimeValid}
tabIndex={1}
/>
</span>
<Text isInline fontWeight={600} color="#A3A9AE" style={{ marginRight: "8px" }}>
{t("Before")}
</Text>
<TimePicker
date={filters.deliveryTo}
setDate={setDeliveryTo}
hasError={!isTimeValid}
tabIndex={2}
/>
</TimePickerCell>
) : (
<TimeSelectorAdder />
))}
</Selectors>
</>
);
};
export default inject(({ webhooksStore }) => {
const {} = webhooksStore;
return {};
})(observer(DeliveryDatePicker));

View File

@ -0,0 +1,79 @@
import React from "react";
import styled from "styled-components";
import { inject, observer } from "mobx-react";
import Text from "@docspace/components/text";
import Button from "@docspace/components/button";
import { useTranslation } from "react-i18next";
const RoundedButton = styled(Button)`
box-sizing: border-box;
font-size: 13px;
font-weight: 400;
padding: 13.5px 15px;
border-radius: 16px;
margin-right: 7px;
line-height: 20px;
`;
const StatusBadgeSelector = ({ label, statusCode, isStatusSelected, handleStatusClick }) => {
const handleOnClick = () => handleStatusClick(statusCode);
return (
<RoundedButton label={label} onClick={handleOnClick} primary={isStatusSelected(statusCode)} />
);
};
const StatusPicker = ({ Selectors, filters, setFilters }) => {
const { t } = useTranslation(["Webhooks", "People"]);
const StatusCodes = ["Not sent", "2XX", "3XX", "4XX", "5XX"];
const isStatusSelected = (statusCode) => {
return filters.status.includes(statusCode);
};
const handleStatusClick = (statusCode) => {
setFilters((prevFilters) => ({
...prevFilters,
status: prevFilters.status.includes(statusCode)
? prevFilters.status.filter((statusItem) => statusItem !== statusCode)
: [...prevFilters.status, statusCode],
}));
};
const StatusBadgeElements = StatusCodes.map((code) =>
code === "Not sent" ? (
<StatusBadgeSelector
label={t("NotSent")}
statusCode={code}
isStatusSelected={isStatusSelected}
handleStatusClick={handleStatusClick}
key={code}
/>
) : (
<StatusBadgeSelector
label={code}
statusCode={code}
isStatusSelected={isStatusSelected}
handleStatusClick={handleStatusClick}
key={code}
/>
),
);
return (
<>
<Text fontWeight={600} fontSize="15px">
{t("People:UserStatus")}
</Text>
<Selectors>{StatusBadgeElements}</Selectors>
</>
);
};
export default inject(({ webhooksStore }) => {
const {} = webhooksStore;
return {};
})(observer(StatusPicker));

View File

@ -0,0 +1,161 @@
import React, { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import moment from "moment";
import ModalDialog from "@docspace/components/modal-dialog";
import styled from "styled-components";
import Button from "@docspace/components/button";
import DeliveryDatePicker from "./DeliveryDatePicker";
import StatusPicker from "./StatusPicker";
import { useParams, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Base } from "@docspace/components/themes";
const DialogBodyWrapper = styled.div`
margin-top: -4px;
`;
const Footer = styled.div`
width: 100%;
display: flex;
button {
width: 100%;
}
button:first-of-type {
margin-right: 10px;
}
`;
const Selectors = styled.div`
position: relative;
margin-top: 8px;
margin-bottom: 16px;
`;
const Separator = styled.hr`
border-top: 1px solid;
border-color: ${(props) => (props.theme.isBase ? "#eceef1" : "#474747")};
margin-bottom: 14px;
`;
Separator.defaultProps = { theme: Base };
const constructUrl = (baseUrl, filters) => {
const url = new URL(baseUrl, "http://127.0.0.1:8092/");
url.searchParams.append("deliveryDate", filters.deliveryDate?.format("YYYY-MM-DD") || null);
url.searchParams.append("deliveryFrom", filters.deliveryFrom.format("HH:mm"));
url.searchParams.append("deliveryTo", filters.deliveryTo.format("HH:mm"));
url.searchParams.append("status", JSON.stringify(filters.status));
return url.pathname + url.search;
};
function areArraysEqual(array1, array2) {
return array1.length === array2.length && array1.every((val, index) => val === array2[index]);
}
const FilterDialog = (props) => {
const { visible, closeModal, applyFilters, formatFilters, setHistoryFilters, historyFilters } =
props;
const { t } = useTranslation(["Webhooks", "Files", "Common"]);
const { id } = useParams();
const navigate = useNavigate();
const [filters, setFilters] = useState({
deliveryDate: null,
deliveryFrom: moment().startOf("day"),
deliveryTo: moment().endOf("day"),
status: [],
});
const [isApplied, setIsApplied] = useState(false);
const [isTimeOpen, setIsTimeOpen] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
const handleApplyFilters = () => {
if (filters.deliveryTo > filters.deliveryFrom) {
const params = formatFilters(filters);
setHistoryFilters(filters);
setIsApplied(true);
applyFilters(params);
closeModal();
}
};
useEffect(() => {
if (historyFilters === null) {
if (filters.deliveryDate !== null || filters.status.length > 0) {
setFilters({
deliveryDate: null,
deliveryFrom: moment().startOf("day"),
deliveryTo: moment().endOf("day"),
status: [],
});
}
isLoaded && navigate(`/portal-settings/developer-tools/webhooks/${id}`);
} else {
setFilters(historyFilters);
setIsTimeOpen(false);
setIsApplied(true);
navigate(constructUrl(`/portal-settings/developer-tools/webhooks/${id}`, historyFilters));
}
setIsLoaded(true);
}, [historyFilters, visible]);
const areFiltersChanged =
historyFilters !== null
? areArraysEqual(filters.status, historyFilters.status) &&
filters.deliveryDate === historyFilters?.deliveryDate &&
filters.deliveryFrom === historyFilters.deliveryFrom &&
filters.deliveryTo === historyFilters.deliveryTo
: filters.deliveryDate === null && filters.status.length === 0;
return (
<ModalDialog withFooterBorder visible={visible} onClose={closeModal} displayType="aside">
<ModalDialog.Header>{t("Files:Filter")}</ModalDialog.Header>
<ModalDialog.Body>
<DialogBodyWrapper>
<DeliveryDatePicker
Selectors={Selectors}
isApplied={isApplied}
setIsApplied={setIsApplied}
filters={filters}
setFilters={setFilters}
isTimeOpen={isTimeOpen}
setIsTimeOpen={setIsTimeOpen}
/>
<Separator />
<StatusPicker Selectors={Selectors} filters={filters} setFilters={setFilters} />
<Separator />
</DialogBodyWrapper>
</ModalDialog.Body>
{!areFiltersChanged && (
<ModalDialog.Footer>
<Footer>
<Button
label={t("Common:ApplyButton")}
size="normal"
primary={true}
onClick={handleApplyFilters}
/>
<Button label={t("Common:CancelButton")} size="normal" onClick={closeModal} />
</Footer>
</ModalDialog.Footer>
)}
</ModalDialog>
);
};
export default inject(({ webhooksStore }) => {
const { formatFilters, setHistoryFilters, historyFilters } = webhooksStore;
return { formatFilters, setHistoryFilters, historyFilters };
})(observer(FilterDialog));

View File

@ -0,0 +1,131 @@
import React, { useState } from "react";
import styled, { css } from "styled-components";
import { inject, observer } from "mobx-react";
import { Base } from "@docspace/components/themes";
import FilterReactSvrUrl from "PUBLIC_DIR/images/filter.react.svg?url";
import IconButton from "@docspace/components/icon-button";
import Text from "@docspace/components/text";
import { useParams } from "react-router-dom";
import FilterDialog from "./FilterDialog";
import StatusBar from "./StatusBar";
import { useTranslation } from "react-i18next";
import { isMobile, isMobileOnly } from "react-device-detect";
const ListHeader = styled.header`
display: flex;
justify-content: space-between;
align-items: center;
${() =>
isMobile &&
css`
margin-top: 9px;
`}
${() =>
isMobileOnly &&
css`
margin-top: 35px;
padding-right: 8px;
`}
`;
const ListHeading = styled(Text)`
line-height: 22px;
font-weight: 700;
margin: 0;
`;
const FilterButton = styled.div`
position: relative;
display: flex;
box-sizing: border-box;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
width: 32px;
height: 32px;
z-index: ${(props) => (props.isGroupMenuVisible ? 199 : 201)};
border: 1px solid;
border-color: ${(props) => (props.theme.isBase ? "#d0d5da" : "rgb(71, 71, 71)")};
border-radius: 3px;
cursor: pointer;
svg {
cursor: pointer;
}
:hover {
border-color: #a3a9ae;
svg {
path {
fill: ${(props) => props.theme.iconButton.hoverColor};
}
}
}
span {
z-index: 203;
width: 8px;
height: 8px;
background-color: #4781d1;
border-radius: 50%;
position: absolute;
bottom: -2px;
right: -2px;
}
`;
FilterButton.defaultProps = { theme: Base };
const HistoryFilterHeader = (props) => {
const { applyFilters, historyFilters, isGroupMenuVisible } = props;
const { t } = useTranslation(["Webhooks"]);
const { id } = useParams();
const [isFiltersVisible, setIsFiltersVisible] = useState(false);
const openFiltersModal = () => {
setIsFiltersVisible(true);
};
const closeFiltersModal = () => {
setIsFiltersVisible(false);
};
return (
<div>
<ListHeader>
<ListHeading fontWeight={700} fontSize="16px">
{t("Webhook")} {id}
</ListHeading>
<FilterButton onClick={openFiltersModal} isGroupMenuVisible={isGroupMenuVisible}>
<IconButton iconName={FilterReactSvrUrl} size={16} />
<span hidden={historyFilters === null}></span>
</FilterButton>
</ListHeader>
{historyFilters !== null && <StatusBar applyFilters={applyFilters} />}
<FilterDialog
visible={isFiltersVisible}
closeModal={closeFiltersModal}
applyFilters={applyFilters}
/>
</div>
);
};
export default inject(({ webhooksStore }) => {
const { historyFilters, isGroupMenuVisible } = webhooksStore;
return {
historyFilters,
isGroupMenuVisible,
};
})(observer(HistoryFilterHeader));

View File

@ -0,0 +1,260 @@
import React, { useEffect } from "react";
import styled, { css } from "styled-components";
import { useNavigate } from "react-router-dom";
import { inject, observer } from "mobx-react";
import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
import RetryIcon from "PUBLIC_DIR/images/refresh.react.svg?url";
import Headline from "@docspace/common/components/Headline";
import IconButton from "@docspace/components/icon-button";
import { Hint } from "../../styled-components";
import { tablet } from "@docspace/components/utils/device";
import TableGroupMenu from "@docspace/components/table-container/TableGroupMenu";
import { isMobile, isMobileOnly } from "react-device-detect";
import DropDownItem from "@docspace/components/drop-down-item";
import toastr from "@docspace/components/toast/toastr";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { showLoader, hideLoader } from "@docspace/common/utils";
const HeaderContainer = styled.div`
position: sticky;
top: 0;
background-color: ${(props) => props.theme.backgroundColor};
z-index: 201;
display: flex;
align-items: center;
width: 100%;
min-height: 70px;
flex-wrap: wrap;
${() =>
isMobile &&
css`
margin-bottom: 11px;
`}
${() =>
isMobileOnly &&
css`
margin-top: 7px;
margin-left: -14px;
padding-left: 14px;
margin-right: -14px;
padding-right: 14px;
`}
.arrow-button {
margin-right: 18.5px;
@media ${tablet} {
padding: 8px 0 8px 8px;
margin-left: -8px;
}
${() =>
isMobileOnly &&
css`
margin-right: 13px;
`}
}
.headline {
font-size: 18px;
margin-right: 16px;
}
.table-container_group-menu {
margin: 0 0 0 -20px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
flex: 0 0 auto;
width: calc(100% + 40px);
height: 69px;
${() =>
isMobile &&
css`
height: 60px;
margin: 0 0 0 -16px;
width: calc(100% + 32px);
`}
${() =>
isMobileOnly &&
css`
position: absolute;
height: 48px;
margin: -35px 0 0 -17px;
width: calc(100% + 32px);
`}
}
`;
const HistoryHeader = (props) => {
const {
isGroupMenuVisible,
checkedEventIds,
checkAllIds,
emptyCheckedIds,
retryWebhookEvents,
isIndeterminate,
areAllIdsChecked,
fetchHistoryItems,
theme,
historyFilters,
formatFilters,
} = props;
const navigate = useNavigate();
const onBack = () => {
navigate("/portal-settings/developer-tools/webhooks");
};
const { t } = useTranslation(["Webhooks", "Common", "InfoPanel"]);
const { id } = useParams();
const handleGroupSelection = (isChecked) => {
isChecked ? checkAllIds() : emptyCheckedIds();
};
const handleRetryAll = async () => {
try {
await emptyCheckedIds();
const tempIds = checkedEventIds;
showLoader();
await retryWebhookEvents(tempIds);
hideLoader();
await fetchHistoryItems({
...(historyFilters ? formatFilters(historyFilters) : {}),
configId: id,
});
toastr.success(
`${t("WebhookRedilivered")}: ${checkedEventIds.length}`,
<b>{t("Common:Done")}</b>,
);
} catch (error) {
console.log(error);
toastr.error(error);
}
};
const headerMenu = [
{
id: "retry-event-option",
label: t("Retry"),
onClick: handleRetryAll,
iconUrl: RetryIcon,
},
];
const onKeyPress = (e) => (e.key === "Esc" || e.key === "Escape") && emptyCheckedIds();
useEffect(() => {
window.addEventListener("keyup", onKeyPress);
return () => window.removeEventListener("keyup", onKeyPress);
}, []);
const menuItems = (
<>
<DropDownItem
key="select-all-event-ids"
label={t("Common:SelectAll")}
data-index={0}
onClick={checkAllIds}
/>
<DropDownItem
key="unselect-all-event-ids"
label={t("UnselectAll")}
data-index={1}
onClick={emptyCheckedIds}
/>
</>
);
const NavigationHeader = () => (
<>
<IconButton
iconName={ArrowPathReactSvgUrl}
size="17"
isFill={true}
onClick={onBack}
className="arrow-button"
/>
<Headline type="content" truncate={true} className="headline">
{t("InfoPanel:SubmenuHistory")}
</Headline>
{/* <Hint
backgroundColor={theme.isBase ? "#F8F9F9" : "#3D3D3D"}
color={theme.isBase ? "#555F65" : "#FFFFFF"}>
{t("EventHint")}
</Hint> */}
</>
);
const GroupMenu = () => (
<TableGroupMenu
checkboxOptions={menuItems}
onChange={handleGroupSelection}
headerMenu={headerMenu}
isChecked={areAllIdsChecked}
isIndeterminate={isIndeterminate}
withoutInfoPanelToggler
/>
);
useEffect(() => {
return emptyCheckedIds;
}, []);
return (
<HeaderContainer>
{isMobileOnly ? (
<>
{isGroupMenuVisible && <GroupMenu />}
<NavigationHeader />
</>
) : isGroupMenuVisible ? (
<GroupMenu />
) : (
<NavigationHeader />
)}
</HeaderContainer>
);
};
export default inject(({ webhooksStore, auth }) => {
const {
isGroupMenuVisible,
checkAllIds,
emptyCheckedIds,
checkedEventIds,
retryWebhookEvents,
isIndeterminate,
areAllIdsChecked,
fetchHistoryItems,
historyFilters,
formatFilters,
} = webhooksStore;
const { settingsStore } = auth;
const { theme } = settingsStore;
return {
isGroupMenuVisible,
checkAllIds,
emptyCheckedIds,
checkedEventIds,
retryWebhookEvents,
isIndeterminate,
areAllIdsChecked,
fetchHistoryItems,
theme,
historyFilters,
formatFilters,
};
})(observer(HistoryHeader));

View File

@ -0,0 +1,59 @@
import React from "react";
import styled from "styled-components";
import CrossReactSvgUrl from "PUBLIC_DIR/images/cross.react.svg?url";
import Text from "@docspace/components/text";
import IconButton from "@docspace/components/icon-button";
import { Base } from "@docspace/components/themes";
const StyledSelectedItem = styled.div`
width: fit-content;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: start;
box-sizing: border-box;
border-radius: 3px;
padding: 6px 8px;
margin-right: 4px;
margin-bottom: 4px;
background: ${(props) => props.theme.filterInput.selectedItems.background};
:hover {
background: ${(props) => props.theme.filterInput.selectedItems.hoverBackground};
}
.selected-item_label {
line-height: 20px;
margin-right: 10px;
max-width: 23ch;
}
`;
StyledSelectedItem.defaultProps = { theme: Base };
const SelectedItem = ({ label, removeSelectedItem }) => {
if (!label) return <></>;
return (
<StyledSelectedItem onClick={removeSelectedItem}>
<Text className={"selected-item_label"} title={label} truncate={true} noSelect>
{label}
</Text>
<IconButton
className="selected-tag-removed"
iconName={CrossReactSvgUrl}
size={12}
onClick={removeSelectedItem}
isFill
/>
</StyledSelectedItem>
);
};
export default React.memo(SelectedItem);

View File

@ -0,0 +1,121 @@
import React, { useEffect } from "react";
import moment from "moment";
import styled from "styled-components";
import { inject, observer } from "mobx-react";
import SelectedItem from "./SelectedItem";
import Link from "@docspace/components/link";
const StatusBarWrapper = styled.div`
margin-top: 9px;
.statusBarItem:last-of-type {
margin-right: 0;
}
.statusActionItem {
margin-left: 12px;
}
`;
const StatusBar = (props) => {
const {
historyFilters,
formatFilters,
applyFilters,
clearHistoryFilters,
clearDate,
unselectStatus,
} = props;
const clearAll = () => {
applyFilters(
formatFilters({
deliveryDate: null,
status: [],
}),
);
clearHistoryFilters();
};
const SelectedDateTime = () => {
return (
<SelectedItem
label={
moment(historyFilters.deliveryDate).format("DD MMM YYYY") +
" " +
moment(historyFilters.deliveryFrom).format("HH:mm") +
" - " +
moment(historyFilters.deliveryTo).format("HH:mm")
}
removeSelectedItem={clearDate}
/>
);
};
const SelectedDate = () => (
<SelectedItem
label={moment(historyFilters.deliveryDate).format("DD MMM YYYY")}
removeSelectedItem={clearDate}
/>
);
const SelectedStatuses = historyFilters.status.map((statusCode) => (
<SelectedItem
label={statusCode}
key={statusCode}
removeSelectedItem={() => unselectStatus(statusCode)}
/>
));
const isEqualDates = (firstDate, secondDate) => {
return firstDate.format() === secondDate.format();
};
useEffect(() => {
applyFilters(formatFilters(historyFilters));
if (historyFilters.deliveryDate === null && historyFilters.status.length === 0) {
clearHistoryFilters();
}
}, [historyFilters]);
return historyFilters.deliveryDate === null && historyFilters.status.length === 0 ? (
""
) : (
<StatusBarWrapper>
{historyFilters.deliveryDate !== null ? (
!isEqualDates(
historyFilters.deliveryFrom,
historyFilters.deliveryFrom.clone().startOf("day"),
) ||
!isEqualDates(historyFilters.deliveryTo, historyFilters.deliveryTo.clone().endOf("day")) ? (
<SelectedDateTime />
) : (
<SelectedDate />
)
) : (
""
)}
{SelectedStatuses}
{((historyFilters.deliveryDate !== null && historyFilters.status.length > 0) ||
historyFilters.status.length > 1) && (
<Link
type="action"
fontWeight={600}
isHovered={true}
onClick={clearAll}
color="#A3A9AE"
className="statusActionItem">
Clear all
</Link>
)}
</StatusBarWrapper>
);
};
export default inject(({ webhooksStore }) => {
const { formatFilters, historyFilters, clearHistoryFilters, clearDate, unselectStatus } =
webhooksStore;
return { formatFilters, historyFilters, clearHistoryFilters, clearDate, unselectStatus };
})(observer(StatusBar));

View File

@ -0,0 +1,103 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { useNavigate, useParams } from "react-router-dom";
import Row from "@docspace/components/row";
import { HistoryRowContent } from "./HistoryRowContent";
import RetryIcon from "PUBLIC_DIR/images/refresh.react.svg?url";
import InfoIcon from "PUBLIC_DIR/images/info.outline.react.svg?url";
import toastr from "@docspace/components/toast/toastr";
import { useTranslation } from "react-i18next";
const HistoryRow = (props) => {
const {
historyItem,
sectionWidth,
toggleEventId,
isIdChecked,
retryWebhookEvent,
fetchHistoryItems,
historyFilters,
formatFilters,
} = props;
const { t } = useTranslation(["Webhooks", "Common"]);
const navigate = useNavigate();
const { id } = useParams();
const redirectToDetails = () => navigate(window.location.pathname + `/${historyItem.id}`);
const handleRetryEvent = async () => {
await retryWebhookEvent(historyItem.id);
await fetchHistoryItems({
...(historyFilters ? formatFilters(historyFilters) : {}),
configId: id,
});
toastr.success(t("WebhookRedilivered"), <b>{t("Common:Done")}</b>);
};
const handleOnSelect = () => toggleEventId(historyItem.id);
const handleRowClick = (e) => {
if (
e.target.closest(".checkbox") ||
e.target.closest(".table-container_row-checkbox") ||
e.target.closest(".type-combobox") ||
e.target.closest(".table-container_row-context-menu-wrapper") ||
e.target.closest(".row_context-menu-wrapper") ||
e.detail === 0
) {
return;
}
toggleEventId(historyItem.id);
};
const contextOptions = [
{
key: "Webhook details dropdownItem",
label: t("WebhookDetails"),
icon: InfoIcon,
onClick: redirectToDetails,
},
{
key: "Retry dropdownItem",
label: t("Retry"),
icon: RetryIcon,
onClick: handleRetryEvent,
},
];
return (
<Row
sectionWidth={sectionWidth}
key={historyItem.id}
contextOptions={contextOptions}
checkbox
checked={isIdChecked(historyItem.id)}
onSelect={handleOnSelect}
className={isIdChecked(historyItem.id) ? "row-item selected-row-item" : "row-item "}
onClick={handleRowClick}>
<HistoryRowContent sectionWidth={sectionWidth} historyItem={historyItem} />
</Row>
);
};
export default inject(({ webhooksStore }) => {
const {
toggleEventId,
isIdChecked,
retryWebhookEvent,
fetchHistoryItems,
historyFilters,
formatFilters,
} = webhooksStore;
return {
toggleEventId,
isIdChecked,
retryWebhookEvent,
fetchHistoryItems,
historyFilters,
formatFilters,
};
})(observer(HistoryRow));

View File

@ -0,0 +1,48 @@
import React from "react";
import moment from "moment";
import styled from "styled-components";
import Text from "@docspace/components/text";
import RowContent from "@docspace/components/row-content";
import StatusBadge from "../../../../sub-components/StatusBadge";
const StyledRowContent = styled(RowContent)`
display: flex;
padding-bottom: 10px;
.rowMainContainer {
height: 100%;
width: 100%;
}
`;
const ContentWrapper = styled.div`
display: flex;
flex-direction: column;
justify-items: center;
`;
const StatusHeader = styled.div`
display: flex;
`;
export const HistoryRowContent = ({ sectionWidth, historyItem }) => {
const formattedDelivery = moment(historyItem.delivery).format("MMM D, YYYY, h:mm:ss A") + " UTC";
return (
<StyledRowContent sectionWidth={sectionWidth}>
<ContentWrapper>
<StatusHeader>
<Text fontWeight={600} fontSize="14px" style={{ marginRight: "8px" }}>
{historyItem.id}
</Text>
<StatusBadge status={historyItem.status} />
</StatusHeader>
<Text fontWeight={600} fontSize="12px" color="#A3A9AE">
{formattedDelivery}
</Text>
</ContentWrapper>
<span></span>
</StyledRowContent>
);
};

View File

@ -0,0 +1,90 @@
import React, { useEffect } from "react";
import styled from "styled-components";
import { inject, observer } from "mobx-react";
import { isMobile, isMobileOnly } from "react-device-detect";
import { useParams } from "react-router-dom";
import RowContainer from "@docspace/components/row-container";
import HistoryRow from "./HistoryRow";
import { Base } from "@docspace/components/themes";
const StyledRowContainer = styled(RowContainer)`
margin-top: 11px;
.row-list-item {
cursor: pointer;
padding-right: ${() => (isMobileOnly ? "5px" : "15px")};
}
.row-item::after {
bottom: -3px;
}
.row-list-item:has(.selected-row-item) {
background-color: ${(props) => (props.theme.isBase ? "#f3f4f4" : "#282828")};
}
`;
StyledRowContainer.defaultProps = { theme: Base };
const HistoryRowView = (props) => {
const {
historyItems,
sectionWidth,
viewAs,
setViewAs,
hasMoreItems,
totalItems,
fetchMoreItems,
historyFilters,
formatFilters,
} = props;
const { id } = useParams();
useEffect(() => {
if (viewAs !== "table" && viewAs !== "row") return;
if (sectionWidth < 1025 || isMobile) {
viewAs !== "row" && setViewAs("row");
} else {
viewAs !== "table" && setViewAs("table");
}
}, [sectionWidth]);
const fetchMoreFiles = () => {
const params = historyFilters === null ? {} : formatFilters(historyFilters);
fetchMoreItems({ ...params, configId: id });
};
return (
<StyledRowContainer
filesLength={historyItems.length}
fetchMoreFiles={fetchMoreFiles}
hasMoreFiles={hasMoreItems}
itemCount={totalItems}
draggable
useReactWindow={true}
itemHeight={59}>
{historyItems.map((item) => (
<HistoryRow key={item.id} historyItem={item} sectionWidth={sectionWidth} />
))}
</StyledRowContainer>
);
};
export default inject(({ setup, webhooksStore }) => {
const { viewAs, setViewAs } = setup;
const { historyItems, fetchMoreItems, hasMoreItems, totalItems, historyFilters, formatFilters } =
webhooksStore;
return {
viewAs,
setViewAs,
historyItems,
fetchMoreItems,
hasMoreItems,
totalItems,
historyFilters,
formatFilters,
};
})(observer(HistoryRowView));

View File

@ -0,0 +1,108 @@
import React, { useState, useEffect } from "react";
import TableHeader from "@docspace/components/table-container/TableHeader";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
const TABLE_VERSION = "5";
const TABLE_COLUMNS = `webhooksHistoryColumns_ver-${TABLE_VERSION}`;
const getColumns = (defaultColumns, userId) => {
const storageColumns = localStorage.getItem(`${TABLE_COLUMNS}=${userId}`);
const columns = [];
if (storageColumns) {
const splitColumns = storageColumns.split(",");
for (let col of defaultColumns) {
const column = splitColumns.find((key) => key === col.key);
column ? (col.enable = true) : (col.enable = false);
columns.push(col);
}
return columns;
} else {
return defaultColumns;
}
};
const HistoryTableHeader = (props) => {
const {
userId,
sectionWidth,
tableRef,
columnStorageName,
columnInfoPanelStorageName,
setHideColumns,
} = props;
const { t, ready } = useTranslation(["Webhooks", "People"]);
const defaultColumns = [
{
key: "Event ID",
title: t("EventID"),
resizable: true,
enable: true,
default: true,
active: true,
minWidth: 150,
onChange: onColumnChange,
},
{
key: "Status",
title: t("People:UserStatus"),
enable: true,
resizable: true,
onChange: onColumnChange,
},
{
key: "Delivery",
title: t("Delivery"),
enable: true,
resizable: true,
onChange: onColumnChange,
},
];
const [columns, setColumns] = useState(getColumns(defaultColumns, userId));
function onColumnChange(key, e) {
const columnIndex = columns.findIndex((c) => c.key === key);
if (columnIndex === -1) return;
setColumns((prevColumns) =>
prevColumns.map((item, index) =>
index === columnIndex ? { ...item, enable: !item.enable } : item,
),
);
const tableColumns = columns.map((c) => c.enable && c.key);
localStorage.setItem(`${TABLE_COLUMNS}=${userId}`, tableColumns);
}
useEffect(() => {
ready && setColumns(getColumns(defaultColumns, userId));
}, [ready]);
return (
<TableHeader
checkboxSize="32px"
containerRef={tableRef}
columns={columns}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
sectionWidth={sectionWidth}
checkboxMargin="12px"
showSettings={false}
useReactWindow
setHideColumns={setHideColumns}
infoPanelVisible={false}
/>
);
};
export default inject(({ auth }) => {
return {
userId: auth.userStore.user.id,
};
})(observer(HistoryTableHeader));

View File

@ -0,0 +1,139 @@
import React from "react";
import moment from "moment";
import styled, { css } from "styled-components";
import { inject, observer } from "mobx-react";
import { useNavigate, useParams } from "react-router-dom";
import TableRow from "@docspace/components/table-container/TableRow";
import TableCell from "@docspace/components/table-container/TableCell";
import Text from "@docspace/components/text";
import Checkbox from "@docspace/components/checkbox";
import StatusBadge from "../../../../sub-components/StatusBadge";
import toastr from "@docspace/components/toast/toastr";
import RetryIcon from "PUBLIC_DIR/images/refresh.react.svg?url";
import InfoIcon from "PUBLIC_DIR/images/info.outline.react.svg?url";
import { useTranslation } from "react-i18next";
const StyledTableRow = styled(TableRow)`
.textOverflow {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
${(props) =>
props.isHighlight &&
css`
.table-container_cell {
background-color: #f3f4f4;
}
`}
`;
const StyledWrapper = styled.div`
display: contents;
`;
const HistoryTableRow = (props) => {
const {
item,
toggleEventId,
isIdChecked,
retryWebhookEvent,
hideColumns,
fetchHistoryItems,
historyFilters,
formatFilters,
} = props;
const { t } = useTranslation(["Webhooks", "Common"]);
const navigate = useNavigate();
const { id } = useParams();
const redirectToDetails = () => navigate(window.location.pathname + `/${item.id}`);
const handleRetryEvent = async () => {
await retryWebhookEvent(item.id);
await fetchHistoryItems({
...(historyFilters ? formatFilters(historyFilters) : {}),
configId: id,
});
toastr.success(t("WebhookRedilivered"), <b>{t("Common:Done")}</b>);
};
const contextOptions = [
{
key: "Webhook details dropdownItem",
label: t("WebhookDetails"),
icon: InfoIcon,
onClick: redirectToDetails,
},
{
key: "Retry dropdownItem",
label: t("Retry"),
icon: RetryIcon,
onClick: handleRetryEvent,
},
];
const formattedDelivery = moment(item.delivery).format("MMM D, YYYY, h:mm:ss A") + " UTC";
const onChange = (e) => {
if (
e.target.closest(".checkbox") ||
e.target.closest(".table-container_row-checkbox") ||
e.target.closest(".type-combobox") ||
e.target.closest(".table-container_row-context-menu-wrapper") ||
e.detail === 0
) {
return;
}
toggleEventId(item.id);
};
const isChecked = isIdChecked(item.id);
return (
<StyledWrapper className={isChecked ? "selected-table-row" : ""} onClick={onChange}>
<StyledTableRow contextOptions={contextOptions} checked={isChecked} hideColumns={hideColumns}>
<TableCell>
<TableCell checked={isChecked} className="checkboxWrapper">
<Checkbox onChange={onChange} isChecked={isChecked} />
</TableCell>
<Text fontWeight={600}>{item.id}</Text>
</TableCell>
<TableCell>
<StatusBadge status={item.status} />
</TableCell>
<TableCell>
<Text fontWeight={600} fontSize="11px" className="textOverflow">
{formattedDelivery}
</Text>
</TableCell>
</StyledTableRow>
</StyledWrapper>
);
};
export default inject(({ webhooksStore }) => {
const {
toggleEventId,
isIdChecked,
retryWebhookEvent,
fetchHistoryItems,
historyFilters,
formatFilters,
} = webhooksStore;
return {
toggleEventId,
isIdChecked,
retryWebhookEvent,
fetchHistoryItems,
historyFilters,
formatFilters,
};
})(observer(HistoryTableRow));

View File

@ -0,0 +1,141 @@
import React, { useState, useRef, useEffect } from "react";
import styled from "styled-components";
import { isMobile } from "react-device-detect";
import TableContainer from "@docspace/components/table-container/TableContainer";
import TableBody from "@docspace/components/table-container/TableBody";
import HistoryTableHeader from "./HistoryTableHeader";
import HistoryTableRow from "./HistoryTableRow";
import { useParams } from "react-router-dom";
import { inject, observer } from "mobx-react";
import { Base } from "@docspace/components/themes";
const TableWrapper = styled(TableContainer)`
margin-top: 0;
.table-container_header {
position: absolute;
}
.header-container-text {
font-size: 12px;
}
.checkboxWrapper {
padding: 0;
padding-left: 8px;
}
.table-list-item {
cursor: pointer;
&:hover {
background-color: ${(props) => (props.theme.isBase ? "#f3f4f4" : "#282828")};
}
}
.table-list-item:has(.selected-table-row) {
background-color: ${(props) => (props.theme.isBase ? "#f3f4f4" : "#282828")};
}
`;
TableWrapper.defaultProps = { theme: Base };
const TABLE_VERSION = "5";
const COLUMNS_SIZE = `webhooksHistoryColumnsSize_ver-${TABLE_VERSION}`;
const INFO_PANEL_COLUMNS_SIZE = `infoPanelWebhooksHistoryColumnsSize_ver-${TABLE_VERSION}`;
const HistoryTableView = (props) => {
const {
sectionWidth,
historyItems,
viewAs,
setViewAs,
hasMoreItems,
totalItems,
fetchMoreItems,
formatFilters,
historyFilters,
userId,
} = props;
const tableRef = useRef(null);
const [hideColumns, setHideColumns] = useState(false);
const { id } = useParams();
useEffect(() => {
if (!sectionWidth) return;
if (sectionWidth < 1025 || isMobile) {
viewAs !== "row" && setViewAs("row");
} else {
viewAs !== "table" && setViewAs("table");
}
}, [sectionWidth]);
const fetchMoreFiles = () => {
const params = historyFilters === null ? {} : formatFilters(historyFilters);
fetchMoreItems({ ...params, configId: id });
};
const columnStorageName = `${COLUMNS_SIZE}=${userId}`;
const columnInfoPanelStorageName = `${INFO_PANEL_COLUMNS_SIZE}=${userId}`;
return (
<TableWrapper
forwardedRef={tableRef}
style={{
gridTemplateColumns: "300px 100px 400px 24px",
}}
useReactWindow>
<HistoryTableHeader
sectionWidth={sectionWidth}
tableRef={tableRef}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
setHideColumns={setHideColumns}
/>
<TableBody
itemHeight={49}
useReactWindow
infoPanelVisible={false}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
filesLength={historyItems.length}
fetchMoreFiles={fetchMoreFiles}
hasMoreFiles={hasMoreItems}
itemCount={totalItems}>
{historyItems.map((item) => (
<HistoryTableRow
key={item.id}
item={{ ...item, title: item.id }}
hideColumns={hideColumns}
/>
))}
</TableBody>
</TableWrapper>
);
};
export default inject(({ setup, webhooksStore, auth }) => {
const { viewAs, setViewAs } = setup;
const { historyItems, fetchMoreItems, hasMoreItems, totalItems, formatFilters, historyFilters } =
webhooksStore;
const { id: userId } = auth.userStore.user;
return {
viewAs,
setViewAs,
historyItems,
fetchMoreItems,
hasMoreItems,
totalItems,
formatFilters,
historyFilters,
userId,
};
})(observer(HistoryTableView));

View File

@ -0,0 +1,31 @@
import React from "react";
import { Consumer } from "@docspace/components/utils/context";
import { inject, observer } from "mobx-react";
import HistoryTableView from "./HistoryTableView";
import HistoryRowView from "./HistoryRowView";
const WebhookHistoryTable = (props) => {
const { viewAs } = props;
return (
<Consumer>
{(context) =>
viewAs === "table" ? (
<HistoryTableView sectionWidth={context.sectionWidth} />
) : (
<HistoryRowView sectionWidth={context.sectionWidth} />
)
}
</Consumer>
);
};
export default inject(({ setup }) => {
const { viewAs } = setup;
return {
viewAs,
};
})(observer(WebhookHistoryTable));

View File

@ -0,0 +1,170 @@
import Button from "@docspace/components/button";
import React, { useState, useEffect, useTransition, Suspense } from "react";
import WebhookDialog from "./sub-components/WebhookDialog";
import WebhookInfo from "./sub-components/WebhookInfo";
import WebhooksTable from "./sub-components/WebhooksTable";
import { inject, observer } from "mobx-react";
import styled from "styled-components";
import { WebhookConfigsLoader } from "./sub-components/Loaders";
import { Base } from "@docspace/components/themes";
import { isMobile } from "@docspace/components/utils/device";
import { useTranslation } from "react-i18next";
import { DeleteWebhookDialog } from "./sub-components/DeleteWebhookDialog";
import { NoBoxShadowToast } from "./styled-components";
import toastr from "@docspace/components/toast/toastr";
const MainWrapper = styled.div`
width: 100%;
margin-top: 5px;
.toggleButton {
display: flex;
align-items: center;
}
`;
const ButtonSeating = styled.div`
position: fixed;
z-index: 2;
width: 100vw;
height: 73px;
bottom: 0;
left: 0;
background-color: ${(props) => props.theme.backgroundColor};
display: flex;
justify-content: center;
align-items: center;
`;
ButtonSeating.defaultProps = { theme: Base };
const StyledCreateButton = styled(Button)`
width: calc(100% - 32px);
`;
const Webhooks = (props) => {
const {
loadWebhooks,
addWebhook,
isWebhookExist,
isWebhooksEmpty,
setDocumentTitle,
currentWebhook,
editWebhook,
deleteWebhook,
} = props;
const { t, ready } = useTranslation(["Webhooks", "Common"]);
const [isPending, startTranslation] = useTransition();
setDocumentTitle(t("Webhooks"));
const [isCreateOpened, setIsCreateOpened] = useState(false);
const [isSettingsOpened, setIsSettingsOpened] = useState(false);
const [isDeleteOpened, setIsDeleteOpened] = useState(false);
const closeCreateModal = () => setIsCreateOpened(false);
const openCreateModal = () => setIsCreateOpened(true);
const closeSettingsModal = () => setIsSettingsOpened(false);
const openSettingsModal = () => setIsSettingsOpened(true);
const closeDeleteModal = () => setIsDeleteOpened(false);
const openDeleteModal = () => setIsDeleteOpened(true);
const onCreateWebhook = async (webhookInfo) => {
if (!isWebhookExist(webhookInfo)) {
await addWebhook(webhookInfo);
closeCreateModal();
}
};
const handleWebhookUpdate = async (webhookInfo) => {
await editWebhook(currentWebhook, webhookInfo);
toastr.success(t("WebhookEditedSuccessfully"), <b>{t("Common:Done")}</b>);
};
const handleWebhookDelete = async () => {
await deleteWebhook(currentWebhook);
toastr.success(t("WebhookRemoved"), <b>{t("Common:Done")}</b>);
};
useEffect(() => {
ready && startTranslation(loadWebhooks);
}, [ready]);
return (
<Suspense fallback={<WebhookConfigsLoader />}>
<MainWrapper>
<WebhookInfo />
{isMobile() ? (
<ButtonSeating>
<StyledCreateButton
label={t("CreateWebhook")}
primary
size={"normal"}
onClick={openCreateModal}
/>
</ButtonSeating>
) : (
<Button label={t("CreateWebhook")} primary size={"small"} onClick={openCreateModal} />
)}
{!isWebhooksEmpty && (
<WebhooksTable openSettingsModal={openSettingsModal} openDeleteModal={openDeleteModal} />
)}
<WebhookDialog
visible={isCreateOpened}
onClose={closeCreateModal}
header={t("CreateWebhook")}
onSubmit={onCreateWebhook}
/>
<WebhookDialog
visible={isSettingsOpened}
onClose={closeSettingsModal}
header={t("SettingsWebhook")}
isSettingsModal={true}
webhook={currentWebhook}
onSubmit={handleWebhookUpdate}
/>
<DeleteWebhookDialog
visible={isDeleteOpened}
onClose={closeDeleteModal}
header={t("DeleteWebhookForeverQuestion")}
handleSubmit={handleWebhookDelete}
/>
<NoBoxShadowToast />
</MainWrapper>
</Suspense>
);
};
export default inject(({ webhooksStore, auth }) => {
const {
state,
loadWebhooks,
addWebhook,
isWebhookExist,
isWebhooksEmpty,
currentWebhook,
editWebhook,
deleteWebhook,
} = webhooksStore;
const { setDocumentTitle } = auth;
return {
state,
loadWebhooks,
addWebhook,
isWebhookExist,
isWebhooksEmpty,
setDocumentTitle,
currentWebhook,
editWebhook,
deleteWebhook,
};
})(observer(Webhooks));

View File

@ -0,0 +1,26 @@
import styled, { css } from "styled-components";
export const Hint = styled.div`
box-sizing: border-box;
padding: 8px 12px;
background: ${(props) => (props.backgroundColor ? props.backgroundColor : "#f8f7bf")};
color: ${(props) => (props.color ? props.color : "initial")};
border-radius: 6px;
font-family: "Open Sans";
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px;
position: relative;
z-index: 3;
${(props) =>
props.isTooltip &&
css`
position: absolute;
z-index: 2;
width: 320px;
`}
`;

View File

@ -0,0 +1,8 @@
import Toast from "@docspace/components/toast";
import styled from "styled-components";
export const NoBoxShadowToast = styled(Toast)`
.Toastify__toast {
box-shadow: none;
}
`;

View File

@ -0,0 +1,2 @@
export { Hint } from "./Hint";
export { NoBoxShadowToast } from "./NoBoxShadowToast";

View File

@ -0,0 +1,60 @@
import React, { useEffect } from "react";
import ModalDialog from "@docspace/components/modal-dialog";
import Button from "@docspace/components/button";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
const StyledBodyText = styled.div`
line-height: 20px;
`;
const Footer = styled.div`
width: 100%;
display: flex;
button {
width: 100%;
}
button:first-of-type {
margin-right: 10px;
}
`;
export const DeleteWebhookDialog = ({ visible, onClose, header, handleSubmit }) => {
const onKeyPress = (e) => (e.key === "Esc" || e.key === "Escape") && onClose();
const { t } = useTranslation(["Webhooks", "Common", "EmptyTrashDialog"]);
const cleanUpEvent = () => window.removeEventListener("keyup", onKeyPress);
useEffect(() => {
window.addEventListener("keyup", onKeyPress);
return cleanUpEvent;
});
const handleDeleteClick = () => {
handleSubmit();
onClose();
};
return (
<ModalDialog visible={visible} onClose={onClose} displayType="modal">
<ModalDialog.Header>{header}</ModalDialog.Header>
<ModalDialog.Body>
<StyledBodyText>{t("DeleteHint")}</StyledBodyText>
</ModalDialog.Body>
<ModalDialog.Footer>
<Footer>
<Button
label={t("EmptyTrashDialog:DeleteForeverButton")}
size="normal"
primary={true}
onClick={handleDeleteClick}
/>
<Button label={t("Common:CancelButton")} size="normal" onClick={onClose} />
</Footer>
</ModalDialog.Footer>
</ModalDialog>
);
};

View File

@ -0,0 +1,44 @@
import React from "react";
import styled from "styled-components";
import TextInput from "@docspace/components/text-input";
import Label from "@docspace/components/label";
const StyledLabel = styled(Label)`
display: block;
margin-top: 20px;
line-height: 20px;
input {
margin-top: 4px;
width: 100%;
}
`;
export const LabledInput = ({
label,
placeholder,
value,
onChange,
name,
mask,
hasError,
className,
required = false,
}) => {
return (
<StyledLabel text={label} className={className}>
<TextInput
name={name}
placeholder={placeholder}
tabIndex={1}
value={value}
onChange={onChange}
required={required}
hasError={hasError}
{...(mask ? { mask: mask } : {})}
/>
</StyledLabel>
);
};

View File

@ -0,0 +1,93 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@docspace/common/components/Loaders";
const LoaderWrapper = styled.div`
width: 100%;
.webhookTextLoader {
display: block;
margin-bottom: 24px;
}
.webhookButtonLoader {
display: block;
margin-bottom: 16px;
}
.labelsLoader {
width: 435px;
display: flex;
justify-content: space-between;
}
.iconsLoader {
width: 131px;
display: flex;
justify-content: space-between;
}
.roundedStatusLoader {
border-radius: 10px;
}
`;
const NavContainerLoader = styled.nav`
width: 184px;
display: flex;
justify-content: space-between;
margin-bottom: 20px;
`;
const TableHeaderLoader = styled.header`
display: flex;
justify-content: space-between;
margin-bottom: 33px;
`;
const TableRowLoader = styled.div`
display: flex;
justify-content: space-between;
margin-bottom: 27px;
`;
export const WebhookConfigsLoader = () => {
const RowLoader = () => (
<TableRowLoader>
<Loaders.Rectangle width="888px" height="20px" />
<div className="iconsLoader">
<Loaders.Rectangle width="28px" height="16px" className="roundedStatusLoader" />
<Loaders.Rectangle width="16px" height="16px" />
</div>
</TableRowLoader>
);
return (
<LoaderWrapper>
<NavContainerLoader>
<Loaders.Rectangle width="82px" height="32px" />
<Loaders.Rectangle width="82px" height="32px" />
</NavContainerLoader>
<Loaders.Rectangle width="700px" height="88px" className="webhookTextLoader" />
<Loaders.Rectangle width="159px" height="32px" className="webhookButtonLoader" />
<TableHeaderLoader>
<div className="labelsLoader">
<Loaders.Rectangle width="51px" height="16px" />
<Loaders.Rectangle width="60px" height="16px" />
</div>
<div className="iconsLoader">
<Loaders.Rectangle width="62px" height="16px" />
<Loaders.Rectangle width="16px" height="16px" />
</div>
</TableHeaderLoader>
<RowLoader />
<RowLoader />
<RowLoader />
<RowLoader />
<RowLoader />
</LoaderWrapper>
);
};

View File

@ -0,0 +1,79 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@docspace/common/components/Loaders";
const LoaderWrapper = styled.div`
width: 100%;
.display-block {
display: block;
}
.mb-4 {
margin-bottom: 4px;
}
.mb-5 {
margin-bottom: 5px;
}
.mb-16 {
margin-bottom: 16px;
}
.mb-23 {
margin-bottom: 23px;
}
.mb-24 {
margin-bottom: 24px;
}
.mr-20 {
margin-right: 20px;
}
`;
const DetailsWrapperLoader = styled.div`
margin-top: 20px;
margin-bottom: 20px;
`;
const DetailsItemWrapper = styled.div`
padding: 16px;
margin-right: 40px;
display: inline-block;
`;
export const WebhookDetailsLoader = () => {
const DetailsItemLoader = () => (
<DetailsItemWrapper>
<Loaders.Rectangle width="37px" height="16px" className="mb-5 display-block" />
<Loaders.Rectangle width="180px" height="20px" />
</DetailsItemWrapper>
);
const MessageHeader = () => <Loaders.Rectangle width="130px" height="20px" className="mb-4" />;
return (
<LoaderWrapper>
<DetailsWrapperLoader>
<Loaders.Rectangle width="80px" height="20px" className="mb-24 display-block" />
<DetailsItemLoader />
<DetailsItemLoader />
<DetailsItemLoader />
<DetailsItemLoader />
</DetailsWrapperLoader>
<div className=" mb-23">
<Loaders.Rectangle width="43px" height="32px" className="mr-20" />
<Loaders.Rectangle width="64px" height="32px" />
</div>
<MessageHeader />
<Loaders.Rectangle width="100%" height="212px" className="mb-16" />
<MessageHeader />
<Loaders.Rectangle width="100%" height="469px" />
</LoaderWrapper>
);
};

View File

@ -0,0 +1,68 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@docspace/common/components/Loaders";
const LoaderWrapper = styled.div`
width: 100%;
`;
const NavContainerLoader = styled.nav`
display: flex;
justify-content: space-between;
margin-top: 5px;
margin-bottom: 17px;
`;
const HistoryHeaderLoader = styled.header`
display: flex;
justify-content: space-between;
margin-bottom: 27px;
`;
const HistoryRowWrapper = styled.div`
margin-bottom: 27px;
.historyIconLoader {
display: inline-block;
margin-right: 16px;
}
.historyContentLoader {
display: inline-block;
width: calc(100% - 36px);
}
`;
export const WebhookHistoryLoader = () => {
const HistoryRowLoader = () => (
<HistoryRowWrapper>
<Loaders.Rectangle width="20px" height="20px" className="historyIconLoader" />
<Loaders.Rectangle height="20px" className="historyContentLoader" />
</HistoryRowWrapper>
);
return (
<LoaderWrapper>
<NavContainerLoader>
<Loaders.Rectangle width="118px" height="22px" />
<Loaders.Rectangle width="32px" height="22px" />
</NavContainerLoader>
<HistoryHeaderLoader>
<Loaders.Rectangle width="51px" height="16px" />
<Loaders.Rectangle width="60px" height="16px" />
<Loaders.Rectangle width="60px" height="16px" />
<Loaders.Rectangle width="62px" height="16px" />
<Loaders.Rectangle width="16px" height="16px" />
</HistoryHeaderLoader>
<HistoryRowLoader />
<HistoryRowLoader />
<HistoryRowLoader />
<HistoryRowLoader />
<HistoryRowLoader />
<HistoryRowLoader />
</LoaderWrapper>
);
};

View File

@ -0,0 +1,3 @@
export { WebhookConfigsLoader } from "./WebhookConfigsLoader";
export { WebhookHistoryLoader } from "./WebhookHistoryLoader";
export { WebhookDetailsLoader } from "./WebhookDetailsLoader";

View File

@ -0,0 +1,73 @@
import React, { useState } from "react";
import styled from "styled-components";
import InfoIcon from "PUBLIC_DIR/images/info.react.svg?url";
import RadioButtonGroup from "@docspace/components/radio-button-group";
import Label from "@docspace/components/label";
import { Hint } from "../styled-components";
import { useTranslation } from "react-i18next";
const Header = styled.p`
font-weight: 600;
margin-top: 22px;
margin-bottom: 10px;
display: flex;
align-items: center;
img {
margin-left: 4px;
}
`;
const StyledInfoIcon = styled.img`
:hover {
cursor: pointer;
}
`;
export const SSLVerification = ({ onChange, value }) => {
const [isHintVisible, setIsHintVisible] = useState(false);
const { t } = useTranslation(["Webhooks"]);
const handleOnChange = (e) => {
onChange({ target: { name: e.target.name, value: e.target.value === "true" } });
};
const toggleHint = () => setIsHintVisible((prevIsHintVisible) => !prevIsHintVisible);
return (
<Label
text={
<Header>
{t("SSLVerification")}{" "}
<StyledInfoIcon src={InfoIcon} alt="infoIcon" onClick={toggleHint} />
</Header>
}>
<Hint isTooltip hidden={!isHintVisible} onClick={toggleHint}>
{t("SSLHint")}
</Hint>
<RadioButtonGroup
fontSize="13px"
fontWeight="400"
name="ssl"
onClick={handleOnChange}
options={[
{
label: t("EnableSSL"),
value: "true",
},
{
label: t("DisableSSL"),
value: "false",
},
]}
selected={value ? "true" : "false"}
width="100%"
orientation="vertical"
spacing="8px"
/>
</Label>
);
};

View File

@ -0,0 +1,165 @@
import React, { useState, useEffect, useRef } from "react";
import styled from "styled-components";
import InfoIcon from "PUBLIC_DIR/images/info.react.svg?url";
import Link from "@docspace/components/link";
import Label from "@docspace/components/label";
import { Hint } from "../styled-components";
import PasswordInput from "@docspace/components/password-input";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
const SecretKeyWrapper = styled.div`
.link {
display: inline-block;
margin-top: 6px;
}
.dotted {
text-decoration: underline dotted;
}
`;
const Header = styled.p`
margin-top: 20px;
display: block;
align-items: center;
margin-bottom: 5px;
img {
margin-left: 4px;
}
`;
const StyledInfoIcon = styled.img`
:hover {
cursor: pointer;
}
`;
const SecretKeyInput = (props) => {
const {
isResetVisible,
name,
value,
onChange,
passwordSettings,
isPasswordValid,
setIsPasswordValid,
setIsResetVisible,
webhooksGuideUrl,
passwordInputKey,
} = props;
const [isHintVisible, setIsHintVisible] = useState(false);
const { t } = useTranslation(["Webhooks"]);
const secretKeyInputRef = useRef(null);
const toggleHint = () => setIsHintVisible((prevIsHintVisible) => !prevIsHintVisible);
const handleInputValidation = (isValid) => {
setIsPasswordValid(isValid);
};
const generatePassword = () => {
secretKeyInputRef.current.onGeneratePassword();
};
const handleHintDisapear = () => {
toggleHint();
};
const handleOnChange = (e) => {
onChange({ target: { name, value: e.target.value } });
};
const hideReset = () => {
generatePassword();
setIsResetVisible(false);
};
useEffect(() => {
if (!isResetVisible) {
onChange({ target: { name, value: secretKeyInputRef.current.state.inputValue } });
}
}, [isResetVisible]);
return (
<SecretKeyWrapper>
<Label
text={
<Header>
{t("SecretKey")} <StyledInfoIcon src={InfoIcon} alt="infoIcon" onClick={toggleHint} />
</Header>
}
fontWeight={600}>
<Hint isTooltip hidden={!isHintVisible} onClick={handleHintDisapear}>
{t("SecretKeyHint")} <br />
<Link
type="page"
isHovered
fontWeight={600}
href={webhooksGuideUrl}
target="_blank"
className="link"
color="#333333">
{t("ReadMore")}
</Link>
</Hint>
{isResetVisible && (
<Hint>
{t("SecretKeyWarning")} <br />
<Link
type="action"
fontWeight={600}
isHovered={true}
onClick={hideReset}
className="link"
color="#333333">
{t("ResetKey")}
</Link>
</Hint>
)}
<div hidden={isResetVisible}>
<PasswordInput
onChange={handleOnChange}
inputValue={value}
inputName={name}
placeholder={t("EnterSecretKey")}
onValidateInput={handleInputValidation}
ref={secretKeyInputRef}
hasError={!isPasswordValid}
isDisableTooltip={true}
inputType="password"
isFullWidth={true}
passwordSettings={passwordSettings}
key={passwordInputKey}
/>
<Link
type="action"
fontWeight={600}
isHovered={true}
onClick={generatePassword}
className="link dotted">
{t("Generate")}
</Link>
</div>
</Label>
</SecretKeyWrapper>
);
};
export default inject(({ settingsStore, auth }) => {
const { passwordSettings } = settingsStore;
const { webhooksGuideUrl } = auth.settingsStore;
return {
passwordSettings,
webhooksGuideUrl,
};
})(observer(SecretKeyInput));

View File

@ -0,0 +1,57 @@
import React from "react";
import Badge from "@docspace/components/badge";
import { useTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
const StatusBadge = (props) => {
const { status, theme } = props;
const badgeColorScheme =
status >= 200 && status < 300
? theme.isBase
? {
backgroundColor: "rgba(53, 173, 23, 0.1)",
color: "#35AD17",
}
: {
backgroundColor: "rgba(59, 164, 32, 0.1)",
color: "#3BA420",
}
: theme.isBase
? {
backgroundColor: "rgba(242, 28, 14, 0.1)",
color: "#F21C0E",
}
: {
backgroundColor: "rgba(224, 100, 81, 0.1)",
color: "#E06451",
};
const { t } = useTranslation(["Webhooks"]);
if (status === undefined) {
return;
}
return (
<Badge
backgroundColor={badgeColorScheme.backgroundColor}
color={badgeColorScheme.color}
label={status === 0 ? t("NotSent") : status.toString()}
fontSize="9px"
fontWeight={700}
noHover
/>
);
};
export default inject(({ auth }) => {
const { settingsStore } = auth;
const { theme } = settingsStore;
return {
theme,
};
})(observer(StatusBadge));

View File

@ -0,0 +1,186 @@
import React, { useState, useEffect, useRef } from "react";
import ModalDialog from "@docspace/components/modal-dialog";
import Button from "@docspace/components/button";
import { LabledInput } from "./LabledInput";
import styled from "styled-components";
import { Hint } from "../styled-components";
import { SSLVerification } from "./SSLVerification";
import SecretKeyInput from "./SecretKeyInput";
import { useTranslation } from "react-i18next";
const StyledWebhookForm = styled.form`
margin-top: 7px;
.margin-0 {
margin: 0;
}
`;
const Footer = styled.div`
width: 100%;
display: flex;
button {
width: 100%;
}
button:first-of-type {
margin-right: 10px;
}
`;
function validateUrl(url) {
try {
new URL(url);
} catch (error) {
return false;
}
return true;
}
const WebhookDialog = (props) => {
const { visible, onClose, header, isSettingsModal, onSubmit, webhook } = props;
const [isResetVisible, setIsResetVisible] = useState(isSettingsModal);
const [isPasswordValid, setIsPasswordValid] = useState(false);
const [isValid, setIsValid] = useState({
name: true,
uri: true,
secretKey: true,
});
const { t } = useTranslation(["Webhooks", "Common"]);
const [webhookInfo, setWebhookInfo] = useState({
id: webhook ? webhook.id : 0,
name: webhook ? webhook.name : "",
uri: webhook ? webhook.uri : "",
secretKey: "",
enabled: webhook ? webhook.enabled : true,
ssl: webhook ? webhook.ssl : true,
});
const [passwordInputKey, setPasswordInputKey] = useState(0);
const submitButtonRef = useRef(null);
const onModalClose = () => {
onClose();
isSettingsModal && setIsResetVisible(true);
};
const onInputChange = (e) => {
if (e.target.name) {
!isValid[e.target.name] &&
setIsValid((prevIsValid) => ({ ...prevIsValid, [e.target.name]: true }));
setWebhookInfo((prevWebhookInfo) => ({
...prevWebhookInfo,
[e.target.name]: e.target.value,
}));
}
};
const handleSubmitClick = () => {
const isUrlValid = validateUrl(webhookInfo.uri);
setIsValid(() => ({
uri: isUrlValid,
name: webhookInfo.name !== "",
secretKey: isPasswordValid,
}));
if (isUrlValid && (isPasswordValid || isResetVisible)) {
submitButtonRef.current.click();
}
};
const onFormSubmit = (e) => {
e.preventDefault();
onSubmit(webhookInfo);
setWebhookInfo({
id: webhook ? webhook.id : 0,
name: "",
uri: "",
secretKey: "",
enabled: true,
});
setPasswordInputKey((prevKey) => prevKey + 1);
onModalClose();
};
const cleanUpEvent = () => window.removeEventListener("keyup", onKeyPress);
useEffect(() => {
window.addEventListener("keyup", onKeyPress);
return cleanUpEvent;
}, []);
useEffect(() => {
setWebhookInfo({
id: webhook ? webhook.id : 0,
name: webhook ? webhook.name : "",
uri: webhook ? webhook.uri : "",
secretKey: "",
enabled: webhook ? webhook.enabled : true,
ssl: webhook ? webhook.ssl : true,
});
}, [webhook]);
const onKeyPress = (e) => (e.key === "Esc" || e.key === "Escape") && onModalClose();
return (
<ModalDialog withFooterBorder visible={visible} onClose={onModalClose} displayType="aside">
<ModalDialog.Header>{header}</ModalDialog.Header>
<ModalDialog.Body>
<StyledWebhookForm onSubmit={onFormSubmit}>
{!isSettingsModal && <Hint>{t("WebhookCreationHint")}</Hint>}
<LabledInput
label={t("WebhookName")}
placeholder={t("EnterWebhookName")}
name="name"
value={webhookInfo.name}
onChange={onInputChange}
hasError={!isValid.name}
className={isSettingsModal ? "margin-0" : ""}
required
/>
<LabledInput
label={t("PayloadUrl")}
placeholder={t("EnterUrl")}
name="uri"
value={webhookInfo.uri}
onChange={onInputChange}
hasError={!isValid.uri}
required
/>
<SSLVerification value={webhookInfo.ssl} onChange={onInputChange} />
<SecretKeyInput
isResetVisible={isResetVisible}
name="secretKey"
value={webhookInfo.secretKey}
onChange={onInputChange}
isPasswordValid={isValid.secretKey}
setIsPasswordValid={setIsPasswordValid}
setIsResetVisible={setIsResetVisible}
passwordInputKey={passwordInputKey}
/>
<button type="submit" ref={submitButtonRef} hidden></button>
</StyledWebhookForm>
</ModalDialog.Body>
<ModalDialog.Footer>
<Footer>
<Button
label={isSettingsModal ? t("Common:SaveButton") : t("Common:Create")}
size="normal"
primary={true}
onClick={handleSubmitClick}
/>
<Button label={t("Common:CancelButton")} size="normal" onClick={onModalClose} />
</Footer>
</ModalDialog.Footer>
</ModalDialog>
);
};
export default WebhookDialog;

View File

@ -0,0 +1,64 @@
import React from "react";
import styled from "styled-components";
import { inject, observer } from "mobx-react";
import { Base } from "@docspace/components/themes";
import Link from "@docspace/components/link";
import Text from "@docspace/components/text";
import { useTranslation } from "react-i18next";
const InfoWrapper = styled.div`
margin-bottom: 25px;
`;
const InfoText = styled(Text)`
max-width: 660px;
white-space: break-spaces;
margin: 0 0 8px 0;
line-height: 20px;
color: ${(props) => (props.theme.isBase ? "#657077" : "rgba(255, 255, 255, 0.6)")};
&:hover {
color: ${(props) => (props.theme.isBase ? "#657077" : "rgba(255, 255, 255, 0.6)")};
}
`;
InfoText.defaultProps = { theme: Base };
const StyledGuideLink = styled(Link)`
color: ${(props) => (props.theme.isBase ? "#316DAA" : "#4781D1")};
&:hover {
color: ${(props) => (props.theme.isBase ? "#316DAA" : "#4781D1")};
}
`;
StyledGuideLink.defaultProps = { theme: Base };
const WebhookInfo = (props) => {
const { t } = useTranslation(["Webhooks"]);
const { webhooksGuideUrl } = props;
return (
<InfoWrapper>
<InfoText as="p">{t("WebhooksInfo")}</InfoText>
<StyledGuideLink
fontWeight={600}
isHovered
type="page"
href={webhooksGuideUrl}
target="_blank">
{t("WebhooksGuide")}
</StyledGuideLink>
</InfoWrapper>
);
};
export default inject(({ auth }) => {
const { webhooksGuideUrl } = auth.settingsStore;
return {
webhooksGuideUrl,
};
})(observer(WebhookInfo));

View File

@ -0,0 +1,108 @@
import React, { useState } from "react";
import { inject, observer } from "mobx-react";
import Row from "@docspace/components/row";
import { WebhookRowContent } from "./WebhookRowContent";
import SettingsIcon from "PUBLIC_DIR/images/catalog.settings.react.svg?url";
import HistoryIcon from "PUBLIC_DIR/images/history.react.svg?url";
import DeleteIcon from "PUBLIC_DIR/images/delete.react.svg?url";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
export const WebhookRow = (props) => {
const {
webhook,
sectionWidth,
toggleEnabled,
openSettingsModal,
openDeleteModal,
setCurrentWebhook,
} = props;
const navigate = useNavigate();
const { t } = useTranslation(["Webhooks", "Common"]);
const [isChecked, setIsChecked] = useState(webhook.enabled);
const handleToggleEnabled = () => {
toggleEnabled(webhook);
setIsChecked((prevIsChecked) => !prevIsChecked);
};
const redirectToHistory = () => {
navigate(window.location.pathname + `/${webhook.id}`);
};
const handleRowClick = (e) => {
if (
e.target.closest(".table-container_row-context-menu-wrapper") ||
e.target.closest(".toggleButton") ||
e.target.closest(".row_context-menu-wrapper") ||
e.detail === 0
) {
return;
}
redirectToHistory();
};
const onSettingsOpen = () => {
setCurrentWebhook(webhook);
openSettingsModal();
};
const onDeleteOpen = () => {
setCurrentWebhook(webhook);
openDeleteModal();
};
const contextOptions = [
{
key: "Settings dropdownItem",
label: t("Common:Settings"),
icon: SettingsIcon,
onClick: onSettingsOpen,
},
{
key: "Webhook history dropdownItem",
label: t("WebhookHistory"),
icon: HistoryIcon,
onClick: redirectToHistory,
},
{
key: "Separator dropdownItem",
isSeparator: true,
},
{
key: "Delete webhook dropdownItem",
label: t("DeleteWebhook"),
icon: DeleteIcon,
onClick: onDeleteOpen,
},
];
return (
<>
<Row
sectionWidth={sectionWidth}
key={webhook.id}
data={webhook}
contextOptions={contextOptions}
onClick={handleRowClick}>
<WebhookRowContent
sectionWidth={sectionWidth}
webhook={webhook}
isChecked={isChecked}
handleToggleEnabled={handleToggleEnabled}
/>
</Row>
</>
);
};
export default inject(({ webhooksStore }) => {
const { toggleEnabled, deleteWebhook, editWebhook, setCurrentWebhook } =
webhooksStore;
return { toggleEnabled, deleteWebhook, editWebhook, setCurrentWebhook };
})(observer(WebhookRow));

View File

@ -0,0 +1,73 @@
import React from "react";
import styled from "styled-components";
import Text from "@docspace/components/text";
import RowContent from "@docspace/components/row-content";
import ToggleButton from "@docspace/components/toggle-button";
import StatusBadge from "../../StatusBadge";
import { isMobileOnly } from "react-device-detect";
const StyledRowContent = styled(RowContent)`
display: flex;
padding-bottom: 10px;
.rowMainContainer {
height: 100%;
width: 100%;
}
.mainIcons {
min-width: 76px;
}
`;
const ContentWrapper = styled.div`
display: flex;
flex-direction: column;
justify-items: center;
`;
const ToggleButtonWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: -52px;
`;
const FlexWrapper = styled.div`
display: flex;
`;
export const WebhookRowContent = ({ sectionWidth, webhook, isChecked, handleToggleEnabled }) => {
return (
<StyledRowContent sectionWidth={sectionWidth}>
<ContentWrapper>
<FlexWrapper>
<Text fontWeight={600} fontSize="14px" style={{ marginRight: "8px" }}>
{webhook.name}
</Text>
<StatusBadge status={webhook.status} />
</FlexWrapper>
{!isMobileOnly && (
<Text fontWeight={600} fontSize="12px" color="#A3A9AE">
{webhook.uri}
</Text>
)}
</ContentWrapper>
<ToggleButtonWrapper>
<ToggleButton
className="toggle toggleButton"
id="toggle id"
isChecked={isChecked}
onChange={handleToggleEnabled}
/>
</ToggleButtonWrapper>
</StyledRowContent>
);
};

View File

@ -0,0 +1,53 @@
import React, { useEffect } from "react";
import styled from "styled-components";
import { inject, observer } from "mobx-react";
import { isMobile } from "react-device-detect";
import RowContainer from "@docspace/components/row-container";
import WebhookRow from "./WebhookRow";
const StyledRowContainer = styled(RowContainer)`
margin-top: 16px;
`;
const WebhooksRowView = (props) => {
const { webhooks, sectionWidth, viewAs, setViewAs, openSettingsModal, openDeleteModal } = props;
useEffect(() => {
if (viewAs !== "table" && viewAs !== "row") return;
if (sectionWidth < 1025 || isMobile) {
viewAs !== "row" && setViewAs("row");
} else {
viewAs !== "table" && setViewAs("table");
}
}, [sectionWidth]);
return (
<StyledRowContainer useReactWindow={false}>
{webhooks.map((webhook) => (
<WebhookRow
key={webhook.id}
webhook={webhook}
sectionWidth={sectionWidth}
openSettingsModal={openSettingsModal}
openDeleteModal={openDeleteModal}
/>
))}
</StyledRowContainer>
);
};
export default inject(({ webhooksStore, setup }) => {
const { webhooks } = webhooksStore;
const { viewAs, setViewAs } = setup;
return {
webhooks,
viewAs,
setViewAs,
};
})(observer(WebhooksRowView));

View File

@ -0,0 +1,105 @@
import React, { useState } from "react";
import TableHeader from "@docspace/components/table-container/TableHeader";
import { useTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
const TABLE_VERSION = "5";
const TABLE_COLUMNS = `webhooksConfigColumns_ver-${TABLE_VERSION}`;
const getColumns = (defaultColumns, userId) => {
const storageColumns = localStorage.getItem(`${TABLE_COLUMNS}=${userId}`);
const columns = [];
if (storageColumns) {
const splitColumns = storageColumns.split(",");
for (let col of defaultColumns) {
const column = splitColumns.find((key) => key === col.key);
column ? (col.enable = true) : (col.enable = false);
columns.push(col);
}
return columns;
} else {
return defaultColumns;
}
};
const WebhookTableHeader = (props) => {
const {
userId,
sectionWidth,
tableRef,
columnStorageName,
columnInfoPanelStorageName,
setHideColumns,
} = props;
const { t } = useTranslation(["Webhooks", "Common"]);
const defaultColumns = [
{
key: "Name",
title: t("Common:Name"),
resizable: true,
enable: true,
default: true,
active: true,
minWidth: 150,
onChange: onColumnChange,
},
{
key: "URL",
title: t("URL"),
enable: true,
resizable: true,
onChange: onColumnChange,
},
{
key: "State",
title: t("State"),
enable: true,
resizable: true,
onChange: onColumnChange,
},
];
const [columns, setColumns] = useState(getColumns(defaultColumns, userId));
function onColumnChange(key, e) {
const columnIndex = columns.findIndex((c) => c.key === key);
if (columnIndex === -1) return;
setColumns((prevColumns) =>
prevColumns.map((item, index) =>
index === columnIndex ? { ...item, enable: !item.enable } : item,
),
);
const tableColumns = columns.map((c) => c.enable && c.key);
localStorage.setItem(`${TABLE_COLUMNS}=${userId}`, tableColumns);
}
return (
<TableHeader
checkboxSize="48px"
containerRef={tableRef}
columns={columns}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
sectionWidth={sectionWidth}
checkboxMargin="12px"
showSettings={false}
useReactWindow
setHideColumns={setHideColumns}
infoPanelVisible={false}
/>
);
};
export default inject(({ auth }) => {
return {
userId: auth.userStore.user.id,
};
})(observer(WebhookTableHeader));

View File

@ -0,0 +1,151 @@
import React, { useState } from "react";
import styled from "styled-components";
import TableRow from "@docspace/components/table-container/TableRow";
import TableCell from "@docspace/components/table-container/TableCell";
import Text from "@docspace/components/text";
import ToggleButton from "@docspace/components/toggle-button";
import SettingsIcon from "PUBLIC_DIR/images/catalog.settings.react.svg?url";
import HistoryIcon from "PUBLIC_DIR/images/history.react.svg?url";
import DeleteIcon from "PUBLIC_DIR/images/delete.react.svg?url";
import StatusBadge from "../../StatusBadge";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
const StyledWrapper = styled.div`
display: contents;
`;
const StyledTableRow = styled(TableRow)`
.table-container_cell {
padding-right: 30px;
text-overflow: ellipsis;
}
.mr-8 {
margin-right: 8px;
}
.textOverflow {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
`;
const WebhooksTableRow = (props) => {
const {
webhook,
toggleEnabled,
openSettingsModal,
openDeleteModal,
setCurrentWebhook,
hideColumns,
} = props;
const navigate = useNavigate();
const { t } = useTranslation(["Webhooks", "Common"]);
const [isChecked, setIsChecked] = useState(webhook.enabled);
const redirectToHistory = () => {
navigate(window.location.pathname + `/${webhook.id}`);
};
const handleRowClick = (e) => {
if (
e.target.closest(".checkbox") ||
e.target.closest(".table-container_row-checkbox") ||
e.target.closest(".type-combobox") ||
e.target.closest(".table-container_row-context-menu-wrapper") ||
e.target.closest(".toggleButton") ||
e.detail === 0
) {
return;
}
redirectToHistory();
};
const handleToggleEnabled = () => {
toggleEnabled(webhook);
setIsChecked((prevIsChecked) => !prevIsChecked);
};
const onSettingsOpen = () => {
setCurrentWebhook(webhook);
openSettingsModal();
};
const onDeleteOpen = () => {
setCurrentWebhook(webhook);
openDeleteModal();
};
const contextOptions = [
{
key: "Settings dropdownItem",
label: t("Common:Settings"),
icon: SettingsIcon,
onClick: onSettingsOpen,
},
{
key: "Webhook history dropdownItem",
label: t("WebhookHistory"),
icon: HistoryIcon,
onClick: redirectToHistory,
},
{
key: "Separator dropdownItem",
isSeparator: true,
},
{
key: "Delete webhook dropdownItem",
label: t("DeleteWebhook"),
icon: DeleteIcon,
onClick: onDeleteOpen,
},
];
return (
<>
<StyledWrapper onClick={handleRowClick}>
<StyledTableRow contextOptions={contextOptions} hideColumns={hideColumns}>
<TableCell>
<Text as="span" fontWeight={600} className="mr-8 textOverflow">
{webhook.name}{" "}
</Text>
<StatusBadge status={webhook.status} />
</TableCell>
<TableCell>
<Text
as="span"
fontSize="11px"
color="#A3A9AE"
fontWeight={600}
className="textOverflow">
{webhook.uri}
</Text>
</TableCell>
<TableCell>
<ToggleButton
className="toggle toggleButton"
id="toggle id"
isChecked={isChecked}
onChange={handleToggleEnabled}
/>
</TableCell>
</StyledTableRow>
</StyledWrapper>
</>
);
};
export default inject(({ webhooksStore }) => {
const { toggleEnabled, setCurrentWebhook } = webhooksStore;
return {
toggleEnabled,
setCurrentWebhook,
};
})(observer(WebhooksTableRow));

View File

@ -0,0 +1,115 @@
import React, { useState, useRef, useEffect } from "react";
import { inject, observer } from "mobx-react";
import { isMobile } from "react-device-detect";
import styled from "styled-components";
import TableContainer from "@docspace/components/table-container/TableContainer";
import TableBody from "@docspace/components/table-container/TableBody";
import WebhooksTableRow from "./WebhooksTableRow";
import WebhookTableHeader from "./WebhookTableHeader";
import { Base } from "@docspace/components/themes";
const TableWrapper = styled(TableContainer)`
margin-top: 16px;
.header-container-text {
font-size: 12px;
}
.table-container_header {
position: absolute;
}
.table-list-item {
margin-top: -1px;
&:hover {
cursor: pointer;
background-color: ${(props) => (props.theme.isBase ? "#F8F9F9" : "#282828")};
}
}
`;
TableWrapper.defaultProps = { theme: Base };
const TABLE_VERSION = "5";
const COLUMNS_SIZE = `webhooksConfigColumnsSize_ver-${TABLE_VERSION}`;
const INFO_PANEL_COLUMNS_SIZE = `infoPanelWebhooksConfigColumnsSize_ver-${TABLE_VERSION}`;
const WebhooksTableView = (props) => {
const {
webhooks,
loadWebhooks,
sectionWidth,
viewAs,
setViewAs,
openSettingsModal,
openDeleteModal,
userId,
} = props;
const tableRef = useRef(null);
const [hideColumns, setHideColumns] = useState(false);
useEffect(() => {
if (!sectionWidth) return;
if (sectionWidth < 1025 || isMobile) {
viewAs !== "row" && setViewAs("row");
} else {
viewAs !== "table" && setViewAs("table");
}
}, [sectionWidth]);
const columnStorageName = `${COLUMNS_SIZE}=${userId}`;
const columnInfoPanelStorageName = `${INFO_PANEL_COLUMNS_SIZE}=${userId}`;
return (
<TableWrapper forwardedRef={tableRef} useReactWindow>
<WebhookTableHeader
sectionWidth={sectionWidth}
tableRef={tableRef}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
setHideColumns={setHideColumns}
/>
<TableBody
itemHeight={49}
useReactWindow
infoPanelVisible={false}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
filesLength={webhooks.length}
fetchMoreFiles={loadWebhooks}
hasMoreFiles={false}
itemCount={webhooks.length}>
{webhooks.map((webhook, index) => (
<WebhooksTableRow
key={webhook.id}
webhook={webhook}
index={index}
openSettingsModal={openSettingsModal}
openDeleteModal={openDeleteModal}
hideColumns={hideColumns}
/>
))}
</TableBody>
</TableWrapper>
);
};
export default inject(({ webhooksStore, setup, auth }) => {
const { webhooks, loadWebhooks } = webhooksStore;
const { viewAs, setViewAs } = setup;
const { id: userId } = auth.userStore.user;
return {
webhooks,
viewAs,
setViewAs,
loadWebhooks,
userId,
};
})(observer(WebhooksTableView));

View File

@ -0,0 +1,38 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { Consumer } from "@docspace/components/utils/context";
import WebhooksTableView from "./WebhooksTableView";
import WebhooksRowView from "./WebhooksRowView";
const WebhooksTable = (props) => {
const { viewAs, openSettingsModal, openDeleteModal } = props;
return (
<Consumer>
{(context) =>
viewAs === "table" ? (
<WebhooksTableView
sectionWidth={context.sectionWidth}
openSettingsModal={openSettingsModal}
openDeleteModal={openDeleteModal}
/>
) : (
<WebhooksRowView
sectionWidth={context.sectionWidth}
openSettingsModal={openSettingsModal}
openDeleteModal={openDeleteModal}
/>
)
}
</Consumer>
);
};
export default inject(({ setup }) => {
const { viewAs } = setup;
return {
viewAs,
};
})(observer(WebhooksTable));

View File

@ -1,434 +1,102 @@
import React, { useState, useEffect } from "react";
import { withTranslation } from "react-i18next";
import styled from "styled-components";
import Box from "@docspace/components/box";
import TextInput from "@docspace/components/text-input";
import Textarea from "@docspace/components/textarea";
import Label from "@docspace/components/label";
import Checkbox from "@docspace/components/checkbox";
import Button from "@docspace/components/button";
import ComboBox from "@docspace/components/combobox";
import Heading from "@docspace/components/heading";
import { tablet } from "@docspace/components/utils/device";
import { objectToGetParams, loadScript } from "@docspace/common/utils";
import React, { useEffect, useState } from "react";
import styled, { css } from "styled-components";
import Submenu from "@docspace/components/submenu";
import { inject, observer } from "mobx-react";
import { isMobile } from "react-device-detect";
import BreakpointWarning from "SRC_DIR/components/BreakpointWarning";
import { SortByFieldName } from "../../../../helpers/constants";
import { combineUrl } from "@docspace/common/utils";
import config from "PACKAGE_FILE";
const Controls = styled(Box)`
width: 500px;
display: flex;
flex-direction: column;
gap: 16px;
import { useNavigate } from "react-router-dom";
@media ${tablet} {
width: 100%;
}
import JavascriptSDK from "./JavascriptSDK";
import Webhooks from "./Webhooks";
.label {
min-width: fit-content;
import AppLoader from "@docspace/common/components/AppLoader";
import SSOLoader from "./sub-components/ssoLoader";
import { WebhookConfigsLoader } from "./Webhooks/sub-components/Loaders";
import { useTranslation } from "react-i18next";
import { isMobile, isMobileOnly } from "react-device-detect";
const StyledSubmenu = styled(Submenu)`
.sticky {
margin-top: ${() => (isMobile ? "0" : "4px")};
z-index: 201;
${() =>
isMobileOnly &&
css`
top: 58px;
`}
}
`;
const ControlsGroup = styled(Box)`
display: flex;
flex-direction: column;
gap: 8px;
`;
const DeveloperToolsWrapper = (props) => {
const { loadBaseInfo, developerToolsTab, setTab } = props;
const [currentTab, setCurrentTab] = useState(developerToolsTab);
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const Frame = styled(Box)`
margin-top: 16px;
const { t, ready } = useTranslation(["JavascriptSdk", "Webhooks"]);
> div {
border: 1px dashed gray;
border-radius: 3px;
min-width: 100%;
min-height: 400px;
}
`;
const Buttons = styled(Box)`
margin-top: 16px;
button {
margin-right: 16px;
}
`;
const Container = styled(Box)`
width: 100%;
display: flex;
gap: 16px;
`;
const Preview = styled(Box)`
width: 50%;
flex-direction: row;
.frameStyle {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
`;
const PortalIntegration = (props) => {
const { t, setDocumentTitle } = props;
setDocumentTitle(t("JavascriptSdk"));
const scriptUrl = `${window.location.origin}/static/scripts/api.js`;
const dataSortBy = [
const data = [
{
key: SortByFieldName.ModifiedDate,
label: t("Common:LastModifiedDate"),
default: true,
id: "javascript-sdk",
name: t("JavascriptSdk"),
content: <JavascriptSDK />,
},
{
id: "webhooks",
name: t("Webhooks:Webhooks"),
content: <Webhooks />,
},
{ key: SortByFieldName.Name, label: t("Common:Title") },
{ key: SortByFieldName.Type, label: t("Common:Type") },
{ key: SortByFieldName.Size, label: t("Common:Size") },
{ key: SortByFieldName.CreationDate, label: t("Files:ByCreation") },
{ key: SortByFieldName.Author, label: t("Files:ByAuthor") },
];
const dataSortOrder = [
{ key: "descending", label: t("Descending"), default: true },
{ key: "ascending", label: t("Ascending") },
];
const [config, setConfig] = useState({
width: "100%",
height: "400px",
frameId: "ds-frame",
showHeader: false,
showTitle: true,
showArticle: false,
showFilter: false,
});
const [sortBy, setSortBy] = useState(dataSortBy[0]);
const [sortOrder, setSortOrder] = useState(dataSortOrder[0]);
const [withSubfolders, setWithSubfolders] = useState(false);
const params = objectToGetParams(config);
const frameId = config.frameId || "ds-frame";
const destroyFrame = () => {
DocSpace.destroyFrame();
const load = async () => {
await loadBaseInfo();
setIsLoading(true);
};
const loadFrame = () => {
const script = document.getElementById("integration");
if (script) {
destroyFrame();
script.remove();
useEffect(() => {
const path = location.pathname;
const currentTab = data.findIndex((item) => path.includes(item.id));
if (currentTab !== -1) {
setCurrentTab(currentTab);
setTab(currentTab);
}
const params = objectToGetParams(config);
load();
}, []);
loadScript(`${scriptUrl}${params}`, "integration");
const onSelect = (e) => {
navigate(
combineUrl(
window.DocSpaceConfig?.proxy?.url,
config.homepage,
`/portal-settings/developer-tools/${e.id}`,
),
);
};
const onChangeWidth = (e) => {
setConfig((config) => {
return { ...config, width: e.target.value };
});
};
if (!isLoading && !ready)
return currentTab === 0 ? (
<SSOLoader />
) : currentTab === 1 ? (
<WebhookConfigsLoader />
) : (
<AppLoader />
);
const onChangeHeight = (e) => {
setConfig((config) => {
return { ...config, height: e.target.value };
});
};
const onChangeFolderId = (e) => {
setConfig((config) => {
return { ...config, folder: e.target.value };
});
};
const onChangeFrameId = (e) => {
setConfig((config) => {
return { ...config, frameId: e.target.value };
});
};
const onChangeWithSubfolders = (e) => {
setConfig((config) => {
return { ...config, withSubfolders: !withSubfolders };
});
setWithSubfolders(!withSubfolders);
};
const onChangeSortBy = (item) => {
setConfig((config) => {
return { ...config, sortby: item.key };
});
setSortBy(item);
};
const onChangeSortOrder = (item) => {
setConfig((config) => {
return { ...config, sortorder: item.key };
});
setSortOrder(item);
};
const onChangeFilterType = (item) => {
setConfig((config) => {
return { ...config, filterType: item.key };
});
setFilterType(item);
};
const onChangeDisplayType = (item) => {
setConfig((config) => {
return { ...config, viewAs: item.key };
});
setDisplayType(item);
};
const onChangeShowHeader = (e) => {
setConfig((config) => {
return { ...config, showHeader: !config.showHeader };
});
};
const onChangeShowTitle = () => {
setConfig((config) => {
return { ...config, showTitle: !config.showTitle };
});
};
const onChangeShowArticle = (e) => {
setConfig((config) => {
return { ...config, showArticle: !config.showArticle };
});
};
const onChangeShowFilter = (e) => {
setConfig((config) => {
return { ...config, showFilter: !config.showFilter };
});
};
const onChangeCount = (e) => {
setConfig((config) => {
return { ...config, count: e.target.value };
});
};
const onChangePage = (e) => {
setConfig((config) => {
return { ...config, page: e.target.value };
});
};
const onChangeSearch = (e) => {
setConfig((config) => {
return { ...config, search: e.target.value };
});
};
const onChangeAuthor = (e) => {
setConfig((config) => {
return { ...config, authorType: e.target.value };
});
};
const codeBlock = `<div id="${frameId}">Fallback text</div>\n<script src="${scriptUrl}${params}"></script>`;
return (
<>
{isMobile ? (
<BreakpointWarning sectionName={t("JavascriptSdk")} />
) : (
<Container>
<Controls>
<Heading level={1} size="small">
{t("WindowParameters")}
</Heading>
<ControlsGroup>
<Label className="label" text={t("FrameId")} />
<TextInput
scale={true}
onChange={onChangeFrameId}
placeholder={t("EnterId")}
value={config.frameId}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Width")} />
<TextInput
scale={true}
onChange={onChangeWidth}
placeholder={t("EnterWidth")}
value={config.width}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Height")} />
<TextInput
scale={true}
onChange={onChangeHeight}
placeholder={t("EnterHeight")}
value={config.height}
/>
</ControlsGroup>
<Checkbox
label={t("Header")}
onChange={onChangeShowHeader}
isChecked={config.showHeader}
/>
<Checkbox
label={t("Common:Title")}
onChange={onChangeShowTitle}
isChecked={config.showTitle}
/>
<Checkbox
label={t("Menu")}
onChange={onChangeShowArticle}
isChecked={config.showArticle}
/>
<Checkbox
label={t("Files:Filter")}
onChange={onChangeShowFilter}
isChecked={config.showFilter}
/>
<Heading level={1} size="small">
{t("DataDisplay")}
</Heading>
<ControlsGroup>
<Label className="label" text={t("FolderId")} />
<TextInput
scale={true}
onChange={onChangeFolderId}
placeholder={t("EnterId")}
value={config.folder}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("ItemsCount")} />
<TextInput
scale={true}
onChange={onChangeCount}
placeholder={t("EnterCount")}
value={config.count}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("Page")} />
<TextInput
scale={true}
onChange={onChangePage}
placeholder={t("EnterPage")}
value={config.page}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("SearchTerm")} />
<Box
style={{ flexDirection: "row", display: "flex", gap: "16px" }}
>
<TextInput
scale={true}
onChange={onChangeSearch}
placeholder={t("Common:Search")}
value={config.search}
/>
<Checkbox
label={t("Files:WithSubfolders")}
onChange={onChangeWithSubfolders}
isChecked={withSubfolders}
/>
</Box>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("Files:ByAuthor")} />
<TextInput
scale={true}
onChange={onChangeAuthor}
placeholder={t("Common:EnterName")}
value={config.authorType}
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("Common:SortBy")} />
<ComboBox
onSelect={onChangeSortBy}
options={dataSortBy}
scaled={true}
selectedOption={sortBy}
displaySelectedOption
directionY="top"
/>
</ControlsGroup>
<ControlsGroup>
<Label className="label" text={t("SortOrder")} />
<ComboBox
onSelect={onChangeSortOrder}
options={dataSortOrder}
scaled={true}
selectedOption={sortOrder}
displaySelectedOption
directionY="top"
/>
</ControlsGroup>
</Controls>
<Preview>
<Frame>
<Box id={frameId} className="frameStyle">
{t("Common:Preview")}
</Box>
</Frame>
<Buttons>
<Button
primary
size="normal"
label={t("Common:Preview")}
onClick={loadFrame}
/>
<Button
primary
size="normal"
label={t("Destroy")}
onClick={destroyFrame}
/>
</Buttons>
<Heading level={1} size="xsmall">
{t("CopyWindowCode")}
</Heading>
<Textarea value={codeBlock} />
</Preview>
</Container>
)}
</>
);
return <StyledSubmenu data={data} startSelect={currentTab} onSelect={onSelect} />;
};
export default inject(({ setup, auth }) => {
const { settingsStore, setDocumentTitle } = auth;
const { theme } = settingsStore;
export default inject(({ setup, webhooksStore }) => {
const { initSettings } = setup;
const { developerToolsTab, setTab } = webhooksStore;
return {
theme,
setDocumentTitle,
loadBaseInfo: async () => {
await initSettings();
},
developerToolsTab,
setTab,
};
})(
withTranslation(["JavascriptSdk", "Files", "EmbeddingPanel", "Common"])(
observer(PortalIntegration)
)
);
})(observer(DeveloperToolsWrapper));

View File

@ -0,0 +1,47 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@docspace/common/components/Loaders";
import StyledSettingsSeparator from "SRC_DIR/pages/PortalSettings/StyledSettingsSeparator";
const StyledLoader = styled.div`
.submenu {
display: flex;
gap: 20px;
margin-bottom: 20px;
.item {
width: 72px;
}
}
.description {
max-width: 700px;
margin-bottom: 20px;
}
.category {
margin-top: 24px;
width: 238px;
}
`;
const SSOLoader = (props) => {
const { isToggleSSO } = props;
return (
<StyledLoader>
{!isToggleSSO && (
<div className="submenu">
<Loaders.Rectangle className="item" height="28px" />
<Loaders.Rectangle className="item" height="28px" />
</div>
)}
<Loaders.Rectangle className="description" height="60px" />
<Loaders.Rectangle height="64px" />
<Loaders.Rectangle className="category" height="22px" />
<StyledSettingsSeparator />
<Loaders.Rectangle className="category" height="22px" />
</StyledLoader>
);
};
export default SSOLoader;

View File

@ -281,18 +281,26 @@ export const settingsTree = [
],
},
{
id: "portal-settings_catalog-developer",
id: "portal-settings_catalog-developer-tools",
key: "5",
icon: DeveloperReactSvgUrl,
link: "developer",
link: "developer-tools",
tKey: "DeveloperTools",
isHeader: true,
children: [
{
id: "portal-settings_catalog-developer-tools",
id: "portal-settings_catalog-javascript-sdk",
key: "5-0",
icon: "",
link: "tools",
link: "javascript-sdk",
tKey: "DeveloperTools",
isCategory: true,
},
{
id: "portal-settings_catalog-webhooks",
key: "5-1",
icon: "",
link: "webhooks",
tKey: "DeveloperTools",
isCategory: true,
},

View File

@ -10,85 +10,65 @@ import Error404 from "SRC_DIR/pages/Errors/404";
const PortalSettings = loadable(() => import("../pages/PortalSettings"));
const CustomizationSettings = loadable(() =>
import("../pages/PortalSettings/categories/common/index.js")
import("../pages/PortalSettings/categories/common/index.js"),
);
const LanguageAndTimeZoneSettings = loadable(() =>
import(
"../pages/PortalSettings/categories/common/Customization/language-and-time-zone"
)
import("../pages/PortalSettings/categories/common/Customization/language-and-time-zone"),
);
const WelcomePageSettings = loadable(() =>
import(
"../pages/PortalSettings/categories/common/Customization/welcome-page-settings"
)
import("../pages/PortalSettings/categories/common/Customization/welcome-page-settings"),
);
const DNSSettings = loadable(() =>
import("../pages/PortalSettings/categories/common/Customization/dns-settings")
import("../pages/PortalSettings/categories/common/Customization/dns-settings"),
);
const PortalRenaming = loadable(() =>
import(
"../pages/PortalSettings/categories/common/Customization/portal-renaming"
)
import("../pages/PortalSettings/categories/common/Customization/portal-renaming"),
);
const WhiteLabel = loadable(() =>
import("../pages/PortalSettings/categories/common/Branding/whitelabel")
import("../pages/PortalSettings/categories/common/Branding/whitelabel"),
);
const SecuritySettings = loadable(() =>
import("../pages/PortalSettings/categories/security/index.js")
import("../pages/PortalSettings/categories/security/index.js"),
);
const TfaPage = loadable(() =>
import("../pages/PortalSettings/categories/security/access-portal/tfa")
import("../pages/PortalSettings/categories/security/access-portal/tfa"),
);
const PasswordStrengthPage = loadable(() =>
import(
"../pages/PortalSettings/categories/security/access-portal/passwordStrength"
)
import("../pages/PortalSettings/categories/security/access-portal/passwordStrength"),
);
const TrustedMailPage = loadable(() =>
import(
"../pages/PortalSettings/categories/security/access-portal/trustedMail"
)
import("../pages/PortalSettings/categories/security/access-portal/trustedMail"),
);
const IpSecurityPage = loadable(() =>
import("../pages/PortalSettings/categories/security/access-portal/ipSecurity")
import("../pages/PortalSettings/categories/security/access-portal/ipSecurity"),
);
const AdminMessagePage = loadable(() =>
import(
"../pages/PortalSettings/categories/security/access-portal/adminMessage"
)
import("../pages/PortalSettings/categories/security/access-portal/adminMessage"),
);
const SessionLifetimePage = loadable(() =>
import(
"../pages/PortalSettings/categories/security/access-portal/sessionLifetime"
)
);
const Integration = loadable(() =>
import("../pages/PortalSettings/categories/integration")
);
const Payments = loadable(() =>
import("../pages/PortalSettings/categories/payments")
import("../pages/PortalSettings/categories/security/access-portal/sessionLifetime"),
);
const Integration = loadable(() => import("../pages/PortalSettings/categories/integration"));
const Payments = loadable(() => import("../pages/PortalSettings/categories/payments"));
const ThirdParty = loadable(() =>
import(
"../pages/PortalSettings/categories/integration/ThirdPartyServicesSettings"
)
import("../pages/PortalSettings/categories/integration/ThirdPartyServicesSettings"),
);
const SingleSignOn = loadable(() =>
import("../pages/PortalSettings/categories/integration/SingleSignOn")
import("../pages/PortalSettings/categories/integration/SingleSignOn"),
);
const DeveloperTools = loadable(() =>
import("../pages/PortalSettings/categories/developer-tools/index.js")
import("../pages/PortalSettings/categories/developer-tools/index.js"),
);
const Backup = loadable(() =>
import("../pages/PortalSettings/categories/data-management/index")
const WebhookHistory = loadable(() =>
import("../pages/PortalSettings/categories/developer-tools/Webhooks/WebhookHistory"),
);
const DeleteDataPage = loadable(() =>
import("../pages/PortalSettings/categories/delete-data")
const WebhookDetails = loadable(() =>
import("../pages/PortalSettings/categories/developer-tools/Webhooks/WebhookEventDetails"),
);
const Backup = loadable(() => import("../pages/PortalSettings/categories/data-management/index"));
const DeleteDataPage = loadable(() => import("../pages/PortalSettings/categories/delete-data"));
const RestoreBackup = loadable(() =>
import(
"../pages/PortalSettings/categories/data-management/backup/restore-backup/index"
)
import("../pages/PortalSettings/categories/data-management/backup/restore-backup/index"),
);
const PortalSettingsRoutes = {
@ -199,13 +179,25 @@ const PortalSettingsRoutes = {
element: <Payments />,
},
{
path: "developer",
element: <Navigate to="developer/tools" />,
path: "developer-tools",
element: <Navigate to="javascript-sdk" />,
},
{
path: "developer/tools",
path: "developer-tools/javascript-sdk",
element: <DeveloperTools />,
},
{
path: "developer-tools/webhooks",
element: <DeveloperTools />,
},
{
path: "developer-tools/webhooks/:id",
element: <WebhookHistory />,
},
{
path: "developer-tools/webhooks/:id/:eventId",
element: <WebhookDetails />,
},
{
path: "backup",
element: <Navigate to="backup/data-backup" />,

View File

@ -0,0 +1,223 @@
import {
createWebhook,
getAllWebhooks,
getWebhooksJournal,
removeWebhook,
retryWebhook,
retryWebhooks,
toggleEnabledWebhook,
updateWebhook,
} from "@docspace/common/api/settings";
import { makeAutoObservable, runInAction } from "mobx";
class WebhooksStore {
webhooks = [];
checkedEventIds = [];
historyFilters = null;
historyItems = [];
startIndex = 0;
totalItems = 0;
developerToolsTab = 0;
currentWebhook = {};
eventDetails = {};
FETCH_COUNT = 100;
constructor() {
makeAutoObservable(this);
}
setCurrentWebhook = (webhook) => {
this.currentWebhook = webhook;
};
setTab = (tabIndex) => {
this.developerToolsTab = tabIndex;
};
loadWebhooks = async () => {
try {
const webhooksData = await getAllWebhooks();
runInAction(() => {
this.webhooks = webhooksData.map((data) => ({
id: data.configs.id,
name: data.configs.name,
uri: data.configs.uri,
secretKey: data.configs.secretKey,
enabled: data.configs.enabled,
ssl: data.configs.ssl,
status: data.status,
}));
});
} catch (error) {
console.error(error);
}
};
addWebhook = async (webhook) => {
const webhookData = await createWebhook(
webhook.name,
webhook.uri,
webhook.secretKey,
webhook.ssl,
);
this.webhooks = [
...this.webhooks,
{
id: webhookData.id,
uri: webhookData.uri,
name: webhookData.name,
enabled: webhookData.enabled,
secretKey: webhookData.secretKey,
ssl: webhookData.ssl,
},
];
};
isWebhookExist = (desiredWebhook) => {
return this.webhooks.some((webhook) => webhook.id === desiredWebhook.id);
};
toggleEnabled = async (desiredWebhook) => {
await toggleEnabledWebhook(desiredWebhook);
const index = this.webhooks.findIndex((webhook) => webhook.id === desiredWebhook.id);
this.webhooks[index].enabled = !this.webhooks[index].enabled;
};
deleteWebhook = async (webhook) => {
await removeWebhook(webhook.id);
this.webhooks = this.webhooks.filter((currentWebhook) => currentWebhook.id !== webhook.id);
};
editWebhook = async (prevWebhook, webhookInfo) => {
await updateWebhook(
prevWebhook.id,
webhookInfo.name,
webhookInfo.uri,
webhookInfo.secretKey || prevWebhook.secretKey,
webhookInfo.ssl,
);
this.webhooks = this.webhooks.map((webhook) =>
webhook.id === prevWebhook.id ? { ...prevWebhook, ...webhookInfo } : webhook,
);
};
retryWebhookEvent = async (id) => {
return await retryWebhook(id);
};
retryWebhookEvents = async (ids) => {
return await retryWebhooks(ids);
};
fetchHistoryItems = async (params) => {
this.totalItems = 0;
this.startIndex = 0;
const count = params.count ? params.count : this.FETCH_COUNT;
const historyData = await getWebhooksJournal({
...params,
startIndex: this.startIndex,
count: count,
});
runInAction(() => {
this.startIndex = count;
this.historyItems = historyData.items;
this.totalItems = historyData.total;
});
};
fetchMoreItems = async (params) => {
const count = params.count ? params.count : this.FETCH_COUNT;
const historyData = await getWebhooksJournal({
...params,
startIndex: this.startIndex,
count: count,
});
runInAction(() => {
this.startIndex = this.startIndex + count;
this.historyItems = [...this.historyItems, ...historyData.items];
});
};
fetchEventData = async (eventId) => {
const data = await getWebhooksJournal({ eventId });
this.eventDetails = data.items[0];
};
get hasMoreItems() {
return this.totalItems > this.startIndex;
}
get isWebhooksEmpty() {
return this.webhooks.length === 0;
}
setHistoryFilters = (filters) => {
this.historyFilters = filters;
};
clearHistoryFilters = () => {
this.historyFilters = null;
};
clearDate = () => {
this.historyFilters = { ...this.historyFilters, deliveryDate: null };
};
unselectStatus = (statusCode) => {
this.historyFilters = {
...this.historyFilters,
status: this.historyFilters.status.filter((item) => item !== statusCode),
};
};
formatFilters = (filters) => {
const params = {};
if (filters.deliveryDate !== null) {
params.deliveryFrom =
filters.deliveryDate.format("YYYY-MM-DD") + "T" + filters.deliveryFrom.format("HH:mm:ss");
params.deliveryTo =
filters.deliveryDate.format("YYYY-MM-DD") + "T" + filters.deliveryTo.format("HH:mm:ss");
}
const statusEnum = {
"Not sent": 1,
"2XX": 2,
"3XX": 4,
"4XX": 8,
"5XX": 16,
};
if (filters.status.length > 0) {
const statusFlag = filters.status.reduce(
(sum, currentValue) => sum + statusEnum[currentValue],
0,
);
params.groupStatus = statusFlag;
}
return params;
};
toggleEventId = (id) => {
this.checkedEventIds = this.checkedEventIds.includes(id)
? this.checkedEventIds.filter((checkedId) => checkedId !== id)
: [...this.checkedEventIds, id];
};
isIdChecked = (id) => {
return this.checkedEventIds.includes(id);
};
checkAllIds = () => {
this.checkedEventIds = this.historyItems.map((event) => event.id);
};
emptyCheckedIds = () => {
this.checkedEventIds = [];
};
get areAllIdsChecked() {
return this.checkedEventIds.length === this.historyItems.length;
}
get isIndeterminate() {
return this.checkedEventIds.length > 0 && !this.areAllIdsChecked;
}
get isGroupMenuVisible() {
return this.checkedEventIds.length !== 0;
}
}
export default WebhooksStore;

View File

@ -35,6 +35,7 @@ import AccessRightsStore from "./AccessRightsStore";
import TableStore from "./TableStore";
import CreateEditRoomStore from "./CreateEditRoomStore";
import WebhooksStore from "./WebhooksStore";
import ClientLoadingStore from "./ClientLoadingStore";
const oformsStore = new OformsStore(authStore);
@ -66,13 +67,10 @@ const filesStore = new FilesStore(
settingsStore,
thirdPartyStore,
accessRightsStore,
clientLoadingStore
clientLoadingStore,
);
const mediaViewerDataStore = new MediaViewerDataStore(
filesStore,
settingsStore
);
const mediaViewerDataStore = new MediaViewerDataStore(filesStore, settingsStore);
const secondaryProgressDataStore = new SecondaryProgressDataStore();
const primaryProgressDataStore = new PrimaryProgressDataStore();
const versionHistoryStore = new VersionHistoryStore(filesStore);
@ -82,15 +80,10 @@ const dialogsStore = new DialogsStore(
treeFoldersStore,
filesStore,
selectedFolderStore,
versionHistoryStore
versionHistoryStore,
);
const peopleStore = new PeopleStore(
authStore,
setupStore,
accessRightsStore,
dialogsStore
);
const peopleStore = new PeopleStore(authStore, setupStore, accessRightsStore, dialogsStore);
const uploadDataStore = new UploadDataStore(
authStore,
@ -100,7 +93,7 @@ const uploadDataStore = new UploadDataStore(
secondaryProgressDataStore,
primaryProgressDataStore,
dialogsStore,
settingsStore
settingsStore,
);
const filesActionsStore = new FilesActionsStore(
@ -113,7 +106,7 @@ const filesActionsStore = new FilesActionsStore(
dialogsStore,
mediaViewerDataStore,
accessRightsStore,
clientLoadingStore
clientLoadingStore,
);
const contextOptionsStore = new ContextOptionsStore(
@ -126,7 +119,7 @@ const contextOptionsStore = new ContextOptionsStore(
uploadDataStore,
versionHistoryStore,
settingsStore,
selectedFolderStore
selectedFolderStore,
);
const hotkeyStore = new HotkeyStore(
@ -135,7 +128,7 @@ const hotkeyStore = new HotkeyStore(
settingsStore,
filesActionsStore,
treeFoldersStore,
uploadDataStore
uploadDataStore,
);
const profileActionsStore = new ProfileActionsStore(
@ -143,7 +136,7 @@ const profileActionsStore = new ProfileActionsStore(
filesStore,
peopleStore,
treeFoldersStore,
selectedFolderStore
selectedFolderStore,
);
const tableStore = new TableStore(authStore, treeFoldersStore);
@ -164,9 +157,11 @@ const createEditRoomStore = new CreateEditRoomStore(
authStore.settingsStore,
authStore.infoPanelStore,
authStore.currentQuotaStore,
clientLoadingStore
clientLoadingStore,
);
const webhooksStore = new WebhooksStore();
const store = {
auth: authStore,
payments: paymentStore,
@ -202,6 +197,8 @@ const store = {
accessRightsStore,
createEditRoomStore,
webhooksStore,
clientLoadingStore,
};

View File

@ -26,12 +26,7 @@ export function getPortalPasswordSettings(confirmKey = null) {
return request(options);
}
export function setPortalPasswordSettings(
minLength,
upperCase,
digits,
specSymbols
) {
export function setPortalPasswordSettings(minLength, upperCase, digits, specSymbols) {
return request({
method: "put",
url: "/settings/security/password",
@ -225,13 +220,7 @@ export function restoreWhiteLabelSettings(isDefault) {
});
}
export function setCompanyInfoSettings(
address,
companyName,
email,
phone,
site
) {
export function setCompanyInfoSettings(address, companyName, email, phone, site) {
const data = {
settings: { address, companyName, email, phone, site },
};
@ -267,7 +256,7 @@ export function getCustomSchemaList() {
export function setAdditionalResources(
feedbackAndSupportEnabled,
videoGuidesEnabled,
helpCenterEnabled
helpCenterEnabled,
) {
const data = {
settings: {
@ -314,7 +303,7 @@ export function setCustomSchema(
regDateCaption,
groupHeadCaption,
guestCaption,
guestsCaption
guestsCaption,
) {
const data = {
userCaption,
@ -393,14 +382,7 @@ export function getMachineName(confirmKey = null) {
return request(options);
}
export function setPortalOwner(
email,
hash,
lng,
timeZone,
confirmKey = null,
analytics
) {
export function setPortalOwner(email, hash, lng, timeZone, confirmKey = null, analytics) {
const options = {
method: "put",
url: "/settings/wizard/complete",
@ -731,6 +713,85 @@ export function removeActiveSession(eventId) {
});
}
export function createWebhook(name, uri, secretKey, ssl) {
return request({
method: "post",
url: `/settings/webhook`,
data: { name, uri, secretKey, ssl },
});
}
export function getAllWebhooks() {
return request({
method: "get",
url: `/settings/webhook`,
});
}
export function updateWebhook(id, name, uri, secretKey, ssl) {
return request({
method: "put",
url: `/settings/webhook`,
data: { id, name, uri, secretKey, ssl },
});
}
export function toggleEnabledWebhook(webhook) {
return request({
method: "put",
url: `/settings/webhook`,
data: {
id: webhook.id,
name: webhook.name,
uri: webhook.uri,
secretKey: webhook.secretKey,
enabled: !webhook.enabled,
},
});
}
export function removeWebhook(id) {
return request({
method: "delete",
url: `/settings/webhook/${id}`,
});
}
export function getWebhooksJournal(props) {
const { configId, eventId, count, startIndex, deliveryFrom, deliveryTo, groupStatus } = props;
const params = {};
configId && (params.configId = configId);
eventId && (params.eventId = eventId);
count && (params.count = count);
startIndex && (params.startIndex = startIndex);
deliveryFrom && (params.deliveryFrom = deliveryFrom);
deliveryTo && (params.deliveryTo = deliveryTo);
groupStatus && (params.groupStatus = groupStatus);
return request({
method: "get",
url: "/settings/webhooks/log?",
params,
});
}
export function retryWebhook(webhookId) {
return request({
method: "put",
url: `/settings/webhook/${webhookId}/retry`,
});
}
export function retryWebhooks(webhooksIds) {
return request({
method: "put",
url: `/settings/webhook/retry`,
data: { Ids: webhooksIds },
});
}
export function muteRoomNotification(id, isMute) {
const options = {
method: "post",

View File

@ -94,6 +94,7 @@ const StyledArticle = styled.article`
@media ${mobile} {
height: 100% !important;
margin-top: 32px;
}
@media ${hugeMobile} {

View File

@ -5,6 +5,7 @@ import Loaders from "@docspace/common/components/Loaders";
import { isTablet as isTabletUtils } from "@docspace/components/utils/device";
import { Link } from "react-router-dom";
import { isTablet, isMobileOnly } from "react-device-detect";
import { isMobile } from "@docspace/components/utils/device";
import { inject, observer } from "mobx-react";
import {
StyledArticleHeader,
@ -25,9 +26,10 @@ const ArticleHeader = ({
}) => {
const navigate = useNavigate();
const isTabletView = (isTabletUtils() || isTablet) && !isMobileOnly;
const isTabletView =
(isTabletUtils() || isTablet) && !isMobileOnly && !isMobile();
const onLogoClick = () => {
onLogoClickAction && onLogoClickAction();
navigate("/");
};
@ -39,7 +41,7 @@ const ArticleHeader = ({
? getLogoFromPath(whiteLabelLogoUrls[0].path.dark)
: getLogoFromPath(whiteLabelLogoUrls[0].path.light);
if (isMobileOnly) return <></>;
if (isMobileOnly || isMobile()) return <></>;
return (
<StyledArticleHeader showText={showText} {...rest}>
{isTabletView && isBurgerLoading ? (

View File

@ -5,7 +5,10 @@ import Avatar from "@docspace/components/avatar";
import Text from "@docspace/components/text";
import ContextMenuButton from "@docspace/components/context-menu-button";
import ContextMenu from "@docspace/components/context-menu";
import { isTablet as isTabletUtils } from "@docspace/components/utils/device";
import {
isTablet as isTabletUtils,
isMobile as isMobileUtils,
} from "@docspace/components/utils/device";
import { isTablet, isMobileOnly } from "react-device-detect";
import {
StyledArticleProfile,
@ -22,7 +25,8 @@ const ArticleProfile = (props) => {
const ref = useRef(null);
const menuRef = useRef(null);
const isTabletView = (isTabletUtils() || isTablet) && !isMobileOnly;
const isTabletView =
(isTabletUtils() || isTablet) && !isMobileOnly && !isMobileUtils();
const avatarSize = isTabletView ? "min" : "base";
const userRole = getUserRole(user);
@ -48,6 +52,8 @@ const ArticleProfile = (props) => {
const userAvatar = user.hasAvatar ? user.avatar : DefaultUserPhotoPngUrl;
if (!isMobileOnly && isMobileUtils()) return <></>;
return (
<StyledProfileWrapper showText={showText}>
<StyledArticleProfile showText={showText} tablet={isTabletView}>

View File

@ -2,12 +2,7 @@ import { makeAutoObservable } from "mobx";
import api from "../api";
import { combineUrl, setCookie, getCookie } from "../utils";
import FirebaseHelper from "../utils/firebase";
import {
ThemeKeys,
COOKIE_EXPIRATION_YEAR,
LANGUAGE,
TenantStatus,
} from "../constants";
import { ThemeKeys, COOKIE_EXPIRATION_YEAR, LANGUAGE, TenantStatus } from "../constants";
import { version } from "../package.json";
import SocketIOHelper from "../utils/socket";
import { Dark, Base } from "@docspace/components/themes";
@ -37,8 +32,7 @@ class SettingsStore {
? window.RendererProcessVariable?.theme?.type === "dark"
? Dark
: Base
: window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
: window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
? Dark
: Base;
trustedDomains = [];
@ -283,6 +277,10 @@ class SettingsStore {
return `${this.helpLink}/administration/docspace-settings.aspx#AutoBackup`;
}
get webhooksGuideUrl() {
return `${this.helpLink}/administration/docspace-webhooks.aspx`;
}
get wizardCompleted() {
return this.isLoaded && !this.wizardToken;
}
@ -323,10 +321,7 @@ class SettingsStore {
else newSettings = await api.settings.getSettings(true);
if (window["AscDesktopEditor"] !== undefined || this.personal) {
const dp = combineUrl(
window.DocSpaceConfig?.proxy?.url,
"/products/files/"
);
const dp = combineUrl(window.DocSpaceConfig?.proxy?.url, "/products/files/");
this.setDefaultPage(dp);
}
@ -336,7 +331,7 @@ class SettingsStore {
key,
key === "defaultPage"
? combineUrl(window.DocSpaceConfig?.proxy?.url, newSettings[key])
: newSettings[key]
: newSettings[key],
);
if (key === "culture") {
if (newSettings.wizardToken) return;
@ -396,7 +391,7 @@ class SettingsStore {
this.getPortalSettings(),
this.getAppearanceTheme(),
this.getWhiteLabelLogoUrls(),
this.getBuildVersionInfo()
this.getBuildVersionInfo(),
);
await Promise.all(requests);
@ -432,12 +427,12 @@ class SettingsStore {
setAdditionalResources = async (
feedbackAndSupportEnabled,
videoGuidesEnabled,
helpCenterEnabled
helpCenterEnabled,
) => {
return await api.settings.setAdditionalResources(
feedbackAndSupportEnabled,
videoGuidesEnabled,
helpCenterEnabled
helpCenterEnabled,
);
};
@ -484,13 +479,7 @@ class SettingsStore {
};
setCompanyInfoSettings = async (address, companyName, email, phone, site) => {
return api.settings.setCompanyInfoSettings(
address,
companyName,
email,
phone,
site
);
return api.settings.setCompanyInfoSettings(address, companyName, email, phone, site);
};
setLogoUrl = (url) => {
@ -549,15 +538,11 @@ class SettingsStore {
};
getLoginLink = (token, code) => {
return combineUrl(
window.DocSpaceConfig?.proxy?.url,
`/login.ashx?p=${token}&code=${code}`
);
return combineUrl(window.DocSpaceConfig?.proxy?.url, `/login.ashx?p=${token}&code=${code}`);
};
setModuleInfo = (homepage, productId) => {
if (this.homepage === homepage || this.currentProductId === productId)
return;
if (this.homepage === homepage || this.currentProductId === productId) return;
console.log(`setModuleInfo('${homepage}', '${productId}')`);
@ -603,17 +588,12 @@ class SettingsStore {
this.setPasswordSettings(settings);
};
setPortalPasswordSettings = async (
minLength,
upperCase,
digits,
specSymbols
) => {
setPortalPasswordSettings = async (minLength, upperCase, digits, specSymbols) => {
const settings = await api.settings.setPortalPasswordSettings(
minLength,
upperCase,
digits,
specSymbols
specSymbols,
);
this.setPasswordSettings(settings);
};
@ -680,8 +660,7 @@ class SettingsStore {
...versionInfo,
};
if (!this.buildVersionInfo.documentServer)
this.buildVersionInfo.documentServer = "6.4.1";
if (!this.buildVersionInfo.documentServer) this.buildVersionInfo.documentServer = "6.4.1";
};
setTheme = (key) => {
@ -699,8 +678,7 @@ class SettingsStore {
case ThemeKeys.SystemStr:
default:
theme =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
? ThemeKeys.DarkStr
: ThemeKeys.BaseStr;
}

View File

@ -5,6 +5,8 @@ import {
MainButtonTheme,
CatalogItemTheme,
CalendarTheme,
DateItemTheme,
RoundButtonTheme,
BadgeTheme,
SubmenuTextTheme,
SubmenuItemLabelTheme,
@ -200,6 +202,24 @@ const ColorTheme = forwardRef(
/>
);
}
case ThemeType.RoundButton: {
return (
<RoundButtonTheme
{...props}
$currentColorScheme={currentColorScheme}
ref={ref}
/>
);
}
case ThemeType.DateItem: {
return (
<DateItemTheme
{...props}
$currentColorScheme={currentColorScheme}
ref={ref}
/>
);
}
case ThemeType.ComboButton: {
return (
<ComboButtonTheme

View File

@ -3,7 +3,6 @@ export const ThemeType = {
MainButton: "mainButton",
CatalogItem: "catalogItem",
Badge: "badge",
Calendar: 'calendar',
SubmenuText: "submenuText",
SubmenuItemLabel: "submenuItemLabel",
ToggleButton: "toggleButton",
@ -14,6 +13,8 @@ export const ThemeType = {
FilterBlockItemTag: "filterBlockItemTag",
IconWrapper: "iconWrapper",
Calendar: "calendar",
DateItem: "dateItem",
RoundButton: "RoundButton",
VersionBadge: "versionBadge",
Textarea: "textarea",
InputBlock: "inputBlock",

View File

@ -1,48 +1,15 @@
import styled, { css } from 'styled-components';
import styled, { css } from "styled-components";
import {
Container,
DateItem,
CurrentDateItem,
RoundButton,
} from '@docspace/components/calendar/styled-components';
HeaderActionIcon,
} from "@docspace/components/calendar/styled-components";
const getDefaultStyles = ({ $currentColorScheme }) =>
$currentColorScheme &&
css`
${CurrentDateItem} {
background: ${$currentColorScheme.main.accent};
:hover {
background-color: ${$currentColorScheme.main.accent};
}
:focus {
background-color: ${$currentColorScheme.main.accent};
}
}
${DateItem} {
color: ${(props) =>
props.disabled
? props.theme.calendar.disabledColor
: props.focused
? $currentColorScheme.main.accent
: props.theme.calendar.color};
border-color: ${(props) => (props.focused ? $currentColorScheme.main.accent : 'transparent')};
:focus {
color: ${$currentColorScheme.main.accent};
border-color: ${$currentColorScheme.main.accent};
}
}
${RoundButton} {
:hover {
outline: ${(props) =>
props.disabled
? `1px solid ${props.theme.calendar.outlineColor}`
: `2px solid ${$currentColorScheme.main.accent}`};
span {
border-color: ${(props) =>
props.disabled ? props.theme.calendar.disabledArrow : $currentColorScheme.main.accent};
}
}
${HeaderActionIcon} {
border-color: ${$currentColorScheme.main.accent};
}
`;

View File

@ -0,0 +1,28 @@
import styled, { css } from "styled-components";
import { DateItem } from "@docspace/components/calendar/styled-components";
const getDefaultStyles = ({ $currentColorScheme }) =>
$currentColorScheme &&
css`
${(props) =>
props.isCurrent &&
css`
background: ${$currentColorScheme.main.accent};
:hover {
background-color: ${$currentColorScheme.main.accent};
}
:focus {
background-color: ${$currentColorScheme.main.accent};
}
`}
color: ${(props) =>
props.disabled
? props.theme.calendar.disabledColor
: props.focused
? $currentColorScheme.main.accent
: props.theme.calendar.color};
border-color: ${(props) => (props.focused ? $currentColorScheme.main.accent : "transparent")};
`;
export default styled(DateItem)(getDefaultStyles);

View File

@ -6,7 +6,11 @@ export { default as CatalogItemTheme } from "./catalogItem";
export { default as BadgeTheme } from "./badge";
export { default as CalendarTheme } from './calendar'
export { default as CalendarTheme } from "./calendar";
export { default as RoundButtonTheme } from "./roundButton";
export { default as DateItemTheme } from "./dateItem";
export { default as SubmenuTextTheme } from "./submenuText";

View File

@ -0,0 +1,19 @@
import styled, { css } from "styled-components";
import { RoundButton } from "@docspace/components/calendar/styled-components";
const getDefaultStyles = ({ $currentColorScheme }) =>
$currentColorScheme &&
css`
:hover {
outline: ${(props) =>
props.disabled
? `1px solid ${props.theme.calendar.outlineColor}`
: `2px solid ${$currentColorScheme.main.accent}`};
span {
border-color: ${(props) =>
props.disabled ? props.theme.calendar.disabledArrow : $currentColorScheme.main.accent};
}
}
`;
export default styled(RoundButton)(getDefaultStyles);

View File

@ -18,6 +18,8 @@ const Calendar = ({
style,
initialDate,
onChange,
isMobile,
forwardedRef,
}) => {
moment.locale(locale);
@ -41,7 +43,7 @@ const Calendar = ({
);
}
initialDate.startOf("day");
setSelectedDate(initialDate);
// setSelectedDate(initialDate);
setObservedDate(initialDate);
}, []);
@ -51,6 +53,8 @@ const Calendar = ({
id={id}
className={className}
style={style}
isMobile={isMobile}
ref={forwardedRef}
>
{selectedScene === 0 ? (
<Days
@ -61,6 +65,7 @@ const Calendar = ({
handleDateChange={handleDateChange}
minDate={minDate}
maxDate={maxDate}
isMobile={isMobile}
/>
) : selectedScene === 1 ? (
<Months
@ -70,6 +75,7 @@ const Calendar = ({
selectedDate={selectedDate}
minDate={minDate}
maxDate={maxDate}
isMobile={isMobile}
/>
) : (
<Years
@ -79,6 +85,7 @@ const Calendar = ({
selectedDate={selectedDate}
minDate={minDate}
maxDate={maxDate}
isMobile={isMobile}
/>
)}
</ColorTheme>

View File

@ -1,4 +1,4 @@
import styled from "styled-components";
import styled, { css } from "styled-components";
import Base from "../../themes/base";
export const ArrowIcon = styled.span`
@ -7,6 +7,21 @@ export const ArrowIcon = styled.span`
border-bottom: 2px solid;
width: 5px;
height: 5px;
${(props) =>
props.next &&
css`
transform: rotate(-45deg);
top: 11.5px;
left: 12.5px;
`}
${(props) =>
props.previous &&
css`
transform: rotate(135deg);
top: 14px;
left: 12.5px;
`}
`;
ArrowIcon.defaultProps = { theme: Base };

View File

@ -0,0 +1,18 @@
import styled from "styled-components";
export const CalendarContainer = styled.div`
display: grid;
row-gap: ${(props) => (props.big ? "26.7px" : "9px")};
column-gap: ${(props) =>
props.big
? props.isMobile
? "8%"
: "41.3px"
: props.isMobile
? "2%"
: "14px"};
grid-template-columns: ${(props) =>
props.big ? "repeat(4, 1fr)" : "repeat(7, 1fr)"};
box-sizing: border-box;
padding: ${(props) => (props.big ? "14px 6px 6px 6px" : "0 6px")};
`;

View File

@ -1,12 +1,13 @@
import styled from "styled-components";
export const Container = styled.div`
width: 432px;
height: 446px;
box-sizing: border-box;
padding: 30px 28px 28px 28px;
width: ${(props) => (props.isMobile ? "100%" : "432px")};
height: ${(props) => (props.isMobile ? "420px" : "446px")};
padding: ${(props) => (props.isMobile ? "16px" : "30px 28px 28px 28px")};
box-shadow: 0px 12px 40px rgba(4, 15, 27, 0.12);
border-radius: 6px;
border-radius: 6px;
z-index: 320;
background-color: ${(props) => props.theme.backgroundColor};
`;

View File

@ -1,18 +0,0 @@
import styled from "styled-components";
import { DateItem } from "./DateItem";
export const CurrentDateItem = styled(DateItem)`
background: #4781D1;
border-radius: 50%;
color: white;
:hover{
background-color: #4781D1;
color: white;
}
:focus{
background-color: #4781D1;
color: white;
}
`;

View File

@ -1,4 +1,4 @@
import styled from "styled-components";
import styled, { css } from "styled-components";
import Base from "../../themes/base";
export const DateItem = styled.button`
@ -6,15 +6,8 @@ export const DateItem = styled.button`
font-weight: 600;
font-size: 16px;
border-radius: 50%;
color: ${(props) =>
props.disabled
? props.theme.calendar.disabledColor
: props.focused
? "#4781D1"
: props.theme.calendar.color};
border: 2px solid;
border-color: ${(props) => (props.focused ? "#4781D1" : "transparent")};
background-color: transparent;
width: ${(props) => (props.big ? "60px" : "40px")};
@ -27,9 +20,37 @@ export const DateItem = styled.button`
:hover {
cursor: ${(props) => (props.disabled ? "default" : "pointer")};
background: ${(props) =>
props.disabled
? "transparent"
: props.theme.calendar.onHoverBackground};
props.disabled ? "transparent" : props.theme.calendar.onHoverBackground};
}
${(props) =>
props.isCurrent &&
css`
color: white !important;
:hover {
color: white !important;
}
:focus {
color: white !important;
}
`}
${(props) =>
props.isSecondary &&
css`
color: ${(props) =>
props.disabled
? props.theme.calendar.disabledColor
: props.theme.calendar.pastColor} !important;
:hover {
cursor: ${(props) => (props.disabled ? "auto" : "pointer")};
color: ${(props) =>
props.disabled
? props.theme.calendar.disabledColor
: props.theme.calendar.pastColor} !important;
}
`}
`;
DateItem.defaultProps = { theme: Base };

View File

@ -1,10 +0,0 @@
import styled from "styled-components";
export const DaysContainer = styled.div`
display: grid;
row-gap: 9px;
column-gap: 14px;
grid-template-columns: repeat(7, 1fr);
box-sizing: border-box;
padding: 0 6px;
`

View File

@ -0,0 +1,9 @@
import styled from "styled-components";
import { ArrowIcon } from "./ArrowIcon";
export const HeaderActionIcon = styled(ArrowIcon)`
transform: rotate(225deg);
top: 11px;
right: -14.29px;
border-color: ${(props) => props.theme.calendar.outlineColor};
`;

View File

@ -8,4 +8,4 @@ export const HeaderContainer = styled.header`
align-items: center;
box-sizing: border-box;
margin-bottom: 16px;
`;
`;

View File

@ -1,10 +0,0 @@
import styled from "styled-components";
export const MonthsContainer = styled.div`
display: grid;
row-gap: 26.7px;
column-gap: 41.3px;
grid-template-columns: repeat(4, 1fr);
box-sizing: border-box;
padding: 14px 6px 6px 6px;
`

View File

@ -1,8 +0,0 @@
import styled from "styled-components";
import { ArrowIcon } from "./ArrowIcon";
export const NextIcon = styled(ArrowIcon)`
transform: rotate(-45deg);
top: 11.5px;
left: 12.5px;
`;

View File

@ -1,8 +0,0 @@
import styled from "styled-components";
import { ArrowIcon } from "./ArrowIcon";
export const PrevIcon = styled(ArrowIcon)`
transform: rotate(135deg);
top: 14px;
left: 12.5px;
`;

Some files were not shown because too many files have changed in this diff Show More