Merge branch 'develop' of github.com:ONLYOFFICE/DocSpace into feature/branding
This commit is contained in:
commit
720b98c894
14
build/Jenkinsfile
vendored
14
build/Jenkinsfile
vendored
@ -43,20 +43,20 @@ pipeline {
|
||||
stages {
|
||||
stage('Components') {
|
||||
steps {
|
||||
sh "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}/packages/asc-web-components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true"
|
||||
sh "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}/packages/components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true"
|
||||
}
|
||||
post {
|
||||
success {
|
||||
junit 'packages/asc-web-components/junit.xml'
|
||||
junit 'packages/components/junit.xml'
|
||||
publishHTML target: [
|
||||
allowMissing : false,
|
||||
alwaysLinkToLastBuild: false,
|
||||
keepAll : true,
|
||||
reportDir : 'packages/asc-web-components/coverage/lcov-report',
|
||||
reportDir : 'packages/components/coverage/lcov-report',
|
||||
reportFiles : 'index.html',
|
||||
reportName : 'Unix Test Report'
|
||||
]
|
||||
publishCoverage adapters: [coberturaAdapter('packages/asc-web-components/coverage/cobertura-coverage.xml')]
|
||||
publishCoverage adapters: [coberturaAdapter('packages/components/coverage/cobertura-coverage.xml')]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,16 +72,16 @@ pipeline {
|
||||
stages {
|
||||
stage('Components') {
|
||||
steps {
|
||||
bat "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}\\packages\\asc-web-components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true"
|
||||
bat "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}\\packages\\components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true"
|
||||
}
|
||||
post {
|
||||
success {
|
||||
junit 'packages\\asc-web-components\\junit.xml'
|
||||
junit 'packages\\components\\junit.xml'
|
||||
publishHTML target: [
|
||||
allowMissing : false,
|
||||
alwaysLinkToLastBuild: false,
|
||||
keepAll : true,
|
||||
reportDir : 'packages\\asc-web-components\\coverage\\lcov-report',
|
||||
reportDir : 'packages\\components\\coverage\\lcov-report',
|
||||
reportFiles : 'index.html',
|
||||
reportName : 'Windows Test Report'
|
||||
]
|
||||
|
@ -10,26 +10,31 @@ if %errorlevel% == 0 (
|
||||
call start\stop.bat nopause
|
||||
dotnet build ..\asc.web.slnf /fl1 /flp1:logfile=asc.web.log;verbosity=normal
|
||||
echo.
|
||||
)
|
||||
|
||||
if %errorlevel% == 0 (
|
||||
|
||||
echo install nodejs projects dependencies...
|
||||
echo.
|
||||
|
||||
|
||||
for /R "scripts\" %%f in (*.bat) do (
|
||||
echo Run script %%~nxf...
|
||||
echo.
|
||||
call scripts\%%~nxf
|
||||
)
|
||||
)
|
||||
|
||||
echo.
|
||||
|
||||
if %errorlevel% == 0 (
|
||||
call start\start.bat nopause
|
||||
)
|
||||
|
||||
echo.
|
||||
|
||||
if "%1"=="nopause" goto end
|
||||
pause
|
||||
|
||||
)
|
||||
|
||||
|
||||
:end
|
@ -1,5 +1,6 @@
|
||||
@echo "MIGRATIONS"
|
||||
@echo off
|
||||
|
||||
PUSHD %~dp0..\common\Tools\Migration.Creator
|
||||
dotnet run --project Migration.Creator.csproj
|
||||
PUSHD %~dp0..\common\Tools\ASC.Migration.Creator
|
||||
dotnet run --project ASC.Migration.Creator.csproj
|
||||
pause
|
@ -1,9 +1,11 @@
|
||||
@echo off
|
||||
|
||||
PUSHD %~dp0..\..
|
||||
setlocal EnableDelayedExpansion
|
||||
PUSHD %~dp0..
|
||||
call runasadmin.bat "%~dpnx0"
|
||||
|
||||
if %errorlevel% == 0 (
|
||||
PUSHD %~dp0..\..
|
||||
setlocal EnableDelayedExpansion
|
||||
for /R "build\run\" %%f in (*.bat) do (
|
||||
call build\run\%%~nxf
|
||||
echo service create "Onlyoffice%%~nf"
|
||||
|
@ -18,6 +18,7 @@
|
||||
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="6.0.2" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="6.0.5" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.1.0" />
|
||||
|
103
common/ASC.Api.Core/Auth/BasicAuthHandler.cs
Normal file
103
common/ASC.Api.Core/Auth/BasicAuthHandler.cs
Normal file
@ -0,0 +1,103 @@
|
||||
// (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 System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using SecurityContext = ASC.Core.SecurityContext;
|
||||
|
||||
namespace ASC.Api.Core.Auth;
|
||||
|
||||
[Scope]
|
||||
public class BasicAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
private readonly UserManager _userManager;
|
||||
private readonly SecurityContext _securityContext;
|
||||
private readonly PasswordHasher _passwordHasher;
|
||||
|
||||
public BasicAuthHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock
|
||||
) : base(options, logger, encoder, clock)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public BasicAuthHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
UserManager userManager,
|
||||
SecurityContext securityContext,
|
||||
PasswordHasher passwordHasher) : this(options, logger, encoder, clock)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_securityContext = securityContext;
|
||||
_passwordHasher = passwordHasher;
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
Response.Headers.Add("WWW-Authenticate", "Basic");
|
||||
|
||||
if (!Request.Headers.ContainsKey("Authorization"))
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail("Authorization header missing."));
|
||||
}
|
||||
|
||||
// Get authorization key
|
||||
var authorizationHeader = Request.Headers["Authorization"].ToString();
|
||||
var authHeaderRegex = new Regex(@"Basic (.*)");
|
||||
|
||||
if (!authHeaderRegex.IsMatch(authorizationHeader))
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail("Authorization code not formatted properly."));
|
||||
}
|
||||
|
||||
var authBase64 = Encoding.UTF8.GetString(Convert.FromBase64String(authHeaderRegex.Replace(authorizationHeader, "$1")));
|
||||
var authSplit = authBase64.Split(Convert.ToChar(":"), 2);
|
||||
var authUsername = authSplit[0];
|
||||
var authPassword = authSplit.Length > 1 ? authSplit[1] : throw new Exception("Unable to get password");
|
||||
|
||||
try
|
||||
{
|
||||
var userInfo = _userManager.GetUserByEmail(authUsername);
|
||||
var passwordHash = _passwordHasher.GetClientPassword(authPassword);
|
||||
|
||||
_securityContext.AuthenticateMe(userInfo.Email, passwordHash);
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail("The username or password is not correct."));
|
||||
}
|
||||
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, Scheme.Name)));
|
||||
}
|
||||
}
|
@ -31,9 +31,9 @@ namespace ASC.Api.Core.Auth;
|
||||
[Scope]
|
||||
public class CookieAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
private readonly AuthorizationHelper _authorizationHelper;
|
||||
private readonly SecurityContext _securityContext;
|
||||
private readonly CookiesManager _cookiesManager;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public CookieAuthHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
@ -42,31 +42,59 @@ public class CookieAuthHandler : AuthenticationHandler<AuthenticationSchemeOptio
|
||||
ISystemClock clock)
|
||||
: base(options, logger, encoder, clock) { }
|
||||
|
||||
public CookieAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock,
|
||||
AuthorizationHelper authorizationHelper,
|
||||
public CookieAuthHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
SecurityContext securityContext,
|
||||
CookiesManager cookiesManager)
|
||||
CookiesManager cookiesManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: this(options, logger, encoder, clock)
|
||||
{
|
||||
_authorizationHelper = authorizationHelper;
|
||||
_securityContext = securityContext;
|
||||
_cookiesManager = cookiesManager;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var result = _authorizationHelper.ProcessBasicAuthorization(out _);
|
||||
if (!result)
|
||||
try
|
||||
{
|
||||
_securityContext.Logout();
|
||||
_cookiesManager.ClearCookies(CookiesType.AuthKey);
|
||||
_cookiesManager.ClearCookies(CookiesType.SocketIO);
|
||||
var authorization = _httpContextAccessor.HttpContext.Request.Cookies["asc_auth_key"] ?? _httpContextAccessor.HttpContext.Request.Headers["Authorization"].ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(authorization))
|
||||
{
|
||||
throw new AuthenticationException(nameof(HttpStatusCode.Unauthorized));
|
||||
}
|
||||
|
||||
authorization = authorization.Trim();
|
||||
|
||||
if (0 <= authorization.IndexOf("Bearer", 0))
|
||||
{
|
||||
authorization = authorization.Substring("Bearer ".Length);
|
||||
}
|
||||
|
||||
if (!_securityContext.AuthenticateMe(authorization))
|
||||
{
|
||||
throw new AuthenticationException(nameof(HttpStatusCode.Unauthorized));
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(nameof(HttpStatusCode.Unauthorized))));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!_securityContext.IsAuthenticated)
|
||||
{
|
||||
_securityContext.Logout();
|
||||
_cookiesManager.ClearCookies(CookiesType.AuthKey);
|
||||
_cookiesManager.ClearCookies(CookiesType.SocketIO);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(
|
||||
result ?
|
||||
AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)) :
|
||||
AuthenticateResult.Fail(new AuthenticationException(nameof(HttpStatusCode.Unauthorized))));
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, Scheme.Name)));
|
||||
}
|
||||
}
|
@ -24,6 +24,11 @@
|
||||
// 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 System.IdentityModel.Tokens.Jwt;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
using JsonConverter = System.Text.Json.Serialization.JsonConverter;
|
||||
|
||||
namespace ASC.Api.Core;
|
||||
@ -102,13 +107,13 @@ public abstract class BaseStartup
|
||||
services.AddSingleton(jsonOptions);
|
||||
|
||||
DIHelper.AddControllers();
|
||||
DIHelper.TryAdd<DisposeMiddleware>();
|
||||
DIHelper.TryAdd<CultureMiddleware>();
|
||||
DIHelper.TryAdd<IpSecurityFilter>();
|
||||
DIHelper.TryAdd<PaymentFilter>();
|
||||
DIHelper.TryAdd<ProductSecurityFilter>();
|
||||
DIHelper.TryAdd<TenantStatusFilter>();
|
||||
DIHelper.TryAdd<ConfirmAuthHandler>();
|
||||
DIHelper.TryAdd<BasicAuthHandler>();
|
||||
DIHelper.TryAdd<CookieAuthHandler>();
|
||||
DIHelper.TryAdd<WebhooksGlobalFilterAttribute>();
|
||||
|
||||
@ -147,16 +152,72 @@ public abstract class BaseStartup
|
||||
config.OutputFormatters.Add(new XmlOutputFormatter());
|
||||
});
|
||||
|
||||
var authBuilder = services.AddAuthentication("cookie")
|
||||
.AddScheme<AuthenticationSchemeOptions, CookieAuthHandler>("cookie", a => { });
|
||||
|
||||
if (ConfirmAddScheme)
|
||||
var authBuilder = services.AddAuthentication(options =>
|
||||
{
|
||||
authBuilder.AddScheme<AuthenticationSchemeOptions, ConfirmAuthHandler>("confirm", a => { });
|
||||
}
|
||||
options.DefaultScheme = "MultiAuthSchemes";
|
||||
options.DefaultChallengeScheme = "MultiAuthSchemes";
|
||||
}).AddScheme<AuthenticationSchemeOptions, CookieAuthHandler>(CookieAuthenticationDefaults.AuthenticationScheme, a => { })
|
||||
.AddScheme<AuthenticationSchemeOptions, BasicAuthHandler>("Basic", a => { })
|
||||
.AddScheme<AuthenticationSchemeOptions, ConfirmAuthHandler>("confirm", a => { })
|
||||
.AddJwtBearer("Bearer", options =>
|
||||
{
|
||||
options.Authority = _configuration["core:oidc:authority"];
|
||||
options.IncludeErrorDetails = true;
|
||||
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateAudience = false
|
||||
};
|
||||
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnTokenValidated = ctx =>
|
||||
{
|
||||
using var scope = ctx.HttpContext.RequestServices.CreateScope();
|
||||
|
||||
var securityContext = scope.ServiceProvider.GetService<ASC.Core.SecurityContext>();
|
||||
|
||||
var claimUserId = ctx.Principal.FindFirstValue("userId");
|
||||
|
||||
if (String.IsNullOrEmpty(claimUserId))
|
||||
{
|
||||
throw new Exception("Claim 'UserId' is not present in claim list");
|
||||
}
|
||||
|
||||
var userId = new Guid(claimUserId);
|
||||
|
||||
securityContext.AuthenticateMeWithoutCookie(userId, ctx.Principal.Claims.ToList());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
})
|
||||
.AddPolicyScheme("MultiAuthSchemes", JwtBearerDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
|
||||
options.ForwardDefaultSelector = context =>
|
||||
{
|
||||
var authorizationHeader = context.Request.Headers[HeaderNames.Authorization].FirstOrDefault();
|
||||
|
||||
if (String.IsNullOrEmpty(authorizationHeader)) return CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
|
||||
if (authorizationHeader.StartsWith("Basic ")) return "Basic";
|
||||
|
||||
if (authorizationHeader.StartsWith("Bearer "))
|
||||
{
|
||||
var token = authorizationHeader.Substring("Bearer ".Length).Trim();
|
||||
var jwtHandler = new JwtSecurityTokenHandler();
|
||||
|
||||
return (jwtHandler.CanReadToken(token) && jwtHandler.ReadJwtToken(token).Issuer.Equals(_configuration["core:oidc:authority"]))
|
||||
? JwtBearerDefaults.AuthenticationScheme : CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
}
|
||||
|
||||
return CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
};
|
||||
});
|
||||
|
||||
services.AddAutoMapper(GetAutoMapperProfileAssemblies());
|
||||
|
||||
|
||||
if (!_hostEnvironment.IsDevelopment())
|
||||
{
|
||||
services.AddStartupTask<WarmupServicesStartupTask>()
|
||||
@ -189,8 +250,6 @@ public abstract class BaseStartup
|
||||
|
||||
app.UseCultureMiddleware();
|
||||
|
||||
app.UseDisposeMiddleware();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapCustom();
|
||||
|
@ -24,8 +24,6 @@
|
||||
// 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
|
||||
|
||||
global using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
global using System.Collections.Concurrent;
|
||||
global using System.ComponentModel;
|
||||
global using System.Globalization;
|
||||
global using System.Linq.Expressions;
|
||||
@ -36,6 +34,7 @@ global using System.Runtime.Serialization;
|
||||
global using System.Security;
|
||||
global using System.Security.Authentication;
|
||||
global using System.Security.Claims;
|
||||
global using System.Text;
|
||||
global using System.Text.Encodings.Web;
|
||||
global using System.Text.Json;
|
||||
global using System.Text.Json.Serialization;
|
||||
@ -56,8 +55,8 @@ global using ASC.AuditTrail.Types;
|
||||
global using ASC.Common;
|
||||
global using ASC.Common.Caching;
|
||||
global using ASC.Common.DependencyInjection;
|
||||
global using ASC.Common.Log;
|
||||
global using ASC.Common.Logging;
|
||||
global using ASC.Common.Mapping;
|
||||
global using ASC.Common.Notify.Engine;
|
||||
global using ASC.Common.Threading;
|
||||
global using ASC.Common.Utils;
|
||||
@ -119,11 +118,14 @@ global using Microsoft.AspNetCore.WebUtilities;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
global using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
global using Microsoft.Extensions.Hosting;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using Microsoft.Extensions.Options;
|
||||
global using Microsoft.Extensions.Primitives;
|
||||
global using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
global using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
global using Newtonsoft.Json;
|
||||
global using Newtonsoft.Json.Serialization;
|
||||
|
@ -24,6 +24,8 @@
|
||||
// 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 CallContext = ASC.Common.Notify.Engine.CallContext;
|
||||
|
||||
namespace ASC.Api.Core.Middleware;
|
||||
|
||||
[Scope]
|
||||
|
@ -24,8 +24,6 @@
|
||||
// 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 System.Text;
|
||||
|
||||
namespace ASC.Api.Core.Middleware;
|
||||
|
||||
[Scope]
|
||||
@ -34,12 +32,14 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable
|
||||
private readonly MemoryStream _stream;
|
||||
private Stream _bodyStream;
|
||||
private readonly IWebhookPublisher _webhookPublisher;
|
||||
private readonly ILogger<WebhooksGlobalFilterAttribute> _logger;
|
||||
private static readonly List<string> _methodList = new List<string> { "POST", "UPDATE", "DELETE" };
|
||||
|
||||
public WebhooksGlobalFilterAttribute(IWebhookPublisher webhookPublisher)
|
||||
public WebhooksGlobalFilterAttribute(IWebhookPublisher webhookPublisher, ILogger<WebhooksGlobalFilterAttribute> logger)
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
_webhookPublisher = webhookPublisher;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
@ -68,11 +68,18 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable
|
||||
await _stream.CopyToAsync(_bodyStream);
|
||||
context.HttpContext.Response.Body = _bodyStream;
|
||||
|
||||
var (method, routePattern) = GetData(context.HttpContext);
|
||||
try
|
||||
{
|
||||
var (method, routePattern) = GetData(context.HttpContext);
|
||||
|
||||
var resultContent = Encoding.UTF8.GetString(_stream.ToArray());
|
||||
var eventName = $"method: {method}, route: {routePattern}";
|
||||
_webhookPublisher.Publish(eventName, resultContent);
|
||||
var resultContent = Encoding.UTF8.GetString(_stream.ToArray());
|
||||
|
||||
await _webhookPublisher.PublishAsync(method, routePattern, resultContent);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorWithException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,6 @@ public class DistributedTask
|
||||
Publication(this);
|
||||
}
|
||||
|
||||
[Obsolete("GetProperty<T> is deprecated, please use indexer this[propName] instead.")]
|
||||
public T GetProperty<T>(string propName)
|
||||
{
|
||||
if (!_props.TryGetValue(propName, out var propValue))
|
||||
@ -88,8 +87,7 @@ public class DistributedTask
|
||||
return JsonSerializer.Deserialize<T>(propValue);
|
||||
}
|
||||
|
||||
[Obsolete("SetProperty is deprecated, please use indexer this[propName] = propValue instead.")]
|
||||
public void SetProperty(string propName, object propValue)
|
||||
public void SetProperty<T>(string propName, T propValue)
|
||||
{
|
||||
_props[propName] = JsonSerializer.Serialize(propValue);
|
||||
}
|
||||
|
@ -1,93 +0,0 @@
|
||||
// (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
|
||||
|
||||
namespace ASC.Common.Web;
|
||||
|
||||
public class DisposableHttpContext : IDisposable
|
||||
{
|
||||
private const string Key = "disposable.key";
|
||||
|
||||
public object this[string key]
|
||||
{
|
||||
get => Items.ContainsKey(key) ? Items[key] : null;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
if (value is not IDisposable)
|
||||
{
|
||||
throw new ArgumentException("Only IDisposable may be added!");
|
||||
}
|
||||
|
||||
Items[key] = (IDisposable)value;
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, IDisposable> Items
|
||||
{
|
||||
get
|
||||
{
|
||||
var table = (Dictionary<string, IDisposable>)_context.Items[Key];
|
||||
|
||||
if (table == null)
|
||||
{
|
||||
table = new Dictionary<string, IDisposable>(1);
|
||||
_context.Items.Add(Key, table);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HttpContext _context;
|
||||
private bool _isDisposed;
|
||||
|
||||
public DisposableHttpContext(HttpContext ctx)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ctx);
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
foreach (var item in Items.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -97,9 +97,10 @@ public class SecurityContext
|
||||
|
||||
public bool AuthenticateMe(string cookie)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(cookie))
|
||||
{
|
||||
if (string.IsNullOrEmpty(cookie)) return false;
|
||||
|
||||
if (!_cookieStorage.DecryptCookie(cookie, out var tenant, out var userid, out var indexTenant, out var expire, out var indexUser, out var loginEventId))
|
||||
{
|
||||
if (cookie.Equals("Bearer", StringComparison.InvariantCulture))
|
||||
{
|
||||
var ipFrom = string.Empty;
|
||||
@ -115,54 +116,6 @@ public class SecurityContext
|
||||
}
|
||||
_logger.InformationEmptyBearer(ipFrom, address);
|
||||
}
|
||||
else if (_cookieStorage.DecryptCookie(cookie, out var tenant, out var userid, out var indexTenant, out var expire, out var indexUser, out var loginEventId))
|
||||
{
|
||||
if (tenant != _tenantManager.GetCurrentTenant().Id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var settingsTenant = _tenantCookieSettingsHelper.GetForTenant(tenant);
|
||||
if (indexTenant != settingsTenant.Index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expire != DateTime.MaxValue && expire < DateTime.UtcNow)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var settingsUser = _tenantCookieSettingsHelper.GetForUser(userid);
|
||||
if (indexUser != settingsUser.Index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var settingLoginEvents = _dbLoginEventsManager.GetLoginEventIds(tenant, userid).Result; // remove Result
|
||||
if (loginEventId != 0 && !settingLoginEvents.Contains(loginEventId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthenticateMeWithoutCookie(new UserAccount(new UserInfo { Id = userid }, tenant, _userFormatter));
|
||||
return true;
|
||||
}
|
||||
catch (InvalidCredentialException ice)
|
||||
{
|
||||
_logger.AuthenticateDebug(cookie, tenant, userid, ice);
|
||||
}
|
||||
catch (SecurityException se)
|
||||
{
|
||||
_logger.AuthenticateDebug(cookie, tenant, userid, se);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logger.AuthenticateError(cookie, tenant, userid, err);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var ipFrom = string.Empty;
|
||||
@ -179,7 +132,57 @@ public class SecurityContext
|
||||
|
||||
_logger.WarningCanNotDecrypt(cookie, ipFrom, address);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tenant != _tenantManager.GetCurrentTenant().Id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var settingsTenant = _tenantCookieSettingsHelper.GetForTenant(tenant);
|
||||
|
||||
if (indexTenant != settingsTenant.Index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expire != DateTime.MaxValue && expire < DateTime.UtcNow)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var settingsUser = _tenantCookieSettingsHelper.GetForUser(userid);
|
||||
if (indexUser != settingsUser.Index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var settingLoginEvents = _dbLoginEventsManager.GetLoginEventIds(tenant, userid).Result; // remove Result
|
||||
if (loginEventId != 0 && !settingLoginEvents.Contains(loginEventId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthenticateMeWithoutCookie(new UserAccount(new UserInfo { Id = userid }, tenant, _userFormatter));
|
||||
return true;
|
||||
}
|
||||
catch (InvalidCredentialException ice)
|
||||
{
|
||||
_logger.AuthenticateDebug(cookie, tenant, userid, ice);
|
||||
}
|
||||
catch (SecurityException se)
|
||||
{
|
||||
_logger.AuthenticateDebug(cookie, tenant, userid, se);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logger.AuthenticateError(cookie, tenant, userid, err);
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -24,6 +24,8 @@
|
||||
// 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.Builders;
|
||||
|
||||
namespace ASC.Core.Common.EF.Model;
|
||||
|
||||
public class ModelBuilderWrapper
|
||||
@ -75,6 +77,11 @@ public class ModelBuilderWrapper
|
||||
return this;
|
||||
}
|
||||
|
||||
public EntityTypeBuilder<T> Entity<T>() where T : class
|
||||
{
|
||||
return ModelBuilder.Entity<T>();
|
||||
}
|
||||
|
||||
public void AddDbFunction()
|
||||
{
|
||||
ModelBuilder
|
||||
|
@ -30,117 +30,186 @@ namespace ASC.Webhooks.Core;
|
||||
public class DbWorker
|
||||
{
|
||||
private readonly IDbContextFactory<WebhooksDbContext> _dbContextFactory;
|
||||
private readonly TenantManager _tenantManager;
|
||||
public DbWorker(IDbContextFactory<WebhooksDbContext> dbContextFactory, TenantManager tenantManager)
|
||||
private readonly TenantManager _tenantManager;
|
||||
private readonly AuthContext _authContext;
|
||||
|
||||
private int Tenant
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tenantManager.GetCurrentTenant().Id;
|
||||
}
|
||||
}
|
||||
|
||||
public DbWorker(IDbContextFactory<WebhooksDbContext> dbContextFactory, TenantManager tenantManager, AuthContext authContext)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_tenantManager = tenantManager;
|
||||
}
|
||||
public void AddWebhookConfig(WebhooksConfig webhooksConfig)
|
||||
{
|
||||
webhooksConfig.TenantId = _tenantManager.GetCurrentTenant().Id;
|
||||
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
_tenantManager = tenantManager;
|
||||
_authContext = authContext;
|
||||
}
|
||||
|
||||
var addObj = webhooksDbContext.WebhooksConfigs.Where(it =>
|
||||
it.SecretKey == webhooksConfig.SecretKey &&
|
||||
it.TenantId == webhooksConfig.TenantId &&
|
||||
it.Uri == webhooksConfig.Uri).FirstOrDefault();
|
||||
|
||||
if (addObj != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
webhooksDbContext.WebhooksConfigs.Add(webhooksConfig);
|
||||
webhooksDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
public int ConfigsNumber()
|
||||
public async Task<WebhooksConfig> AddWebhookConfig(string name, string uri, string secretKey)
|
||||
{
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
return webhooksDbContext.WebhooksConfigs.Count();
|
||||
|
||||
var toAdd = new WebhooksConfig { TenantId = Tenant, Uri = uri, SecretKey = secretKey, Name = name };
|
||||
toAdd = await webhooksDbContext.AddOrUpdateAsync(r => r.WebhooksConfigs, toAdd);
|
||||
await webhooksDbContext.SaveChangesAsync();
|
||||
|
||||
return toAdd;
|
||||
}
|
||||
|
||||
public List<WebhooksLog> GetTenantWebhooks()
|
||||
public async IAsyncEnumerable<WebhooksConfig> GetTenantWebhooks()
|
||||
{
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
var q = webhooksDbContext.WebhooksConfigs
|
||||
.AsNoTracking()
|
||||
.Where(it => it.TenantId == Tenant)
|
||||
.AsAsyncEnumerable();
|
||||
|
||||
await foreach (var webhook in q)
|
||||
{
|
||||
yield return webhook;
|
||||
}
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<WebhooksConfig> GetWebhookConfigs()
|
||||
{
|
||||
var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
return webhooksDbContext.WebhooksConfigs
|
||||
.Where(t => t.TenantId == Tenant)
|
||||
.AsAsyncEnumerable();
|
||||
}
|
||||
|
||||
public async Task<WebhooksConfig> UpdateWebhookConfig(int id, string name, string uri, string key, bool? enabled)
|
||||
{
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
var updateObj = await webhooksDbContext.WebhooksConfigs
|
||||
.Where(it => it.TenantId == Tenant && it.Id == id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (updateObj != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(uri))
|
||||
{
|
||||
updateObj.Uri = uri;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
updateObj.Name = name;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
updateObj.SecretKey = key;
|
||||
}
|
||||
|
||||
if (enabled.HasValue)
|
||||
{
|
||||
updateObj.Enabled = enabled.Value;
|
||||
}
|
||||
|
||||
webhooksDbContext.WebhooksConfigs.Update(updateObj);
|
||||
await webhooksDbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return updateObj;
|
||||
}
|
||||
|
||||
public async Task<WebhooksConfig> RemoveWebhookConfig(int id)
|
||||
{
|
||||
var tenant = _tenantManager.GetCurrentTenant().Id;
|
||||
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
return webhooksDbContext.WebhooksLogs.Where(it => it.TenantId == tenant)
|
||||
.Select(t => new WebhooksLog
|
||||
{
|
||||
Uid = t.Uid,
|
||||
CreationTime = t.CreationTime,
|
||||
RequestPayload = t.RequestPayload,
|
||||
RequestHeaders = t.RequestHeaders,
|
||||
ResponsePayload = t.ResponsePayload,
|
||||
ResponseHeaders = t.ResponseHeaders,
|
||||
Status = t.Status
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public List<WebhooksConfig> GetWebhookConfigs(int tenant)
|
||||
{
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
return webhooksDbContext.WebhooksConfigs.Where(t => t.TenantId == tenant).ToList();
|
||||
}
|
||||
|
||||
public WebhookEntry ReadFromJournal(int id)
|
||||
{
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
return webhooksDbContext.WebhooksLogs
|
||||
.Where(it => it.Id == id)
|
||||
.Join(webhooksDbContext.WebhooksConfigs, t => t.ConfigId, t => t.ConfigId, (payload, config) => new { payload, config })
|
||||
.Select(t => new WebhookEntry { Id = t.payload.Id, Payload = t.payload.RequestPayload, SecretKey = t.config.SecretKey, Uri = t.config.Uri })
|
||||
.OrderBy(t => t.Id).FirstOrDefault();
|
||||
}
|
||||
|
||||
public void RemoveWebhookConfig(WebhooksConfig webhooksConfig)
|
||||
{
|
||||
webhooksConfig.TenantId = _tenantManager.GetCurrentTenant().Id;
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
var removeObj = webhooksDbContext.WebhooksConfigs.Where(it =>
|
||||
it.SecretKey == webhooksConfig.SecretKey &&
|
||||
it.TenantId == webhooksConfig.TenantId &&
|
||||
it.Uri == webhooksConfig.Uri).FirstOrDefault();
|
||||
var removeObj = await webhooksDbContext.WebhooksConfigs
|
||||
.Where(it => it.TenantId == tenant && it.Id == id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
webhooksDbContext.WebhooksConfigs.Remove(removeObj);
|
||||
webhooksDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void UpdateWebhookConfig(WebhooksConfig webhooksConfig)
|
||||
{
|
||||
webhooksConfig.TenantId = _tenantManager.GetCurrentTenant().Id;
|
||||
await webhooksDbContext.SaveChangesAsync();
|
||||
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
var updateObj = webhooksDbContext.WebhooksConfigs.Where(it =>
|
||||
it.SecretKey == webhooksConfig.SecretKey &&
|
||||
it.TenantId == webhooksConfig.TenantId &&
|
||||
it.Uri == webhooksConfig.Uri).FirstOrDefault();
|
||||
|
||||
webhooksDbContext.WebhooksConfigs.Update(updateObj);
|
||||
webhooksDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void UpdateWebhookJournal(int id, ProcessStatus status, string responsePayload, string responseHeaders, string requestHeaders)
|
||||
return removeObj;
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<WebhooksLog> ReadJournal(int startIndex, int limit, DateTime? delivery, string hookname, string route)
|
||||
{
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
var webhook = webhooksDbContext.WebhooksLogs.Where(t => t.Id == id).FirstOrDefault();
|
||||
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(hookname))
|
||||
{
|
||||
q = q.Where(r => r.Config.Name == hookname);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(route))
|
||||
{
|
||||
q = q.Where(r => r.Route == route);
|
||||
}
|
||||
|
||||
if (startIndex != 0)
|
||||
{
|
||||
q = q.Skip(startIndex);
|
||||
}
|
||||
|
||||
if (limit != 0)
|
||||
{
|
||||
q = q.Take(limit);
|
||||
}
|
||||
|
||||
return q.OrderByDescending(t => t.Id).AsAsyncEnumerable();
|
||||
}
|
||||
|
||||
public async Task<WebhooksLog> ReadJournal(int id)
|
||||
{
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
return await webhooksDbContext.WebhooksLogs
|
||||
.AsNoTracking()
|
||||
.Where(it => it.Id == id)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<WebhooksLog> WriteToJournal(WebhooksLog webhook)
|
||||
{
|
||||
webhook.TenantId = _tenantManager.GetCurrentTenant().Id;
|
||||
webhook.Uid = _authContext.CurrentAccount.ID;
|
||||
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
var entity = await webhooksDbContext.WebhooksLogs.AddAsync(webhook);
|
||||
await webhooksDbContext.SaveChangesAsync();
|
||||
|
||||
return entity.Entity;
|
||||
}
|
||||
|
||||
public async Task<WebhooksLog> UpdateWebhookJournal(int id, int status, DateTime delivery, string requestHeaders, string responsePayload, string responseHeaders)
|
||||
{
|
||||
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.RequestHeaders = requestHeaders;
|
||||
webhooksDbContext.WebhooksLogs.Update(webhook);
|
||||
webhooksDbContext.SaveChanges();
|
||||
}
|
||||
webhook.ResponseHeaders = responseHeaders;
|
||||
webhook.Delivery = delivery;
|
||||
|
||||
public int WriteToJournal(WebhooksLog webhook)
|
||||
{
|
||||
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
|
||||
var entity = webhooksDbContext.WebhooksLogs.Add(webhook);
|
||||
webhooksDbContext.SaveChanges();
|
||||
return entity.Entity.Id;
|
||||
webhooksDbContext.WebhooksLogs.Update(webhook);
|
||||
await webhooksDbContext.SaveChangesAsync();
|
||||
|
||||
return webhook;
|
||||
}
|
||||
}
|
@ -28,8 +28,8 @@ namespace ASC.Webhooks.Core.EF.Context;
|
||||
|
||||
public class WebhooksDbContext : DbContext
|
||||
{
|
||||
public virtual DbSet<WebhooksConfig> WebhooksConfigs { get; set; }
|
||||
public virtual DbSet<WebhooksLog> WebhooksLogs { get; set; }
|
||||
public DbSet<WebhooksConfig> WebhooksConfigs { get; set; }
|
||||
public DbSet<WebhooksLog> WebhooksLogs { get; set; }
|
||||
|
||||
public WebhooksDbContext(DbContextOptions<WebhooksDbContext> options) : base(options) { }
|
||||
|
||||
|
@ -25,12 +25,20 @@
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
namespace ASC.Webhooks.Core.EF.Model;
|
||||
public partial class WebhooksConfig
|
||||
|
||||
public class WebhooksConfig : BaseEntity
|
||||
{
|
||||
public int ConfigId { get; set; }
|
||||
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 string Uri { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public override object[] GetKeys()
|
||||
{
|
||||
return new object[] { Id };
|
||||
}
|
||||
}
|
||||
|
||||
public static class WebhooksConfigExtension
|
||||
@ -46,15 +54,18 @@ public static class WebhooksConfigExtension
|
||||
{
|
||||
modelBuilder.Entity<WebhooksConfig>(entity =>
|
||||
{
|
||||
entity.HasKey(e => new { e.ConfigId })
|
||||
.HasName("PRIMARY");
|
||||
entity.HasKey(e => new { e.Id })
|
||||
.HasName("PRIMARY");
|
||||
|
||||
entity.HasIndex(e => e.TenantId)
|
||||
.HasDatabaseName("tenant_id");
|
||||
|
||||
entity.ToTable("webhooks_config")
|
||||
.HasCharSet("utf8");
|
||||
|
||||
entity.Property(e => e.ConfigId)
|
||||
entity.Property(e => e.Id)
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("config_id");
|
||||
.HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.TenantId)
|
||||
.HasColumnName("tenant_id")
|
||||
@ -68,7 +79,17 @@ public static class WebhooksConfigExtension
|
||||
entity.Property(e => e.SecretKey)
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("secret_key")
|
||||
.HasDefaultValueSql("''");
|
||||
.HasDefaultValueSql("''");
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("name")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.Enabled)
|
||||
.HasColumnName("enabled")
|
||||
.HasDefaultValueSql("'1'")
|
||||
.HasColumnType("tinyint(1)");
|
||||
});
|
||||
}
|
||||
|
||||
@ -76,14 +97,17 @@ public static class WebhooksConfigExtension
|
||||
{
|
||||
modelBuilder.Entity<WebhooksConfig>(entity =>
|
||||
{
|
||||
entity.HasKey(e => new { e.ConfigId })
|
||||
entity.HasKey(e => new { e.Id })
|
||||
.HasName("PRIMARY");
|
||||
|
||||
entity.ToTable("webhooks_config");
|
||||
entity.ToTable("webhooks_config");
|
||||
|
||||
entity.HasIndex(e => e.TenantId)
|
||||
.HasDatabaseName("tenant_id");
|
||||
|
||||
entity.Property(e => e.ConfigId)
|
||||
entity.Property(e => e.Id)
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("config_id");
|
||||
.HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.TenantId)
|
||||
.HasColumnName("tenant_id")
|
||||
@ -97,7 +121,16 @@ public static class WebhooksConfigExtension
|
||||
entity.Property(e => e.SecretKey)
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("secret_key")
|
||||
.HasDefaultValueSql("''");
|
||||
.HasDefaultValueSql("''");
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("name")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.Enabled)
|
||||
.HasColumnName("enabled")
|
||||
.HasDefaultValueSql("true");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -26,25 +26,31 @@
|
||||
|
||||
namespace ASC.Webhooks.Core.EF.Model;
|
||||
|
||||
public partial class WebhooksLog
|
||||
public class WebhooksLog
|
||||
{
|
||||
public int ConfigId { get; set; }
|
||||
public DateTime CreationTime { get; set; }
|
||||
public string Event { get; set; }
|
||||
public int Id { get; set; }
|
||||
public int Id { get; set; }
|
||||
public string Method { get; set; }
|
||||
public string Route { get; set; }
|
||||
public string RequestHeaders { get; set; }
|
||||
public string RequestPayload { get; set; }
|
||||
public string ResponseHeaders { get; set; }
|
||||
public string ResponsePayload { get; set; }
|
||||
public ProcessStatus Status { get; set; }
|
||||
public int Status { get; set; }
|
||||
public int TenantId { get; set; }
|
||||
public string Uid { get; set; }
|
||||
public Guid Uid { get; set; }
|
||||
public DateTime? Delivery { get; set; }
|
||||
|
||||
public WebhooksConfig Config { get; set; }
|
||||
}
|
||||
|
||||
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);
|
||||
@ -60,7 +66,10 @@ public static class WebhooksPayloadExtension
|
||||
.HasName("PRIMARY");
|
||||
|
||||
entity.ToTable("webhooks_logs")
|
||||
.HasCharSet("utf8");
|
||||
.HasCharSet("utf8");
|
||||
|
||||
entity.HasIndex(e => e.TenantId)
|
||||
.HasDatabaseName("tenant_id");
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasColumnType("int")
|
||||
@ -72,9 +81,10 @@ public static class WebhooksPayloadExtension
|
||||
.HasColumnName("config_id");
|
||||
|
||||
entity.Property(e => e.Uid)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("uid")
|
||||
.HasMaxLength(50);
|
||||
.HasColumnName("uid")
|
||||
.HasColumnType("varchar(36)")
|
||||
.HasCharSet("utf8")
|
||||
.UseCollation("utf8_general_ci");
|
||||
|
||||
entity.Property(e => e.TenantId)
|
||||
.HasColumnName("tenant_id")
|
||||
@ -83,7 +93,9 @@ public static class WebhooksPayloadExtension
|
||||
entity.Property(e => e.RequestPayload)
|
||||
.IsRequired()
|
||||
.HasColumnName("request_payload")
|
||||
.HasColumnType("json");
|
||||
.HasColumnType("text")
|
||||
.HasCharSet("utf8")
|
||||
.UseCollation("utf8_general_ci");
|
||||
|
||||
entity.Property(e => e.RequestHeaders)
|
||||
.HasColumnName("request_headers")
|
||||
@ -91,25 +103,35 @@ public static class WebhooksPayloadExtension
|
||||
|
||||
entity.Property(e => e.ResponsePayload)
|
||||
.HasColumnName("response_payload")
|
||||
.HasColumnType("json");
|
||||
.HasColumnType("text")
|
||||
.HasCharSet("utf8")
|
||||
.UseCollation("utf8_general_ci");
|
||||
|
||||
entity.Property(e => e.ResponseHeaders)
|
||||
.HasColumnName("response_headers")
|
||||
.HasColumnType("json");
|
||||
|
||||
entity.Property(e => e.Event)
|
||||
entity.Property(e => e.Method)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("event")
|
||||
.HasColumnName("method")
|
||||
.HasMaxLength(100);
|
||||
|
||||
entity.Property(e => e.Route)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("route")
|
||||
.HasMaxLength(100);
|
||||
|
||||
entity.Property(e => e.CreationTime)
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("creation_time");
|
||||
|
||||
entity.Property(e => e.Delivery)
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("delivery");
|
||||
|
||||
entity.Property(e => e.Status)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("status")
|
||||
.HasMaxLength(50);
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("status");
|
||||
});
|
||||
}
|
||||
|
||||
@ -120,7 +142,10 @@ public static class WebhooksPayloadExtension
|
||||
entity.HasKey(e => new { e.Id })
|
||||
.HasName("PRIMARY");
|
||||
|
||||
entity.ToTable("webhooks_logs");
|
||||
entity.ToTable("webhooks_logs");
|
||||
|
||||
entity.HasIndex(e => e.TenantId)
|
||||
.HasDatabaseName("tenant_id");
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasColumnType("int")
|
||||
@ -142,34 +167,40 @@ public static class WebhooksPayloadExtension
|
||||
|
||||
entity.Property(e => e.RequestPayload)
|
||||
.IsRequired()
|
||||
.HasColumnName("request_payload")
|
||||
.HasColumnType("json");
|
||||
.HasColumnName("request_payload");
|
||||
|
||||
entity.Property(e => e.RequestHeaders)
|
||||
.HasColumnName("request_headers")
|
||||
.HasColumnType("json");
|
||||
|
||||
entity.Property(e => e.ResponsePayload)
|
||||
.HasColumnName("response_payload")
|
||||
.HasColumnType("json");
|
||||
.HasColumnName("response_payload");
|
||||
|
||||
entity.Property(e => e.ResponseHeaders)
|
||||
.HasColumnName("response_headers")
|
||||
.HasColumnType("json");
|
||||
|
||||
entity.Property(e => e.Event)
|
||||
entity.Property(e => e.Method)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("event")
|
||||
.HasColumnName("method")
|
||||
.HasMaxLength(100);
|
||||
|
||||
entity.Property(e => e.Route)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("route")
|
||||
.HasMaxLength(100);
|
||||
|
||||
entity.Property(e => e.CreationTime)
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("creation_time");
|
||||
.HasColumnName("creation_time");
|
||||
|
||||
entity.Property(e => e.Delivery)
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("delivery");
|
||||
|
||||
entity.Property(e => e.Status)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("status")
|
||||
.HasMaxLength(50);
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("status");
|
||||
});
|
||||
}
|
||||
}
|
@ -29,5 +29,6 @@ namespace ASC.Webhooks.Core;
|
||||
[Scope]
|
||||
public interface IWebhookPublisher
|
||||
{
|
||||
public void Publish(string eventName, string requestPayload);
|
||||
public Task PublishAsync(string method, string route, string requestPayload);
|
||||
public Task<WebhooksLog> PublishAsync(string method, string route, string requestPayload, int configId);
|
||||
}
|
@ -30,51 +30,56 @@ namespace ASC.Webhooks.Core;
|
||||
public class WebhookPublisher : IWebhookPublisher
|
||||
{
|
||||
private readonly DbWorker _dbWorker;
|
||||
private readonly TenantManager _tenantManager;
|
||||
private readonly ICacheNotify<WebhookRequest> _webhookNotify;
|
||||
|
||||
|
||||
public WebhookPublisher(
|
||||
DbWorker dbWorker,
|
||||
TenantManager tenantManager,
|
||||
ICacheNotify<WebhookRequest> webhookNotify)
|
||||
{
|
||||
_dbWorker = dbWorker;
|
||||
_tenantManager = tenantManager;
|
||||
_webhookNotify = webhookNotify;
|
||||
}
|
||||
|
||||
public void Publish(string eventName, string requestPayload)
|
||||
public async Task PublishAsync(string method, string route, string requestPayload)
|
||||
{
|
||||
var tenantId = _tenantManager.GetCurrentTenant().Id;
|
||||
var webhookConfigs = _dbWorker.GetWebhookConfigs(tenantId);
|
||||
|
||||
foreach (var config in webhookConfigs)
|
||||
if (string.IsNullOrEmpty(requestPayload))
|
||||
{
|
||||
var webhooksLog = new WebhooksLog
|
||||
{
|
||||
Uid = Guid.NewGuid().ToString(),
|
||||
TenantId = tenantId,
|
||||
Event = eventName,
|
||||
CreationTime = DateTime.UtcNow,
|
||||
RequestPayload = requestPayload,
|
||||
Status = ProcessStatus.InProcess,
|
||||
ConfigId = config.ConfigId
|
||||
};
|
||||
var DbId = _dbWorker.WriteToJournal(webhooksLog);
|
||||
return;
|
||||
}
|
||||
|
||||
var webhookConfigs = _dbWorker.GetWebhookConfigs();
|
||||
|
||||
var request = new WebhookRequest()
|
||||
{
|
||||
Id = DbId
|
||||
};
|
||||
|
||||
_webhookNotify.Publish(request, CacheNotifyAction.Update);
|
||||
await foreach (var config in webhookConfigs.Where(r => r.Enabled))
|
||||
{
|
||||
_ = await PublishAsync(method, route, requestPayload, config.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProcessStatus
|
||||
{
|
||||
InProcess,
|
||||
Success,
|
||||
Failed
|
||||
public async Task<WebhooksLog> PublishAsync(string method, string route, string requestPayload, int configId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(requestPayload))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var webhooksLog = new WebhooksLog
|
||||
{
|
||||
Method = method,
|
||||
Route = route,
|
||||
CreationTime = DateTime.UtcNow,
|
||||
RequestPayload = requestPayload,
|
||||
ConfigId = configId
|
||||
};
|
||||
|
||||
var webhook = await _dbWorker.WriteToJournal(webhooksLog);
|
||||
|
||||
var request = new WebhookRequest
|
||||
{
|
||||
Id = webhook.Id
|
||||
};
|
||||
|
||||
_webhookNotify.Publish(request, CacheNotifyAction.Update);
|
||||
|
||||
return webhook;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="6.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ASC.Api.Core\ASC.Api.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
@ -37,9 +37,13 @@ global using ASC.Common.Log;
|
||||
global using ASC.Common.Utils;
|
||||
global using ASC.Web.Webhooks;
|
||||
global using ASC.Webhooks.Core;
|
||||
global using ASC.Webhooks.Service;
|
||||
global using ASC.Webhooks.Service.Log;
|
||||
global using ASC.Webhooks.Service.Services;
|
||||
|
||||
global using Microsoft.AspNetCore.Builder;
|
||||
global using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
|
||||
global using Polly;
|
||||
global using Polly.Extensions.Http;
|
@ -41,11 +41,20 @@ builder.Host.ConfigureDefault(args, (hostContext, config, env, path) =>
|
||||
|
||||
diHelper.TryAdd<DbWorker>();
|
||||
|
||||
services.AddHostedService<BuildQueueService>();
|
||||
diHelper.TryAdd<BuildQueueService>();
|
||||
|
||||
services.AddHostedService<WorkerService>();
|
||||
diHelper.TryAdd<WorkerService>();
|
||||
diHelper.TryAdd<WorkerService>();
|
||||
|
||||
services.AddHttpClient("webhook")
|
||||
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
|
||||
.AddPolicyHandler((s, request) =>
|
||||
{
|
||||
var settings = s.GetRequiredService<Settings>();
|
||||
|
||||
return HttpPolicyExtensions
|
||||
.HandleTransientHttpError()
|
||||
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
.WaitAndRetryAsync(settings.RepeatCount.HasValue ? settings.RepeatCount.Value : 5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
|
||||
});
|
||||
});
|
||||
|
||||
builder.WebHost.ConfigureDefaultKestrel();
|
||||
|
@ -29,26 +29,36 @@ namespace ASC.Webhooks.Service.Services;
|
||||
[Singletone]
|
||||
public class WorkerService : BackgroundService
|
||||
{
|
||||
private readonly ILogger<WorkerService> _logger;
|
||||
private readonly ConcurrentQueue<WebhookRequest> _queue;
|
||||
private readonly ILogger<WorkerService> _logger;
|
||||
private readonly int? _threadCount = 10;
|
||||
private readonly WebhookSender _webhookSender;
|
||||
private readonly TimeSpan _waitingPeriod;
|
||||
private readonly TimeSpan _waitingPeriod;
|
||||
private readonly ConcurrentQueue<WebhookRequest> _queue;
|
||||
private readonly ICacheNotify<WebhookRequest> _webhookNotify;
|
||||
|
||||
public WorkerService(WebhookSender webhookSender,
|
||||
public WorkerService(
|
||||
ICacheNotify<WebhookRequest> webhookNotify,
|
||||
WebhookSender webhookSender,
|
||||
ILogger<WorkerService> logger,
|
||||
BuildQueueService buildQueueService,
|
||||
Settings settings)
|
||||
{
|
||||
_logger = logger;
|
||||
{
|
||||
_webhookNotify = webhookNotify;
|
||||
_queue = new ConcurrentQueue<WebhookRequest>();
|
||||
_logger = logger;
|
||||
_webhookSender = webhookSender;
|
||||
_queue = buildQueueService.Queue;
|
||||
_threadCount = settings.ThreadCount;
|
||||
_waitingPeriod = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
{
|
||||
_webhookNotify.Subscribe(_queue.Enqueue, CacheNotifyAction.Update);
|
||||
|
||||
stoppingToken.Register(() =>
|
||||
{
|
||||
_webhookNotify.Unsubscribe(CacheNotifyAction.Update);
|
||||
});
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var queueSize = _queue.Count;
|
||||
@ -62,7 +72,7 @@ public class WorkerService : BackgroundService
|
||||
continue;
|
||||
}
|
||||
|
||||
var tasks = new List<Task>();
|
||||
var tasks = new List<Task>(queueSize);
|
||||
var counter = 0;
|
||||
|
||||
for (var i = 0; i < queueSize; i++)
|
||||
@ -82,13 +92,13 @@ public class WorkerService : BackgroundService
|
||||
|
||||
if (counter >= _threadCount)
|
||||
{
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
await Task.WhenAll(tasks);
|
||||
tasks.Clear();
|
||||
counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
_logger.DebugProcedureFinish();
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public class Settings
|
||||
{
|
||||
var cfg = configuration.GetSetting<Settings>("webhooks");
|
||||
RepeatCount = cfg.RepeatCount ?? 5;
|
||||
ThreadCount = cfg.ThreadCount ?? 1;
|
||||
ThreadCount = cfg.ThreadCount ?? 10;
|
||||
}
|
||||
public int? RepeatCount { get; }
|
||||
public int? ThreadCount { get; }
|
||||
|
@ -24,6 +24,8 @@
|
||||
// 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 System.Text.Json.Serialization;
|
||||
|
||||
namespace ASC.Webhooks.Service;
|
||||
|
||||
[Singletone]
|
||||
@ -31,15 +33,19 @@ public class WebhookSender
|
||||
{
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
private readonly ILogger _log;
|
||||
public int? RepeatCount { get; init; }
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
||||
|
||||
public WebhookSender(ILoggerProvider options, IServiceScopeFactory scopeFactory, Settings settings, IHttpClientFactory clientFactory)
|
||||
public WebhookSender(ILoggerProvider options, IServiceScopeFactory scopeFactory, IHttpClientFactory clientFactory)
|
||||
{
|
||||
_log = options.CreateLogger("ASC.Webhooks.Core");
|
||||
_scopeFactory = scopeFactory;
|
||||
RepeatCount = settings.RepeatCount;
|
||||
_clientFactory = clientFactory;
|
||||
_clientFactory = clientFactory;
|
||||
_jsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
IgnoreReadOnlyProperties = true
|
||||
};
|
||||
}
|
||||
|
||||
public async Task Send(WebhookRequest webhookRequest, CancellationToken cancellationToken)
|
||||
@ -47,82 +53,65 @@ public class WebhookSender
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbWorker = scope.ServiceProvider.GetRequiredService<DbWorker>();
|
||||
|
||||
var entry = dbWorker.ReadFromJournal(webhookRequest.Id);
|
||||
var id = entry.Id;
|
||||
var requestURI = entry.Uri;
|
||||
var secretKey = entry.SecretKey;
|
||||
var data = entry.Payload;
|
||||
|
||||
var response = new HttpResponseMessage();
|
||||
var request = new HttpRequestMessage();
|
||||
|
||||
for (var i = 0; i < RepeatCount; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
request = new HttpRequestMessage(HttpMethod.Post, requestURI);
|
||||
request.Headers.Add("Accept", "*/*");
|
||||
request.Headers.Add("Secret", "SHA256=" + GetSecretHash(secretKey, data));
|
||||
|
||||
request.Content = new StringContent(
|
||||
data,
|
||||
Encoding.UTF8,
|
||||
"application/json");
|
||||
|
||||
var httpClient = _clientFactory.CreateClient();
|
||||
response = await httpClient.SendAsync(request, cancellationToken);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
UpdateDb(dbWorker, id, response, request, ProcessStatus.Success);
|
||||
_log.DebugResponse(response);
|
||||
break;
|
||||
}
|
||||
else if (i == RepeatCount - 1)
|
||||
{
|
||||
UpdateDb(dbWorker, id, response, request, ProcessStatus.Failed);
|
||||
_log.DebugResponse(response);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == RepeatCount - 1)
|
||||
{
|
||||
UpdateDb(dbWorker, id, response, request, ProcessStatus.Failed);
|
||||
}
|
||||
|
||||
_log.ErrorWithException(ex);
|
||||
continue;
|
||||
}
|
||||
var entry = await dbWorker.ReadJournal(webhookRequest.Id);
|
||||
|
||||
var status = 0;
|
||||
string responsePayload = null;
|
||||
string responseHeaders = null;
|
||||
string requestHeaders = null;
|
||||
var delivery = DateTime.MinValue;
|
||||
|
||||
try
|
||||
{
|
||||
var httpClient = _clientFactory.CreateClient("webhook");
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, entry.Config.Uri)
|
||||
{
|
||||
Content = new StringContent(entry.RequestPayload, Encoding.UTF8, "application/json")
|
||||
};
|
||||
|
||||
request.Headers.Add("Accept", "*/*");
|
||||
request.Headers.Add("Secret", "SHA256=" + GetSecretHash(entry.Config.SecretKey, entry.RequestPayload));
|
||||
requestHeaders = JsonSerializer.Serialize(request.Headers.ToDictionary(r => r.Key, v => v.Value), _jsonSerializerOptions);
|
||||
|
||||
var response = await httpClient.SendAsync(request, cancellationToken);
|
||||
|
||||
status = (int)response.StatusCode;
|
||||
responseHeaders = JsonSerializer.Serialize(response.Headers.ToDictionary(r => r.Key, v => v.Value), _jsonSerializerOptions);
|
||||
responsePayload = await response.Content.ReadAsStringAsync();
|
||||
delivery = DateTime.UtcNow;
|
||||
|
||||
_log.DebugResponse(response);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
if (e.StatusCode.HasValue)
|
||||
{
|
||||
status = (int)e.StatusCode.Value;
|
||||
}
|
||||
responsePayload = e.Message;
|
||||
delivery = DateTime.UtcNow;
|
||||
|
||||
_log.ErrorWithException(e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.ErrorWithException(e);
|
||||
}
|
||||
|
||||
if (delivery != DateTime.MinValue)
|
||||
{
|
||||
await dbWorker.UpdateWebhookJournal(entry.Id, status, delivery, requestHeaders, responsePayload, responseHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSecretHash(string secretKey, string body)
|
||||
{
|
||||
string computedSignature;
|
||||
{
|
||||
var secretBytes = Encoding.UTF8.GetBytes(secretKey);
|
||||
|
||||
using (var hasher = new HMACSHA256(secretBytes))
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(body);
|
||||
computedSignature = BitConverter.ToString(hasher.ComputeHash(data));
|
||||
return BitConverter.ToString(hasher.ComputeHash(data));
|
||||
}
|
||||
|
||||
return computedSignature;
|
||||
}
|
||||
|
||||
private void UpdateDb(DbWorker dbWorker, int id, HttpResponseMessage response, HttpRequestMessage request, ProcessStatus status)
|
||||
{
|
||||
var responseHeaders = JsonSerializer.Serialize(response.Headers.ToDictionary(r => r.Key, v => v.Value));
|
||||
var requestHeaders = JsonSerializer.Serialize(request.Headers.ToDictionary(r => r.Key, v => v.Value));
|
||||
string responsePayload;
|
||||
|
||||
using (var streamReader = new StreamReader(response.Content.ReadAsStream()))
|
||||
{
|
||||
var responseContent = streamReader.ReadToEnd();
|
||||
responsePayload = JsonSerializer.Serialize(responseContent);
|
||||
}
|
||||
|
||||
dbWorker.UpdateWebhookJournal(id, status, responsePayload, responseHeaders, requestHeaders);
|
||||
}
|
||||
}
|
@ -34,6 +34,9 @@
|
||||
"hosting": {
|
||||
"intervalCheckRegisterInstanceInSeconds": "1",
|
||||
"timeUntilUnregisterInSeconds": "15"
|
||||
},
|
||||
"oidc": {
|
||||
"authority" : ""
|
||||
}
|
||||
},
|
||||
"license": {
|
||||
@ -50,7 +53,7 @@
|
||||
"enabled": "true"
|
||||
},
|
||||
"version": {
|
||||
"number": "11.5.0",
|
||||
"number": "1.0.0",
|
||||
"release": {
|
||||
"date": "",
|
||||
"sign": ""
|
||||
|
163
migrations/mysql/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs
generated
Normal file
163
migrations/mysql/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs
generated
Normal file
@ -0,0 +1,163 @@
|
||||
// <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("20220818144209_WebhooksDbContext_Upgrade1")]
|
||||
partial class WebhooksDbContext_Upgrade1
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
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<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()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)")
|
||||
.HasColumnName("uri")
|
||||
.HasDefaultValueSql("''");
|
||||
|
||||
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>("Method")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("method");
|
||||
|
||||
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<string>("Route")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("route");
|
||||
|
||||
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.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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ASC.Migrations.MySql.Migrations.WebhooksDb
|
||||
{
|
||||
public partial class WebhooksDbContext_Upgrade1 : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "config_id",
|
||||
table: "webhooks_config",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "webhooks_logs",
|
||||
keyColumn: "uid",
|
||||
keyValue: null,
|
||||
column: "uid",
|
||||
value: "");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "uid",
|
||||
table: "webhooks_logs",
|
||||
type: "varchar(36)",
|
||||
nullable: false,
|
||||
collation: "utf8_general_ci",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(50)",
|
||||
oldMaxLength: 50,
|
||||
oldNullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8")
|
||||
.OldAnnotation("MySql:CharSet", "utf8");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "status",
|
||||
table: "webhooks_logs",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(50)",
|
||||
oldMaxLength: 50)
|
||||
.OldAnnotation("MySql:CharSet", "utf8");
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "delivery",
|
||||
table: "webhooks_logs",
|
||||
type: "datetime",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "enabled",
|
||||
table: "webhooks_config",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValueSql: "'1'");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "name",
|
||||
table: "webhooks_config",
|
||||
type: "varchar(50)",
|
||||
maxLength: 50,
|
||||
nullable: false,
|
||||
defaultValue: "")
|
||||
.Annotation("MySql:CharSet", "utf8");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_webhooks_logs_config_id",
|
||||
table: "webhooks_logs",
|
||||
column: "config_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "tenant_id",
|
||||
table: "webhooks_logs",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "tenant_id",
|
||||
table: "webhooks_config",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_webhooks_logs_webhooks_config_config_id",
|
||||
table: "webhooks_logs",
|
||||
column: "config_id",
|
||||
principalTable: "webhooks_config",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_webhooks_logs_webhooks_config_config_id",
|
||||
table: "webhooks_logs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_webhooks_logs_config_id",
|
||||
table: "webhooks_logs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "tenant_id",
|
||||
table: "webhooks_logs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "tenant_id",
|
||||
table: "webhooks_config");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "delivery",
|
||||
table: "webhooks_logs");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "enabled",
|
||||
table: "webhooks_config");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "name",
|
||||
table: "webhooks_config");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "webhooks_config",
|
||||
newName: "config_id");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "uid",
|
||||
table: "webhooks_logs",
|
||||
type: "varchar(50)",
|
||||
maxLength: 50,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar(36)",
|
||||
oldCollation: "utf8_general_ci")
|
||||
.Annotation("MySql:CharSet", "utf8")
|
||||
.OldAnnotation("MySql:CharSet", "utf8");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "status",
|
||||
table: "webhooks_logs",
|
||||
type: "varchar(50)",
|
||||
maxLength: 50,
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int")
|
||||
.Annotation("MySql:CharSet", "utf8");
|
||||
}
|
||||
}
|
||||
}
|
@ -16,15 +16,27 @@ namespace ASC.Migrations.MySql.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.4")
|
||||
.HasAnnotation("ProductVersion", "6.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
|
||||
{
|
||||
b.Property<int>("ConfigId")
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("config_id");
|
||||
.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<string>("SecretKey")
|
||||
.ValueGeneratedOnAdd()
|
||||
@ -44,9 +56,12 @@ namespace ASC.Migrations.MySql.Migrations
|
||||
.HasColumnName("uri")
|
||||
.HasDefaultValueSql("''");
|
||||
|
||||
b.HasKey("ConfigId")
|
||||
b.HasKey("Id")
|
||||
.HasName("PRIMARY");
|
||||
|
||||
b.HasIndex("TenantId")
|
||||
.HasDatabaseName("tenant_id");
|
||||
|
||||
b.ToTable("webhooks_config", (string)null);
|
||||
|
||||
b.HasAnnotation("MySql:CharSet", "utf8");
|
||||
@ -67,10 +82,14 @@ namespace ASC.Migrations.MySql.Migrations
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("creation_time");
|
||||
|
||||
b.Property<string>("Event")
|
||||
b.Property<DateTime?>("Delivery")
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("delivery");
|
||||
|
||||
b.Property<string>("Method")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("event");
|
||||
.HasColumnName("method");
|
||||
|
||||
b.Property<string>("RequestHeaders")
|
||||
.HasColumnType("json")
|
||||
@ -78,21 +97,28 @@ namespace ASC.Migrations.MySql.Migrations
|
||||
|
||||
b.Property<string>("RequestPayload")
|
||||
.IsRequired()
|
||||
.HasColumnType("json")
|
||||
.HasColumnName("request_payload");
|
||||
.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("json")
|
||||
.HasColumnName("response_payload");
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("response_payload")
|
||||
.UseCollation("utf8_general_ci")
|
||||
.HasAnnotation("MySql:CharSet", "utf8");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)")
|
||||
b.Property<string>("Route")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("route");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<uint>("TenantId")
|
||||
@ -100,17 +126,35 @@ namespace ASC.Migrations.MySql.Migrations
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)")
|
||||
.HasColumnName("uid");
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(36)")
|
||||
.HasColumnName("uid")
|
||||
.UseCollation("utf8_general_ci")
|
||||
.HasAnnotation("MySql:CharSet", "utf8");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
158
migrations/postgre/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs
generated
Normal file
158
migrations/postgre/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs
generated
Normal file
@ -0,0 +1,158 @@
|
||||
// <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("20220818144209_WebhooksDbContext_Upgrade1")]
|
||||
partial class WebhooksDbContext_Upgrade1
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
|
||||
.HasAnnotation("ProductVersion", "6.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
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<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()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.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>("Method")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("method");
|
||||
|
||||
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<string>("Route")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("route");
|
||||
|
||||
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.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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb
|
||||
{
|
||||
public partial class WebhooksDbContext_Upgrade1 : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "config_id",
|
||||
table: "webhooks_config",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "uid",
|
||||
table: "webhooks_logs",
|
||||
type: "varchar",
|
||||
maxLength: 50,
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar",
|
||||
oldMaxLength: 50,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "status",
|
||||
table: "webhooks_logs",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar",
|
||||
oldMaxLength: 50);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "delivery",
|
||||
table: "webhooks_logs",
|
||||
type: "datetime",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "enabled",
|
||||
table: "webhooks_config",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValueSql: "true");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "name",
|
||||
table: "webhooks_config",
|
||||
type: "character varying(50)",
|
||||
maxLength: 50,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_webhooks_logs_config_id",
|
||||
table: "webhooks_logs",
|
||||
column: "config_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "tenant_id",
|
||||
table: "webhooks_logs",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "tenant_id",
|
||||
table: "webhooks_config",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_webhooks_logs_webhooks_config_config_id",
|
||||
table: "webhooks_logs",
|
||||
column: "config_id",
|
||||
principalTable: "webhooks_config",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_webhooks_logs_webhooks_config_config_id",
|
||||
table: "webhooks_logs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_webhooks_logs_config_id",
|
||||
table: "webhooks_logs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "tenant_id",
|
||||
table: "webhooks_logs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "tenant_id",
|
||||
table: "webhooks_config");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "delivery",
|
||||
table: "webhooks_logs");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "enabled",
|
||||
table: "webhooks_config");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "name",
|
||||
table: "webhooks_config");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "webhooks_config",
|
||||
newName: "config_id");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "uid",
|
||||
table: "webhooks_logs",
|
||||
type: "varchar",
|
||||
maxLength: 50,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "varchar",
|
||||
oldMaxLength: 50);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "status",
|
||||
table: "webhooks_logs",
|
||||
type: "varchar",
|
||||
maxLength: 50,
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
}
|
||||
}
|
||||
}
|
@ -18,17 +18,29 @@ namespace ASC.Migrations.PostgreSql.Migrations
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
|
||||
.HasAnnotation("ProductVersion", "6.0.4")
|
||||
.HasAnnotation("ProductVersion", "6.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
|
||||
{
|
||||
b.Property<int>("ConfigId")
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("config_id")
|
||||
.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<string>("SecretKey")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(50)
|
||||
@ -47,9 +59,12 @@ namespace ASC.Migrations.PostgreSql.Migrations
|
||||
.HasColumnName("uri")
|
||||
.HasDefaultValueSql("''");
|
||||
|
||||
b.HasKey("ConfigId")
|
||||
b.HasKey("Id")
|
||||
.HasName("PRIMARY");
|
||||
|
||||
b.HasIndex("TenantId")
|
||||
.HasDatabaseName("tenant_id");
|
||||
|
||||
b.ToTable("webhooks_config", (string)null);
|
||||
});
|
||||
|
||||
@ -69,10 +84,14 @@ namespace ASC.Migrations.PostgreSql.Migrations
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("creation_time");
|
||||
|
||||
b.Property<string>("Event")
|
||||
b.Property<DateTime?>("Delivery")
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("delivery");
|
||||
|
||||
b.Property<string>("Method")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("event");
|
||||
.HasColumnName("method");
|
||||
|
||||
b.Property<string>("RequestHeaders")
|
||||
.HasColumnType("json")
|
||||
@ -80,7 +99,7 @@ namespace ASC.Migrations.PostgreSql.Migrations
|
||||
|
||||
b.Property<string>("RequestPayload")
|
||||
.IsRequired()
|
||||
.HasColumnType("json")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("request_payload");
|
||||
|
||||
b.Property<string>("ResponseHeaders")
|
||||
@ -88,13 +107,16 @@ namespace ASC.Migrations.PostgreSql.Migrations
|
||||
.HasColumnName("response_headers");
|
||||
|
||||
b.Property<string>("ResponsePayload")
|
||||
.HasColumnType("json")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("response_payload");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
b.Property<string>("Route")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("route");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<int>("TenantId")
|
||||
@ -102,6 +124,7 @@ namespace ASC.Migrations.PostgreSql.Migrations
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar")
|
||||
.HasColumnName("uid");
|
||||
@ -109,8 +132,24 @@ namespace ASC.Migrations.PostgreSql.Migrations
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Girişi bərpa edin",
|
||||
"RecoverContactEmailPlaceholder": "Əlaqə üçün e-poçt ünvanı",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Probleminizi təsvir edin",
|
||||
"RecoverTextBody": "Cari hesabınızla giriş edə bilmirsinizsə və ya yeni istifadəçi kimi qeydiyyatdan keçmək istəyirsinizsə, o zaman portal administratoru ilə əlaqə saxlayın.",
|
||||
"RecoverTitle": "Girişin bərpası",
|
||||
"TurnOnDesktopVersion": "Masaüstü versiyanı işə salın"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Поднови достъп",
|
||||
"RecoverContactEmailPlaceholder": "Имейл за контакт",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Опишете проблема си",
|
||||
"RecoverTextBody": "Ако не можете да се впишете със съществуващия си профил или искате да се регистрирате като нов потребител, свържете се с администратора на портала. ",
|
||||
"RecoverTitle": "Възстановяване на достъп",
|
||||
"TurnOnDesktopVersion": "Превключете на настолна версия"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Obnovit přístup",
|
||||
"RecoverContactEmailPlaceholder": "Kontaktní emailová adresa",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Popište svůj problém",
|
||||
"RecoverTextBody": "Pokud se nemůžete přihlásit pomocí stávajícího účtu nebo chcete být zaregistrováni jako nový uživatel, kontaktujte správce portálu. ",
|
||||
"RecoverTitle": "Obnovení přístupu",
|
||||
"TurnOnDesktopVersion": "Zapnout verzi pro počítač"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Zugriff wiederherstellen",
|
||||
"RecoverContactEmailPlaceholder": "E-Mail-Adresse",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Bitte Problem beschreiben",
|
||||
"RecoverTextBody": "Wenn Sie sich mit Ihren Anmeldeinformationen nicht einloggen können oder ein neues Profil erstellen möchten, wenden Sie sich an den Administrator des Portals.",
|
||||
"RecoverTitle": "Zugriffswiederherstellung",
|
||||
"TurnOnDesktopVersion": "Desktopversion anzeigen"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Ανάκτηση πρόσβασης",
|
||||
"RecoverContactEmailPlaceholder": "Email επικοινωνίας",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Περιγράψτε το πρόβλημά σας",
|
||||
"RecoverTextBody": "Εάν δεν μπορείτε να συνδεθείτε με τον υπάρχοντα λογαριασμό σας ή θέλετε να εγγραφείτε ως νέος χρήστης, επικοινωνήστε με τον διαχειριστή της πύλης.",
|
||||
"RecoverTitle": "Ανάκτηση πρόσβασης",
|
||||
"TurnOnDesktopVersion": "Ενεργοποιήστε την έκδοση για υπολογιστές"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Recover access",
|
||||
"RecoverContactEmailPlaceholder": "Contact email",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Describe your problem",
|
||||
"RecoverTextBody": "If you can't log in with your existing account or want to be registered as a new user, contact the portal administrator.",
|
||||
"RecoverTitle": "Access recovery",
|
||||
"TurnOnDesktopVersion": "Turn on desktop version"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Recuperar acceso",
|
||||
"RecoverContactEmailPlaceholder": "E-mail de contacto",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Describa su problema",
|
||||
"RecoverTextBody": "Si no puede conectarse con su cuenta actual o quiere registrarse como nuevo usuario, póngase en contacto con el administrador del portal. ",
|
||||
"RecoverTitle": "Recuperación de acceso",
|
||||
"TurnOnDesktopVersion": "Activar la versión de escritorio"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Palauta käyttöoikeus",
|
||||
"RecoverContactEmailPlaceholder": "Sähköposti yhteydenottoa varten",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Kuvaile ongelmasi",
|
||||
"RecoverTextBody": "Jos et voi kirjautua sisään nykyisellä tililläsi tai haluat rekisteröityä uutena käyttäjänä, ota yhteyttä portaalin järjestelmänvalvojaan. ",
|
||||
"RecoverTitle": "Käytä palautusta",
|
||||
"TurnOnDesktopVersion": "Ota työpöytäversio käyttöön"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Récupérer l'accès",
|
||||
"RecoverContactEmailPlaceholder": "E-mail de contact",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Décrivez votre problème",
|
||||
"RecoverTextBody": "Si vous ne pouvez pas vous connecter avec votre compte existant ou que vous souhaitez en créer un nouveau, merci de contacter l'administrateur du portail.",
|
||||
"RecoverTitle": "Récupération d'accès",
|
||||
"TurnOnDesktopVersion": "Activer l'application de bureau"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Վերականգնել մատչումը",
|
||||
"RecoverContactEmailPlaceholder": "Կոնտակտային էլ. փոստ",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Նկարագրեք Ձեր խնդիրը",
|
||||
"RecoverTextBody": "Եթե Դուք չեք կարող մուտք գործել Ձեր գոյություն ունեցող հաշիվ կամ ցանկանում եք գրանցվել որպես նոր օգտվող, կապվեք կայքէջի ադմինիստրատորի հետ:",
|
||||
"RecoverTitle": "Մատչման վերականգնում",
|
||||
"TurnOnDesktopVersion": "Միացնել աշխատասեղանի տարբերակը"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Rimuovi accesso",
|
||||
"RecoverContactEmailPlaceholder": "Email di contatto",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Descrivi il tuo problema",
|
||||
"RecoverTextBody": "Se non riesci ad accedere con il tuo account esistente o desideri essere registrato come un nuovo utente, contatta l'amministratore del portale.",
|
||||
"RecoverTitle": "Ripristino dell'accesso",
|
||||
"TurnOnDesktopVersion": "Attivare la versione desktop"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "アクセス回復",
|
||||
"RecoverContactEmailPlaceholder": "連絡先メール",
|
||||
"RecoverDescribeYourProblemPlaceholder": "ご質問を説明ください",
|
||||
"RecoverTextBody": "既存のアカウントでログインできない場合や、新規ユーザーとして登録したい場合は、ポータル管理者にお問い合わせください。 ",
|
||||
"RecoverTitle": "アクセス回復機能",
|
||||
"TurnOnDesktopVersion": "デスクトップ版を起動する"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "액세스 복구",
|
||||
"RecoverContactEmailPlaceholder": "연락처 이메일",
|
||||
"RecoverDescribeYourProblemPlaceholder": "겪고 계신 문제를 설명해주세요",
|
||||
"RecoverTextBody": "기존 계정으로 로그인하실 수 없거나 새로운 사용자로 등록하시길 원하시는 경우, 포털 관리자에게 문의하세요. ",
|
||||
"RecoverTitle": "액세스 복원",
|
||||
"TurnOnDesktopVersion": "데스크탑 버전 켜기"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "ກູ້ຄືນການເຂົ້າເຖິງ",
|
||||
"RecoverContactEmailPlaceholder": "ອີເມວຕິດຕໍ່",
|
||||
"RecoverDescribeYourProblemPlaceholder": "ອະທິບາຍບັນຫາຂອງທ່ານ",
|
||||
"RecoverTextBody": "ຖ້າທ່ານບໍ່ສາມາດເຂົ້າສູ່ລະບົບໄດ້ຫຼືຕ້ອງການລົງທະບຽນໃຫມ່, ໃຫ້ທ່ານຕິດຕໍ່ຜູ້ດູແລລະບົບ portal ",
|
||||
"RecoverTitle": "ການເຂົ້າເຖິງການກູ້ຄືນ",
|
||||
"TurnOnDesktopVersion": "ເປີດທາງ desktop"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Atgūt piekļuvi",
|
||||
"RecoverContactEmailPlaceholder": "E-pasts saziņai",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Aprakstiet savu problēmu",
|
||||
"RecoverTextBody": "Ja nevarat pieteikties ar savu esošo kontu vai vēlaties reģistrēties kā jauns lietotājs, sazinieties ar portāla administratoru. ",
|
||||
"RecoverTitle": "Piekļuves atkopšana",
|
||||
"TurnOnDesktopVersion": "Ieslēgt darbvirsmas versiju"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Toegang herstellen",
|
||||
"RecoverContactEmailPlaceholder": "Contact e-mail",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Beschrijf uw probleem",
|
||||
"RecoverTextBody": "Als u zich niet kunt aanmelden met uw bestaande account of als nieuwe gebruiker wilt worden geregistreerd, neem dan contact op met de portaalbeheerder.",
|
||||
"RecoverTitle": "Toegang herstel",
|
||||
"TurnOnDesktopVersion": "Schakel desktop versie in"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Odzyskaj dostęp",
|
||||
"RecoverContactEmailPlaceholder": "E-mail kontaktowy",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Opisz swój problem",
|
||||
"RecoverTextBody": "Jeśli nie możesz się zalogować za pomocą istniejącego konta, lub chcesz zarejestrować się jako nowy użytkownik, skontaktuj się z administratorem portalu. ",
|
||||
"RecoverTitle": "Odzyskiwanie dostępu",
|
||||
"TurnOnDesktopVersion": "Włącz wersję na komputer stacjonarny"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Recuperar o acesso",
|
||||
"RecoverContactEmailPlaceholder": "E-mail de contato",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Descreva seu problema",
|
||||
"RecoverTextBody": "Se você não conseguir entrar com sua conta existente ou quiser ser registrado como um novo usuário, entre em contato com o administrador do portal. ",
|
||||
"RecoverTitle": "Recuperação de acesso",
|
||||
"TurnOnDesktopVersion": "Ligar a versão desktop"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Recuperar o acesso",
|
||||
"RecoverContactEmailPlaceholder": "Email de contacto",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Descreva o seu problema",
|
||||
"RecoverTextBody": "Se não consegue iniciar sessão com a sua conta ou se quiser registar-se como um novo utilizador, contacte o administrador do portal.",
|
||||
"RecoverTitle": "Recuperação de acesso",
|
||||
"TurnOnDesktopVersion": "Ativar a versão para computador"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Recuperarea accesului la cont",
|
||||
"RecoverContactEmailPlaceholder": "E-mail de contact",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Descrieți problema",
|
||||
"RecoverTextBody": "Dacă nu puteți să vă conectați la contul dvs curent sau doriți să vă înregistrați din nou, contactați administratorul portalului. ",
|
||||
"RecoverTitle": "Restabilirea accesului",
|
||||
"TurnOnDesktopVersion": "Lansați versiunea desktop"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Доступ к порталу",
|
||||
"RecoverContactEmailPlaceholder": "Адрес email, по которому можно связаться с Вами",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Пожалуйста, опишите вашу проблему",
|
||||
"RecoverTextBody": "Если Вы уже зарегистрированы и у Вас есть проблемы с доступом к этому порталу, или Вы хотите зарегистрироваться как новый пользователь портала, пожалуйста, обратитесь к администратору портала, используя форму, расположенную ниже.",
|
||||
"RecoverTitle": "Доступ к порталу",
|
||||
"TurnOnDesktopVersion": "Включить версию для ПК"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Obnoviť prístup",
|
||||
"RecoverContactEmailPlaceholder": "Kontaktný email",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Opíšte svoj problém",
|
||||
"RecoverTextBody": "Ak sa nemôžete prihlásiť so svojím existujúcim kontom, alebo sa chcete zaregistrovať ako nový užívateľ, kontaktujte admina portálu.",
|
||||
"RecoverTitle": "Obnova prístupu",
|
||||
"TurnOnDesktopVersion": "Zapnúť počítačovú verziu"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Obnova dostopa",
|
||||
"RecoverContactEmailPlaceholder": "Kontaktni email",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Opišite vaš problem",
|
||||
"RecoverTextBody": "Če se ne morete prijaviti z obstoječim računom ali se želite registrirati kot nov uporabnik, se obrnite na skrbnika portala. ",
|
||||
"RecoverTitle": "Dostop za obnovo",
|
||||
"TurnOnDesktopVersion": "Vključi namizno verzijo"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Erişimi kurtar",
|
||||
"RecoverContactEmailPlaceholder": "İletişim e-postası",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Sorununuzu tarif edin",
|
||||
"RecoverTextBody": "Eğer hesabınıza giriş yapamıyorsanız veya yeni kullanıcı olarak kayıt olmak istiyorsanız, portal yöneticisi ile iletişime geçin. ",
|
||||
"RecoverTitle": "Erişim kurtarma",
|
||||
"TurnOnDesktopVersion": "Masaüstü sürümünü aç"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Відновити доступ",
|
||||
"RecoverContactEmailPlaceholder": "Контактна електронна пошта",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Опишіть свою проблему",
|
||||
"RecoverTextBody": "Якщо ви не можете увійти зі своїм існуючим обліковим записом або хочете зареєструватися як новий користувач, зверніться до адміністратора порталу.",
|
||||
"RecoverTitle": "Відновлення доступу",
|
||||
"TurnOnDesktopVersion": "Увімкнути настільну версію"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "Khôi phục quyền truy cập",
|
||||
"RecoverContactEmailPlaceholder": "Email liên hệ",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Mô tả vấn đề của bạn",
|
||||
"RecoverTextBody": "Nếu bạn không thể đăng nhập bằng tài khoản hiện có của mình hoặc muốn đăng ký làm người dùng mới, hãy liên hệ với quản trị viên cổng thông tin.",
|
||||
"RecoverTitle": "Khôi phục truy cập",
|
||||
"TurnOnDesktopVersion": "Bật phiên bản dành cho máy tính"
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
{
|
||||
"RecoverAccess": "恢复访问",
|
||||
"RecoverContactEmailPlaceholder": "联系邮箱",
|
||||
"RecoverDescribeYourProblemPlaceholder": "描述您的问题",
|
||||
"RecoverTextBody": "如果您无法使用现有账户登录或希望注册为新用户,请联系门户管理员。",
|
||||
"RecoverTitle": "访问恢复",
|
||||
"TurnOnDesktopVersion": "打开桌面版"
|
||||
}
|
||||
|
32
packages/client/src/DocspaceLogo/index.js
Normal file
32
packages/client/src/DocspaceLogo/index.js
Normal file
@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { ReactSVG } from "react-svg";
|
||||
import { hugeMobile } from "@docspace/components/utils/device";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.logo-wrapper {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
|
||||
@media ${hugeMobile} {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DocspaceLogo = (props) => {
|
||||
const { className } = props;
|
||||
if (isMobileOnly) return <></>;
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<ReactSVG
|
||||
src="/static/images/docspace.big.react.svg"
|
||||
className={`logo-wrapper ${className}`}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocspaceLogo;
|
@ -417,7 +417,6 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
|
||||
|
||||
const pathname = window.location.pathname.toLowerCase();
|
||||
const isEditor = pathname.indexOf("doceditor") !== -1;
|
||||
const isLogin = pathname.indexOf("login") !== -1;
|
||||
|
||||
const loginRoutes = [];
|
||||
|
||||
@ -463,7 +462,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
|
||||
<Router history={history}>
|
||||
<Toast />
|
||||
<ReactSmartBanner t={t} ready={ready} />
|
||||
{isEditor || isLogin || !isMobileOnly ? <></> : <NavMenu />}
|
||||
{isEditor || !isMobileOnly ? <></> : <NavMenu />}
|
||||
<IndicatorLoader />
|
||||
<ScrollToTop />
|
||||
<DialogsWrapper t={t} />
|
||||
|
@ -1,12 +1,6 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import {
|
||||
isIOS,
|
||||
isFirefox,
|
||||
isSafari,
|
||||
isMobile,
|
||||
isMobileOnly,
|
||||
} from "react-device-detect";
|
||||
import { isIOS, isFirefox, isMobileOnly } from "react-device-detect";
|
||||
|
||||
const StyledMain = styled.main`
|
||||
height: ${isIOS && !isFirefox ? "calc(var(--vh, 1vh) * 100)" : "100vh"};
|
||||
@ -41,6 +35,7 @@ const Main = React.memo((props) => {
|
||||
// }, []);
|
||||
|
||||
//console.log("Main render");
|
||||
|
||||
return <StyledMain className="main" {...props} />;
|
||||
});
|
||||
|
||||
|
@ -2,7 +2,6 @@ import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import Box from "@docspace/components/box";
|
||||
import RecoverAccess from "./recover-access-container";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { combineUrl } from "@docspace/common/utils";
|
||||
@ -24,7 +23,10 @@ const Header = styled.header`
|
||||
width: 475px;
|
||||
}
|
||||
@media (max-width: 375px) {
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
//padding: 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,10 +42,9 @@ const Header = styled.header`
|
||||
}
|
||||
|
||||
.header-logo-icon {
|
||||
width: 146px;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
padding: 3px 20px 0 6px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 12px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
@ -69,18 +70,11 @@ const HeaderUnAuth = ({
|
||||
{!isAuthenticated && isLoaded ? (
|
||||
<div>
|
||||
<a className="header-logo-wrapper" href="/">
|
||||
<img
|
||||
className="header-logo-min_icon"
|
||||
src={combineUrl(
|
||||
AppServerConfig.proxyURL,
|
||||
"/static/images/nav.logo.react.svg"
|
||||
)}
|
||||
/>
|
||||
<img
|
||||
className="header-logo-icon"
|
||||
src={combineUrl(
|
||||
AppServerConfig.proxyURL,
|
||||
"/static/images/nav.logo.opened.react.svg"
|
||||
"/static/images/logo.docspace.react.svg"
|
||||
)}
|
||||
/>
|
||||
</a>
|
||||
@ -88,8 +82,6 @@ const HeaderUnAuth = ({
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<div>{enableAdmMess && !wizardToken && <RecoverAccess t={t} />}</div>
|
||||
</Box>
|
||||
</Header>
|
||||
);
|
||||
|
@ -1,136 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import Box from "@docspace/components/box";
|
||||
import Text from "@docspace/components/text";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import UnionIcon from "../svg/union.react.svg";
|
||||
import RecoverAccessModalDialog from "./recover-access-modal-dialog";
|
||||
import { sendRecoverRequest } from "@docspace/common/api/settings/index";
|
||||
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
|
||||
import { Base } from "@docspace/components/themes";
|
||||
|
||||
const StyledUnionIcon = styled(UnionIcon)`
|
||||
${commonIconsStyles}
|
||||
`;
|
||||
|
||||
const RecoverContainer = styled(Box)`
|
||||
cursor: pointer;
|
||||
background-color: ${(props) => props.theme.header.recoveryColor};
|
||||
|
||||
.recover-icon {
|
||||
@media (max-width: 450px) {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
.recover-text {
|
||||
@media (max-width: 450px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
RecoverContainer.defaultProps = { theme: Base };
|
||||
|
||||
const RecoverAccess = ({ t }) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [email, setEmail] = useState("");
|
||||
const [emailErr, setEmailErr] = useState(false);
|
||||
|
||||
const [description, setDescription] = useState("");
|
||||
const [descErr, setDescErr] = useState(false);
|
||||
|
||||
const onRecoverClick = () => {
|
||||
setVisible(true);
|
||||
};
|
||||
const onRecoverModalClose = () => {
|
||||
setVisible(false);
|
||||
setEmail("");
|
||||
setEmailErr(false);
|
||||
setDescription("");
|
||||
setDescErr(false);
|
||||
};
|
||||
const onChangeEmail = (e) => {
|
||||
setEmail(e.currentTarget.value);
|
||||
setEmailErr(false);
|
||||
};
|
||||
const onChangeDescription = (e) => {
|
||||
setDescription(e.currentTarget.value);
|
||||
setDescErr(false);
|
||||
};
|
||||
const onSendRecoverRequest = () => {
|
||||
if (!email.trim()) {
|
||||
setEmailErr(true);
|
||||
}
|
||||
if (!description.trim()) {
|
||||
setDescErr(true);
|
||||
} else {
|
||||
setLoading(true);
|
||||
sendRecoverRequest(email, description)
|
||||
.then((res) => {
|
||||
setLoading(false);
|
||||
toastr.success(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
toastr.error(error);
|
||||
})
|
||||
.finally(onRecoverModalClose);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
widthProp="100%"
|
||||
heightProp="100%"
|
||||
displayProp="flex"
|
||||
justifyContent="flex-end"
|
||||
alignItems="center"
|
||||
>
|
||||
<RecoverContainer
|
||||
heightProp="100%"
|
||||
displayProp="flex"
|
||||
onClick={onRecoverClick}
|
||||
>
|
||||
<Box paddingProp="12px 8px 12px 12px" className="recover-icon">
|
||||
<StyledUnionIcon />
|
||||
</Box>
|
||||
<Box
|
||||
paddingProp="14px 12px 14px 0px"
|
||||
className="recover-text"
|
||||
widthProp="100%"
|
||||
>
|
||||
<Text color="#fff" isBold={true}>
|
||||
{t("RecoverAccess")}
|
||||
</Text>
|
||||
</Box>
|
||||
</RecoverContainer>
|
||||
</Box>
|
||||
{visible && (
|
||||
<RecoverAccessModalDialog
|
||||
visible={visible}
|
||||
loading={loading}
|
||||
email={email}
|
||||
emailErr={emailErr}
|
||||
description={description}
|
||||
descErr={descErr}
|
||||
t={t}
|
||||
onChangeEmail={onChangeEmail}
|
||||
onChangeDescription={onChangeDescription}
|
||||
onRecoverModalClose={onRecoverModalClose}
|
||||
onSendRecoverRequest={onSendRecoverRequest}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
RecoverAccess.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default RecoverAccess;
|
26
packages/client/src/pages/Confirm/ConfirmWrapper.js
Normal file
26
packages/client/src/pages/Confirm/ConfirmWrapper.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { isIOS, isFirefox, isMobileOnly } from "react-device-detect";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
height: ${isIOS && !isFirefox ? "calc(var(--vh, 1vh) * 100)" : "100vh"};
|
||||
width: 100vw;
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
|
||||
${isMobileOnly &&
|
||||
css`
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
`}
|
||||
`;
|
||||
|
||||
const ConfirmWrapper = (props) => {
|
||||
const { children, className } = props;
|
||||
return <StyledWrapper className={className}>{children}</StyledWrapper>;
|
||||
};
|
||||
|
||||
export default ConfirmWrapper;
|
@ -1,6 +1,7 @@
|
||||
import React, { lazy } from "react";
|
||||
import { Switch } from "react-router-dom";
|
||||
import ConfirmRoute from "SRC_DIR/helpers/confirmRoute";
|
||||
import ConfirmRoute from "../../helpers/confirmRoute";
|
||||
import ConfirmWrapper from "./ConfirmWrapper";
|
||||
|
||||
const ActivateUserForm = lazy(() => import("./sub-components/activateUser"));
|
||||
const CreateUserForm = lazy(() => import("./sub-components/createUser"));
|
||||
@ -19,56 +20,58 @@ const Confirm = ({ match }) => {
|
||||
//console.log("Confirm render");
|
||||
const path = match.path;
|
||||
return (
|
||||
<Switch>
|
||||
<ConfirmRoute
|
||||
forUnauthorized
|
||||
path={`${path}/LinkInvite`}
|
||||
component={CreateUserForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
forUnauthorized
|
||||
path={`${path}/Activation`}
|
||||
component={ActivateUserForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/EmailActivation`}
|
||||
component={ActivateEmailForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/EmailChange`}
|
||||
component={ChangeEmailForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
forUnauthorized
|
||||
path={`${path}/PasswordChange`}
|
||||
component={ChangePasswordForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/ProfileRemove`}
|
||||
component={ProfileRemoveForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/PhoneActivation`}
|
||||
component={ChangePhoneForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/PortalOwnerChange`}
|
||||
component={ChangeOwnerForm}
|
||||
/>
|
||||
<ConfirmRoute exact path={`${path}/TfaAuth`} component={TfaAuthForm} />
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/TfaActivation`}
|
||||
component={TfaActivationForm}
|
||||
/>
|
||||
<ConfirmWrapper className="with-background-pattern">
|
||||
<Switch>
|
||||
<ConfirmRoute
|
||||
forUnauthorized
|
||||
path={`${path}/LinkInvite`}
|
||||
component={CreateUserForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
forUnauthorized
|
||||
path={`${path}/Activation`}
|
||||
component={ActivateUserForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/EmailActivation`}
|
||||
component={ActivateEmailForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/EmailChange`}
|
||||
component={ChangeEmailForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
forUnauthorized
|
||||
path={`${path}/PasswordChange`}
|
||||
component={ChangePasswordForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/ProfileRemove`}
|
||||
component={ProfileRemoveForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/PhoneActivation`}
|
||||
component={ChangePhoneForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/PortalOwnerChange`}
|
||||
component={ChangeOwnerForm}
|
||||
/>
|
||||
<ConfirmRoute exact path={`${path}/TfaAuth`} component={TfaAuthForm} />
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${path}/TfaActivation`}
|
||||
component={TfaActivationForm}
|
||||
/>
|
||||
|
||||
{/* <Route component={Error404} /> */}
|
||||
</Switch>
|
||||
{/* <Route component={Error404} /> */}
|
||||
</Switch>
|
||||
</ConfirmWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -13,28 +13,41 @@ export const StyledPage = styled.div`
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
margin-top: 72px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledHeader = styled.div`
|
||||
text-align: left;
|
||||
|
||||
.title {
|
||||
margin-bottom: 24px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.password-form {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledHeader = styled.div`
|
||||
.title {
|
||||
margin-bottom: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.docspace-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledBody = styled.div`
|
||||
width: 320px;
|
||||
|
||||
@media ${tablet} {
|
||||
justify-content: center;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
@media ${mobile} {
|
||||
width: 100%;
|
||||
@ -48,37 +61,27 @@ export const StyledBody = styled.div`
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.password-change-form {
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.confirm-subtitle {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-delete {
|
||||
.phone-input {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.phone-input {
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px;
|
||||
.delete-profile-confirm {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.phone-title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ButtonsWrapper = styled.div`
|
||||
display: flex;
|
||||
flex: 1fr 1fr;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
width: 100%;
|
||||
`;
|
||||
|
@ -12,6 +12,8 @@ import {
|
||||
ButtonsWrapper,
|
||||
} from "./StyledConfirm";
|
||||
import withLoader from "../withLoader";
|
||||
import FormWrapper from "@docspace/components/form-wrapper";
|
||||
import DocspaceLogo from "../../../DocspaceLogo";
|
||||
|
||||
const ChangeOwnerForm = (props) => {
|
||||
const { t, greetingTitle } = props;
|
||||
@ -20,34 +22,36 @@ const ChangeOwnerForm = (props) => {
|
||||
<StyledPage>
|
||||
<StyledBody>
|
||||
<StyledHeader>
|
||||
<DocspaceLogo className="docspace-logo" />
|
||||
<Text fontSize="23px" fontWeight="700" className="title">
|
||||
{greetingTitle}
|
||||
</Text>
|
||||
</StyledHeader>
|
||||
|
||||
<FormWrapper>
|
||||
<Text className="subtitle">
|
||||
{t("ConfirmOwnerPortalTitle", { newOwner: "NEW OWNER" })}
|
||||
</Text>
|
||||
</StyledHeader>
|
||||
|
||||
<ButtonsWrapper>
|
||||
<Button
|
||||
className="button"
|
||||
primary
|
||||
size="normal"
|
||||
label={t("Common:SaveButton")}
|
||||
tabIndex={2}
|
||||
isDisabled={false}
|
||||
//onClick={this.onAcceptClick} // call toast with t("ConfirmOwnerPortalSuccessMessage")
|
||||
/>
|
||||
<Button
|
||||
className="button"
|
||||
size="normal"
|
||||
label={t("Common:CancelButton")}
|
||||
tabIndex={2}
|
||||
isDisabled={false}
|
||||
//onClick={this.onCancelClick}
|
||||
/>
|
||||
</ButtonsWrapper>
|
||||
<ButtonsWrapper>
|
||||
<Button
|
||||
primary
|
||||
scale
|
||||
size="medium"
|
||||
label={t("Common:SaveButton")}
|
||||
tabIndex={2}
|
||||
isDisabled={false}
|
||||
//onClick={this.onAcceptClick} // call toast with t("ConfirmOwnerPortalSuccessMessage")
|
||||
/>
|
||||
<Button
|
||||
scale
|
||||
size="medium"
|
||||
label={t("Common:CancelButton")}
|
||||
tabIndex={2}
|
||||
isDisabled={false}
|
||||
//onClick={this.onCancelClick}
|
||||
/>
|
||||
</ButtonsWrapper>
|
||||
</FormWrapper>
|
||||
</StyledBody>
|
||||
</StyledPage>
|
||||
);
|
||||
|
@ -9,10 +9,12 @@ import FieldContainer from "@docspace/components/field-container";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
|
||||
import withLoader from "../withLoader";
|
||||
import { getPasswordErrorMessage } from "SRC_DIR/helpers/utils";
|
||||
import { getPasswordErrorMessage } from "../../../helpers/utils";
|
||||
import { createPasswordHash } from "@docspace/common/utils";
|
||||
import tryRedirectTo from "@docspace/common/utils/tryRedirectTo";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import FormWrapper from "@docspace/components/form-wrapper";
|
||||
import DocspaceLogo from "../../../DocspaceLogo";
|
||||
|
||||
const ChangePasswordForm = (props) => {
|
||||
const {
|
||||
@ -81,63 +83,66 @@ const ChangePasswordForm = (props) => {
|
||||
<StyledPage>
|
||||
<StyledBody>
|
||||
<StyledHeader>
|
||||
<Text fontSize="23px" fontWeight="700">
|
||||
<DocspaceLogo className="docspace-logo" />
|
||||
<Text fontSize="23px" fontWeight="700" className="title">
|
||||
{greetingTitle}
|
||||
</Text>
|
||||
</StyledHeader>
|
||||
|
||||
<div className="password-change-form">
|
||||
<Text className="confirm-subtitle">{t("PassworResetTitle")}</Text>
|
||||
<FieldContainer
|
||||
className="form-field"
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={isPasswordErrorShow && !passwordValid}
|
||||
errorMessage={`${t(
|
||||
"Common:PasswordLimitMessage"
|
||||
)}: ${getPasswordErrorMessage(t, settings)}`}
|
||||
>
|
||||
<PasswordInput
|
||||
className="confirm-input"
|
||||
simpleView={false}
|
||||
passwordSettings={settings}
|
||||
id="password"
|
||||
inputName="password"
|
||||
placeholder={t("Common:Password")}
|
||||
type="password"
|
||||
inputValue={password}
|
||||
<FormWrapper>
|
||||
<div className="password-form">
|
||||
<Text fontSize="16px" fontWeight="600" className="subtitle">
|
||||
{t("PassworResetTitle")}
|
||||
</Text>
|
||||
<FieldContainer
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={isPasswordErrorShow && !passwordValid}
|
||||
size="large"
|
||||
scale={true}
|
||||
tabIndex={1}
|
||||
autoComplete="current-password"
|
||||
onChange={onChangePassword}
|
||||
onValidateInput={onValidatePassword}
|
||||
onBlur={onBlurPassword}
|
||||
onKeyDown={onKeyPress}
|
||||
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
|
||||
tooltipPasswordLength={`${t("Common:PasswordMinimumLength")}: ${
|
||||
settings ? settings.minLength : 8
|
||||
}`}
|
||||
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
|
||||
tooltipPasswordCapital={`${t("Common:PasswordLimitUpperCase")}`}
|
||||
tooltipPasswordSpecial={`${t(
|
||||
"Common:PasswordLimitSpecialSymbols"
|
||||
)}`}
|
||||
generatePasswordTitle={t("Wizard:GeneratePassword")}
|
||||
/>
|
||||
</FieldContainer>
|
||||
</div>
|
||||
errorMessage={`${t(
|
||||
"Common:PasswordLimitMessage"
|
||||
)}: ${getPasswordErrorMessage(t, settings)}`}
|
||||
>
|
||||
<PasswordInput
|
||||
simpleView={false}
|
||||
passwordSettings={settings}
|
||||
id="password"
|
||||
inputName="password"
|
||||
placeholder={t("Common:Password")}
|
||||
type="password"
|
||||
inputValue={password}
|
||||
hasError={isPasswordErrorShow && !passwordValid}
|
||||
size="large"
|
||||
scale
|
||||
tabIndex={1}
|
||||
autoComplete="current-password"
|
||||
onChange={onChangePassword}
|
||||
onValidateInput={onValidatePassword}
|
||||
onBlur={onBlurPassword}
|
||||
onKeyDown={onKeyPress}
|
||||
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
|
||||
tooltipPasswordLength={`${t("Common:PasswordMinimumLength")}: ${
|
||||
settings ? settings.minLength : 8
|
||||
}`}
|
||||
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
|
||||
tooltipPasswordCapital={`${t("Common:PasswordLimitUpperCase")}`}
|
||||
tooltipPasswordSpecial={`${t(
|
||||
"Common:PasswordLimitSpecialSymbols"
|
||||
)}`}
|
||||
generatePasswordTitle={t("Wizard:GeneratePassword")}
|
||||
/>
|
||||
</FieldContainer>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="confirm-button"
|
||||
primary
|
||||
size="normal"
|
||||
label={t("Common:Create")}
|
||||
tabIndex={5}
|
||||
onClick={onSubmit}
|
||||
isDisabled={isLoading}
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
size="medium"
|
||||
scale
|
||||
label={t("Common:Create")}
|
||||
tabIndex={5}
|
||||
onClick={onSubmit}
|
||||
isDisabled={isLoading}
|
||||
/>
|
||||
</FormWrapper>
|
||||
</StyledBody>
|
||||
</StyledPage>
|
||||
);
|
||||
|
@ -8,6 +8,8 @@ import Section from "@docspace/common/components/Section";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
|
||||
import withLoader from "../withLoader";
|
||||
import FormWrapper from "@docspace/components/form-wrapper";
|
||||
import DocspaceLogo from "../../../DocspaceLogo";
|
||||
|
||||
const ChangePhoneForm = (props) => {
|
||||
const { t, greetingTitle } = props;
|
||||
@ -17,39 +19,45 @@ const ChangePhoneForm = (props) => {
|
||||
<StyledPage>
|
||||
<StyledBody>
|
||||
<StyledHeader>
|
||||
<DocspaceLogo className="docspace-logo" />
|
||||
<Text fontSize="23px" fontWeight="700" className="title">
|
||||
{greetingTitle}
|
||||
</Text>
|
||||
<Text fontSize="16px" fontWeight="600" className="confirm-subtitle">
|
||||
{t("EnterPhone")}
|
||||
</Text>
|
||||
<Text>
|
||||
{t("CurrentNumber")}: {currentNumber}
|
||||
</Text>
|
||||
<Text>{t("PhoneSubtitle")}</Text>
|
||||
</StyledHeader>
|
||||
|
||||
<TextInput
|
||||
className="phone-input"
|
||||
id="phone"
|
||||
name="phone"
|
||||
type="phone"
|
||||
size="large"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
tabIndex={1}
|
||||
hasError={false}
|
||||
guide={false}
|
||||
/>
|
||||
<FormWrapper>
|
||||
<div className="subtitle">
|
||||
<Text fontSize="16px" fontWeight="600" className="phone-title">
|
||||
{t("EnterPhone")}
|
||||
</Text>
|
||||
<Text>
|
||||
{t("CurrentNumber")}: {currentNumber}
|
||||
</Text>
|
||||
<Text>{t("PhoneSubtitle")}</Text>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="confirm-button"
|
||||
primary
|
||||
size="normal"
|
||||
label={t("GetCode")}
|
||||
tabIndex={2}
|
||||
isDisabled={false}
|
||||
/>
|
||||
<TextInput
|
||||
className="phone-input"
|
||||
id="phone"
|
||||
name="phone"
|
||||
type="phone"
|
||||
size="large"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
tabIndex={1}
|
||||
hasError={false}
|
||||
guide={false}
|
||||
/>
|
||||
|
||||
<Button
|
||||
primary
|
||||
scale
|
||||
size="medium"
|
||||
label={t("GetCode")}
|
||||
tabIndex={2}
|
||||
isDisabled={false}
|
||||
/>
|
||||
</FormWrapper>
|
||||
</StyledBody>
|
||||
</StyledPage>
|
||||
);
|
||||
|
@ -14,7 +14,6 @@ import PasswordInput from "@docspace/components/password-input";
|
||||
import FieldContainer from "@docspace/components/field-container";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import SocialButton from "@docspace/components/social-button";
|
||||
//import FacebookButton from "@docspace/components/facebook-button";
|
||||
import {
|
||||
getAuthProviders,
|
||||
getCapabilities,
|
||||
@ -30,16 +29,15 @@ import withLoader from "../withLoader";
|
||||
import MoreLoginModal from "login/moreLogin";
|
||||
import AppLoader from "@docspace/common/components/AppLoader";
|
||||
import EmailInput from "@docspace/components/email-input";
|
||||
import { getPasswordErrorMessage } from "SRC_DIR/helpers/utils";
|
||||
import { hugeMobile, tablet } from "@docspace/components/utils/device";
|
||||
import { getPasswordErrorMessage } from "../../../helpers/utils";
|
||||
import FormWrapper from "@docspace/components/form-wrapper";
|
||||
import DocspaceLogo from "../../../DocspaceLogo";
|
||||
|
||||
export const ButtonsWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 320px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
}
|
||||
width: 100%;
|
||||
|
||||
.buttonWrapper {
|
||||
margin-bottom: 8px;
|
||||
@ -48,15 +46,29 @@ export const ButtonsWrapper = styled.div`
|
||||
`;
|
||||
|
||||
const ConfirmContainer = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 70px;
|
||||
align-items: center;
|
||||
margin: 80px auto 0 auto;
|
||||
max-width: 960px;
|
||||
display: flex;
|
||||
flex: 1fr 1fr;
|
||||
gap: 80px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin-top: 80px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
@media ${tablet} {
|
||||
margin: 100px auto 0 auto;
|
||||
display: flex;
|
||||
flex: 1fr;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 80px;
|
||||
}
|
||||
|
||||
@media ${hugeMobile} {
|
||||
margin-top: 32px;
|
||||
width: 100%;
|
||||
flex: 1fr;
|
||||
flex-direction: column;
|
||||
gap: 80px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -71,9 +83,17 @@ const GreetingContainer = styled.div`
|
||||
display: ${(props) => !props.isGreetingMode && "none"};
|
||||
}
|
||||
|
||||
@media ${hugeMobile} {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.greeting-title {
|
||||
width: 100%;
|
||||
padding-bottom: 32px;
|
||||
|
||||
@media ${tablet} {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.greeting-block {
|
||||
@ -107,6 +127,21 @@ const GreetingContainer = styled.div`
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.docspace-logo {
|
||||
padding-bottom: 32px;
|
||||
|
||||
.injected-svg {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
@media ${tablet} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const RegisterContainer = styled.div`
|
||||
@ -114,6 +149,7 @@ const RegisterContainer = styled.div`
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.or-label {
|
||||
margin: 0 8px;
|
||||
@ -125,18 +161,10 @@ const RegisterContainer = styled.div`
|
||||
|
||||
.line {
|
||||
display: flex;
|
||||
width: 320px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
color: #eceef1;
|
||||
padding-top: 35px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
@media (max-width: 414px) {
|
||||
width: 311px;
|
||||
}
|
||||
}
|
||||
|
||||
.line:before,
|
||||
@ -152,7 +180,7 @@ const RegisterContainer = styled.div`
|
||||
|
||||
.auth-form-container {
|
||||
margin-top: 32px;
|
||||
width: 320px;
|
||||
width: 100%;
|
||||
|
||||
.form-field {
|
||||
height: 48px;
|
||||
@ -527,6 +555,7 @@ const Confirm = (props) => {
|
||||
return (
|
||||
<ConfirmContainer>
|
||||
<GreetingContainer isGreetingMode={isGreetingMode}>
|
||||
<DocspaceLogo className="docspace-logo" />
|
||||
<Text
|
||||
fontSize="23px"
|
||||
fontWeight={700}
|
||||
@ -553,162 +582,183 @@ const Confirm = (props) => {
|
||||
</div>
|
||||
</GreetingContainer>
|
||||
|
||||
<RegisterContainer isGreetingMode={isGreetingMode}>
|
||||
{ssoExists() && <ButtonsWrapper>{ssoButton()}</ButtonsWrapper>}
|
||||
<FormWrapper>
|
||||
<RegisterContainer isGreetingMode={isGreetingMode}>
|
||||
{ssoExists() && <ButtonsWrapper>{ssoButton()}</ButtonsWrapper>}
|
||||
|
||||
{oauthDataExists() && (
|
||||
<>
|
||||
<ButtonsWrapper>{providerButtons()}</ButtonsWrapper>
|
||||
{providers && providers.length > 2 && (
|
||||
<Link
|
||||
isHovered
|
||||
type="action"
|
||||
fontSize="13px"
|
||||
fontWeight="600"
|
||||
color="#3B72A7"
|
||||
className="more-label"
|
||||
onClick={moreAuthOpen}
|
||||
>
|
||||
{t("Common:ShowMore")}
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{oauthDataExists() && (
|
||||
<>
|
||||
<ButtonsWrapper>{providerButtons()}</ButtonsWrapper>
|
||||
{providers && providers.length > 2 && (
|
||||
<Link
|
||||
isHovered
|
||||
type="action"
|
||||
fontSize="13px"
|
||||
fontWeight="600"
|
||||
color="#3B72A7"
|
||||
className="more-label"
|
||||
onClick={moreAuthOpen}
|
||||
>
|
||||
{t("Common:ShowMore")}
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{(oauthDataExists() || ssoExists()) && (
|
||||
<div className="line">
|
||||
<Text color="#A3A9AE" className="or-label">
|
||||
{t("Or")}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
{(oauthDataExists() || ssoExists()) && (
|
||||
<div className="line">
|
||||
<Text color="#A3A9AE" className="or-label">
|
||||
{t("Or")}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form className="auth-form-container">
|
||||
<div className="auth-form-fields">
|
||||
<FieldContainer
|
||||
className="form-field"
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={isEmailErrorShow && !emailValid}
|
||||
errorMessage={
|
||||
emailErrorText
|
||||
? t(`Common:${emailErrorText}`)
|
||||
: t("Common:RequiredField")
|
||||
}
|
||||
>
|
||||
<EmailInput
|
||||
id="login"
|
||||
name="login"
|
||||
type="email"
|
||||
<form className="auth-form-container">
|
||||
<div className="auth-form-fields">
|
||||
<FieldContainer
|
||||
className="form-field"
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={isEmailErrorShow && !emailValid}
|
||||
value={email}
|
||||
placeholder={t("Common:Email")}
|
||||
size="large"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
autoComplete="username"
|
||||
onChange={onChangeEmail}
|
||||
onBlur={onBlurEmail}
|
||||
onValidateInput={onValidateEmail}
|
||||
forwardedRef={inputRef}
|
||||
/>
|
||||
</FieldContainer>
|
||||
errorMessage={
|
||||
emailErrorText
|
||||
? t(`Common:${emailErrorText}`)
|
||||
: t("Common:RequiredField")
|
||||
}
|
||||
>
|
||||
<EmailInput
|
||||
id="login"
|
||||
name="login"
|
||||
type="email"
|
||||
hasError={isEmailErrorShow && !emailValid}
|
||||
value={email}
|
||||
placeholder={t("Common:Email")}
|
||||
size="large"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
autoComplete="username"
|
||||
onChange={onChangeEmail}
|
||||
onBlur={onBlurEmail}
|
||||
onValidateInput={onValidateEmail}
|
||||
forwardedRef={inputRef}
|
||||
/>
|
||||
</FieldContainer>
|
||||
|
||||
<FieldContainer
|
||||
className="form-field"
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={!fnameValid}
|
||||
errorMessage={errorText ? errorText : t("Common:RequiredField")}
|
||||
>
|
||||
<TextInput
|
||||
id="first-name"
|
||||
name="first-name"
|
||||
type="text"
|
||||
<FieldContainer
|
||||
className="form-field"
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={!fnameValid}
|
||||
value={fname}
|
||||
placeholder={t("FirstName")}
|
||||
size="large"
|
||||
scale={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
onChange={onChangeFname}
|
||||
onKeyDown={onKeyPress}
|
||||
/>
|
||||
</FieldContainer>
|
||||
errorMessage={errorText ? errorText : t("Common:RequiredField")}
|
||||
>
|
||||
<TextInput
|
||||
id="first-name"
|
||||
name="first-name"
|
||||
type="text"
|
||||
hasError={!fnameValid}
|
||||
value={fname}
|
||||
placeholder={t("FirstName")}
|
||||
size="large"
|
||||
scale={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
onChange={onChangeFname}
|
||||
onKeyDown={onKeyPress}
|
||||
/>
|
||||
</FieldContainer>
|
||||
|
||||
<FieldContainer
|
||||
className="form-field"
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={!snameValid}
|
||||
errorMessage={errorText ? errorText : t("Common:RequiredField")}
|
||||
>
|
||||
<TextInput
|
||||
id="last-name"
|
||||
name="last-name"
|
||||
type="text"
|
||||
<FieldContainer
|
||||
className="form-field"
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={!snameValid}
|
||||
value={sname}
|
||||
placeholder={t("Common:LastName")}
|
||||
size="large"
|
||||
scale={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
onChange={onChangeSname}
|
||||
onKeyDown={onKeyPress}
|
||||
/>
|
||||
</FieldContainer>
|
||||
errorMessage={errorText ? errorText : t("Common:RequiredField")}
|
||||
>
|
||||
<TextInput
|
||||
id="last-name"
|
||||
name="last-name"
|
||||
type="text"
|
||||
hasError={!snameValid}
|
||||
value={sname}
|
||||
placeholder={t("Common:LastName")}
|
||||
size="large"
|
||||
scale={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
onChange={onChangeSname}
|
||||
onKeyDown={onKeyPress}
|
||||
/>
|
||||
</FieldContainer>
|
||||
|
||||
<FieldContainer
|
||||
className="form-field"
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={isPasswordErrorShow && !passwordValid}
|
||||
errorMessage={`${t(
|
||||
"Common:PasswordLimitMessage"
|
||||
)}: ${getPasswordErrorMessage(t, settings)}`}
|
||||
>
|
||||
<PasswordInput
|
||||
simpleView={false}
|
||||
hideNewPasswordButton
|
||||
showCopyLink={false}
|
||||
passwordSettings={settings}
|
||||
id="password"
|
||||
inputName="password"
|
||||
placeholder={t("Common:Password")}
|
||||
type="password"
|
||||
<FieldContainer
|
||||
className="form-field"
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={isPasswordErrorShow && !passwordValid}
|
||||
inputValue={password}
|
||||
size="large"
|
||||
errorMessage={`${t(
|
||||
"Common:PasswordLimitMessage"
|
||||
)}: ${getPasswordErrorMessage(t, settings)}`}
|
||||
>
|
||||
<PasswordInput
|
||||
simpleView={false}
|
||||
hideNewPasswordButton
|
||||
showCopyLink={false}
|
||||
passwordSettings={settings}
|
||||
id="password"
|
||||
inputName="password"
|
||||
placeholder={t("Common:Password")}
|
||||
type="password"
|
||||
hasError={isPasswordErrorShow && !passwordValid}
|
||||
inputValue={password}
|
||||
size="large"
|
||||
scale={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
autoComplete="current-password"
|
||||
onChange={onChangePassword}
|
||||
onBlur={onBlurPassword}
|
||||
onKeyDown={onKeyPress}
|
||||
onValidateInput={onValidatePassword}
|
||||
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
|
||||
tooltipPasswordLength={`${t(
|
||||
"Common:PasswordMinimumLength"
|
||||
)}: ${settings ? settings.minLength : 8}`}
|
||||
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
|
||||
tooltipPasswordCapital={`${t(
|
||||
"Common:PasswordLimitUpperCase"
|
||||
)}`}
|
||||
tooltipPasswordSpecial={`${t(
|
||||
"Common:PasswordLimitSpecialSymbols"
|
||||
)}`}
|
||||
generatePasswordTitle={t("Wizard:GeneratePassword")}
|
||||
/>
|
||||
</FieldContainer>
|
||||
|
||||
<Button
|
||||
id="submit"
|
||||
className="login-button"
|
||||
primary
|
||||
size="medium"
|
||||
scale={true}
|
||||
label={
|
||||
isLoading
|
||||
? t("Common:LoadingProcessing")
|
||||
: t("LoginRegistryButton")
|
||||
}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
autoComplete="current-password"
|
||||
onChange={onChangePassword}
|
||||
onBlur={onBlurPassword}
|
||||
onKeyDown={onKeyPress}
|
||||
onValidateInput={onValidatePassword}
|
||||
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
|
||||
tooltipPasswordLength={`${t("Common:PasswordMinimumLength")}: ${
|
||||
settings ? settings.minLength : 8
|
||||
}`}
|
||||
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
|
||||
tooltipPasswordCapital={`${t("Common:PasswordLimitUpperCase")}`}
|
||||
tooltipPasswordSpecial={`${t(
|
||||
"Common:PasswordLimitSpecialSymbols"
|
||||
)}`}
|
||||
generatePasswordTitle={t("Wizard:GeneratePassword")}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</FieldContainer>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
id="submit"
|
||||
className="login-button"
|
||||
className="login-button is-greeting-mode-button"
|
||||
primary
|
||||
size="normal"
|
||||
size="medium"
|
||||
scale={true}
|
||||
label={
|
||||
isLoading
|
||||
@ -718,38 +768,21 @@ const Confirm = (props) => {
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit}
|
||||
onClick={onGreetingSubmit}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<Button
|
||||
id="submit"
|
||||
className="login-button is-greeting-mode-button"
|
||||
primary
|
||||
size="normal"
|
||||
scale={true}
|
||||
label={
|
||||
isLoading
|
||||
? t("Common:LoadingProcessing")
|
||||
: t("LoginRegistryButton")
|
||||
}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onGreetingSubmit}
|
||||
<MoreLoginModal
|
||||
t={t}
|
||||
visible={moreAuthVisible}
|
||||
onClose={moreAuthClose}
|
||||
providers={providers}
|
||||
onSocialLoginClick={onSocialButtonClick}
|
||||
ssoLabel={ssoLabel}
|
||||
ssoUrl={ssoUrl}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<MoreLoginModal
|
||||
t={t}
|
||||
visible={moreAuthVisible}
|
||||
onClose={moreAuthClose}
|
||||
providers={providers}
|
||||
onSocialLoginClick={onSocialButtonClick}
|
||||
ssoLabel={ssoLabel}
|
||||
ssoUrl={ssoUrl}
|
||||
/>
|
||||
</RegisterContainer>
|
||||
</RegisterContainer>
|
||||
</FormWrapper>
|
||||
</ConfirmContainer>
|
||||
);
|
||||
};
|
||||
|
@ -9,6 +9,8 @@ import { deleteSelf } from "@docspace/common/api/people";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
|
||||
import withLoader from "../withLoader";
|
||||
import FormWrapper from "@docspace/components/form-wrapper";
|
||||
import DocspaceLogo from "../../../DocspaceLogo";
|
||||
|
||||
const ProfileRemoveForm = (props) => {
|
||||
const { t, greetingTitle, linkData, logout } = props;
|
||||
@ -35,6 +37,7 @@ const ProfileRemoveForm = (props) => {
|
||||
<StyledPage>
|
||||
<StyledBody>
|
||||
<StyledHeader>
|
||||
<DocspaceLogo className="docspace-logo" />
|
||||
<Text fontSize="23px" fontWeight="700" className="title">
|
||||
{t("DeleteProfileSuccessMessage")}
|
||||
</Text>
|
||||
@ -51,26 +54,34 @@ const ProfileRemoveForm = (props) => {
|
||||
<StyledPage>
|
||||
<StyledBody>
|
||||
<StyledHeader>
|
||||
<DocspaceLogo className="docspace-logo" />
|
||||
<Text fontSize="23px" fontWeight="700" className="title">
|
||||
{greetingTitle}
|
||||
</Text>
|
||||
<Text fontSize="16px" fontWeight="600" className="confirm-subtitle">
|
||||
{t("DeleteProfileConfirmation")}
|
||||
</Text>
|
||||
<Text className="info-delete">
|
||||
{t("DeleteProfileConfirmationInfo")}
|
||||
</Text>
|
||||
</StyledHeader>
|
||||
|
||||
<Button
|
||||
className="confirm-button"
|
||||
primary
|
||||
size="normal"
|
||||
label={t("DeleteProfileBtn")}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
onClick={onDeleteProfile}
|
||||
/>
|
||||
<FormWrapper>
|
||||
<div className="subtitle">
|
||||
<Text
|
||||
fontSize="16px"
|
||||
fontWeight="600"
|
||||
className="delete-profile-confirm"
|
||||
>
|
||||
{t("DeleteProfileConfirmation")}
|
||||
</Text>
|
||||
<Text>{t("DeleteProfileConfirmationInfo")}</Text>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
primary
|
||||
scale
|
||||
size="medium"
|
||||
label={t("DeleteProfileBtn")}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
onClick={onDeleteProfile}
|
||||
/>
|
||||
</FormWrapper>
|
||||
</StyledBody>
|
||||
</StyledPage>
|
||||
);
|
||||
|
@ -14,36 +14,53 @@ import toastr from "client/toastr";
|
||||
import ErrorContainer from "@docspace/common/components/ErrorContainer";
|
||||
import { hugeMobile, tablet } from "@docspace/components/utils/device";
|
||||
import Link from "@docspace/components/link";
|
||||
import FormWrapper from "@docspace/components/form-wrapper";
|
||||
import DocspaceLogo from "../../../DocspaceLogo";
|
||||
|
||||
const StyledForm = styled(Box)`
|
||||
margin: 63px auto auto 216px;
|
||||
margin-top: 63px;
|
||||
display: flex;
|
||||
flex: 1fr 1fr;
|
||||
gap: 80px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
@media ${tablet} {
|
||||
margin: 120px auto;
|
||||
width: 480px;
|
||||
margin: 100px auto 0 auto;
|
||||
display: flex;
|
||||
flex: 1fr;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
@media ${hugeMobile} {
|
||||
margin-top: 72px;
|
||||
margin-top: 32px;
|
||||
width: 100%;
|
||||
flex: 1fr;
|
||||
flex-direction: column;
|
||||
gap: 0px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.app-code-wrapper {
|
||||
width: 100%;
|
||||
|
||||
@media ${tablet} {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.docspace-logo {
|
||||
padding-bottom: 64px;
|
||||
|
||||
@media ${tablet} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.set-app-description {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
@ -61,8 +78,7 @@ const StyledForm = styled(Box)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px 80px;
|
||||
background: #f8f9f9;
|
||||
padding: 0px 80px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 32px;
|
||||
|
||||
@ -70,6 +86,10 @@ const StyledForm = styled(Box)`
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.app-code-continue-btn {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
const TfaActivationForm = withLoader((props) => {
|
||||
const {
|
||||
@ -114,6 +134,7 @@ const TfaActivationForm = withLoader((props) => {
|
||||
return (
|
||||
<StyledForm className="set-app-container">
|
||||
<Box className="set-app-description" marginProp="0 0 32px 0">
|
||||
<DocspaceLogo className="docspace-logo" />
|
||||
<Text isBold fontSize="14px" className="set-app-title">
|
||||
{t("SetAppTitle")}
|
||||
</Text>
|
||||
@ -149,56 +170,58 @@ const TfaActivationForm = withLoader((props) => {
|
||||
</Trans>
|
||||
</Text>
|
||||
</Box>
|
||||
<Box
|
||||
displayProp="flex"
|
||||
flexDirection="column"
|
||||
className="app-code-wrapper"
|
||||
>
|
||||
<div className="qrcode-wrapper">
|
||||
<img src={qrCode} height="180px" width="180px" alt="QR-code"></img>
|
||||
</div>
|
||||
<Box className="app-code-input">
|
||||
<FieldContainer
|
||||
labelVisible={false}
|
||||
hasError={error ? true : false}
|
||||
errorMessage={error}
|
||||
>
|
||||
<TextInput
|
||||
id="code"
|
||||
name="code"
|
||||
type="text"
|
||||
size="large"
|
||||
scale
|
||||
isAutoFocussed
|
||||
tabIndex={1}
|
||||
placeholder={t("EnterCodePlaceholder")}
|
||||
isDisabled={isLoading}
|
||||
maxLength={6}
|
||||
onChange={(e) => {
|
||||
setCode(e.target.value);
|
||||
setError("");
|
||||
}}
|
||||
value={code}
|
||||
<FormWrapper>
|
||||
<Box
|
||||
displayProp="flex"
|
||||
flexDirection="column"
|
||||
className="app-code-wrapper"
|
||||
>
|
||||
<div className="qrcode-wrapper">
|
||||
<img src={qrCode} height="180px" width="180px" alt="QR-code"></img>
|
||||
</div>
|
||||
<Box className="app-code-input">
|
||||
<FieldContainer
|
||||
labelVisible={false}
|
||||
hasError={error ? true : false}
|
||||
onKeyDown={onKeyPress}
|
||||
errorMessage={error}
|
||||
>
|
||||
<TextInput
|
||||
id="code"
|
||||
name="code"
|
||||
type="text"
|
||||
size="large"
|
||||
scale
|
||||
isAutoFocussed
|
||||
tabIndex={1}
|
||||
placeholder={t("EnterCodePlaceholder")}
|
||||
isDisabled={isLoading}
|
||||
maxLength={6}
|
||||
onChange={(e) => {
|
||||
setCode(e.target.value);
|
||||
setError("");
|
||||
}}
|
||||
value={code}
|
||||
hasError={error ? true : false}
|
||||
onKeyDown={onKeyPress}
|
||||
/>
|
||||
</FieldContainer>
|
||||
</Box>
|
||||
<Box className="app-code-continue-btn">
|
||||
<Button
|
||||
scale
|
||||
primary
|
||||
size="medium"
|
||||
tabIndex={3}
|
||||
label={
|
||||
isLoading ? t("Common:LoadingProcessing") : t("SetAppButton")
|
||||
}
|
||||
isDisabled={!code.length || isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</FieldContainer>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className="app-code-continue-btn">
|
||||
<Button
|
||||
scale
|
||||
primary
|
||||
size="medium"
|
||||
tabIndex={3}
|
||||
label={
|
||||
isLoading ? t("Common:LoadingProcessing") : t("SetAppButton")
|
||||
}
|
||||
isDisabled={!code.length || isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</FormWrapper>
|
||||
</StyledForm>
|
||||
);
|
||||
});
|
||||
|
@ -16,6 +16,8 @@ import {
|
||||
smallTablet,
|
||||
tablet,
|
||||
} from "@docspace/components/utils/device";
|
||||
import FormWrapper from "@docspace/components/form-wrapper";
|
||||
import DocspaceLogo from "../../../DocspaceLogo";
|
||||
|
||||
const StyledForm = styled(Box)`
|
||||
margin: 63px auto;
|
||||
@ -34,19 +36,28 @@ const StyledForm = styled(Box)`
|
||||
}
|
||||
|
||||
@media ${hugeMobile} {
|
||||
margin: 72px 8px auto 8px;
|
||||
padding: 16px;
|
||||
margin: 32px 8px auto 8px;
|
||||
padding-left: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.docspace-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
|
||||
.app-code-wrapper {
|
||||
@media ${tablet} {
|
||||
flex-direction: column;
|
||||
}
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app-code-text {
|
||||
margin-bottom: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.app-code-continue-btn {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -82,65 +93,66 @@ const TfaAuthForm = withLoader((props) => {
|
||||
if (target.code === "Enter" || target.code === "NumpadEnter") onSubmit();
|
||||
};
|
||||
|
||||
const width = window.innerWidth;
|
||||
|
||||
return (
|
||||
<StyledForm className="app-code-container">
|
||||
<Box className="app-code-description" marginProp="0 0 32px 0">
|
||||
<Text isBold fontSize="14px" className="app-code-text">
|
||||
{t("EnterAppCodeTitle")}
|
||||
</Text>
|
||||
<Text>{t("EnterAppCodeDescription")}</Text>
|
||||
</Box>
|
||||
<Box
|
||||
displayProp="flex"
|
||||
flexDirection="column"
|
||||
className="app-code-wrapper"
|
||||
>
|
||||
<Box className="app-code-input">
|
||||
<FieldContainer
|
||||
labelVisible={false}
|
||||
hasError={error ? true : false}
|
||||
errorMessage={error}
|
||||
>
|
||||
<TextInput
|
||||
id="code"
|
||||
name="code"
|
||||
type="text"
|
||||
size="huge"
|
||||
scale
|
||||
isAutoFocussed
|
||||
tabIndex={1}
|
||||
placeholder={t("EnterCodePlaceholder")}
|
||||
isDisabled={isLoading}
|
||||
maxLength={6}
|
||||
onChange={(e) => {
|
||||
setCode(e.target.value);
|
||||
setError("");
|
||||
}}
|
||||
value={code}
|
||||
<DocspaceLogo className="docspace-logo" />
|
||||
<FormWrapper>
|
||||
<Box className="app-code-description" marginProp="0 0 32px 0">
|
||||
<Text isBold fontSize="14px" className="app-code-text">
|
||||
{t("EnterAppCodeTitle")}
|
||||
</Text>
|
||||
<Text>{t("EnterAppCodeDescription")}</Text>
|
||||
</Box>
|
||||
<Box
|
||||
displayProp="flex"
|
||||
flexDirection="column"
|
||||
className="app-code-wrapper"
|
||||
>
|
||||
<Box className="app-code-input">
|
||||
<FieldContainer
|
||||
labelVisible={false}
|
||||
hasError={error ? true : false}
|
||||
onKeyDown={onKeyPress}
|
||||
errorMessage={error}
|
||||
>
|
||||
<TextInput
|
||||
id="code"
|
||||
name="code"
|
||||
type="text"
|
||||
size="huge"
|
||||
scale
|
||||
isAutoFocussed
|
||||
tabIndex={1}
|
||||
placeholder={t("EnterCodePlaceholder")}
|
||||
isDisabled={isLoading}
|
||||
maxLength={6}
|
||||
onChange={(e) => {
|
||||
setCode(e.target.value);
|
||||
setError("");
|
||||
}}
|
||||
value={code}
|
||||
hasError={error ? true : false}
|
||||
onKeyDown={onKeyPress}
|
||||
/>
|
||||
</FieldContainer>
|
||||
</Box>
|
||||
<Box className="app-code-continue-btn">
|
||||
<Button
|
||||
scale
|
||||
primary
|
||||
size="medium"
|
||||
tabIndex={3}
|
||||
label={
|
||||
isLoading
|
||||
? t("Common:LoadingProcessing")
|
||||
: t("Common:ContinueButton")
|
||||
}
|
||||
isDisabled={!code.length || isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</FieldContainer>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className="app-code-continue-btn">
|
||||
<Button
|
||||
scale
|
||||
primary
|
||||
size="medium"
|
||||
tabIndex={3}
|
||||
label={
|
||||
isLoading
|
||||
? t("Common:LoadingProcessing")
|
||||
: t("Common:ContinueButton")
|
||||
}
|
||||
isDisabled={!code.length || isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</FormWrapper>
|
||||
</StyledForm>
|
||||
);
|
||||
});
|
||||
|
@ -62,3 +62,18 @@ body.desktop {
|
||||
padding: 30px;
|
||||
bottom: 15px;
|
||||
}
|
||||
|
||||
.with-background-pattern {
|
||||
background-image: url("/static/images/background.pattern.react.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: 100% 100%;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
@media (max-width: 428px) {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
36
packages/components/form-wrapper/index.js
Normal file
36
packages/components/form-wrapper/index.js
Normal file
@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { tablet, hugeMobile } from "@docspace/components/utils/device";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 5px 20px rgba(4, 15, 27, 0.07);
|
||||
border-radius: 12px;
|
||||
max-width: 320px;
|
||||
min-width: 320px;
|
||||
|
||||
@media ${tablet} {
|
||||
max-width: 416px;
|
||||
min-width: 416px;
|
||||
}
|
||||
|
||||
@media ${hugeMobile} {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
max-width: 343px;
|
||||
min-width: 343px;
|
||||
background: #ffffff;
|
||||
}
|
||||
`;
|
||||
|
||||
const FormWrapper = (props) => {
|
||||
const { children } = props;
|
||||
return <StyledWrapper>{children}</StyledWrapper>;
|
||||
};
|
||||
|
||||
export default FormWrapper;
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Kodu tapa bilmirsiniz? «Spam» qutunuzu yoxlayın.",
|
||||
"Or": "VƏ YA",
|
||||
"PasswordRecoveryTitle": "Parolun bərpa edilməsi",
|
||||
"RecoverAccess": "Girişi bərpa edin",
|
||||
"RecoverContactEmailPlaceholder": "Əlaqə üçün e-poçt ünvanı",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Probleminizi təsvir edin",
|
||||
"RecoverTextBody": "Cari hesabınızla giriş edə bilmirsinizsə və ya yeni istifadəçi kimi qeydiyyatdan keçmək istəyirsinizsə, o zaman portal administratoru ilə əlaqə saxlayın.",
|
||||
"RecoverTitle": "Girişin bərpası",
|
||||
"Register": "Qeydiyyatdan keç",
|
||||
"RegisterSendButton": "Sorğunu göndər",
|
||||
"RegisterTextBodyAfterDomainsList": "Qeydiyyatdan keçmək üçün, elektron poçt ünvanını daxil edin və Sorğunu göndər düyməsinə basın.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Не можете да намерите кода? Проверете папката «Спам».",
|
||||
"Or": "ИЛИ",
|
||||
"PasswordRecoveryTitle": "Възстановяване на парола",
|
||||
"RecoverAccess": "Поднови достъп",
|
||||
"RecoverContactEmailPlaceholder": "Имейл за контакт",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Опишете проблема си",
|
||||
"RecoverTextBody": "Ако не можете да се впишете със съществуващия си профил или искате да се регистрирате като нов потребител, свържете се с администратора на портала. ",
|
||||
"RecoverTitle": "Възстановяване на достъп",
|
||||
"Register": "Регистрирай се",
|
||||
"RegisterSendButton": "Изпрати заявка",
|
||||
"RegisterTextBodyAfterDomainsList": "За да се регистрирате, въведете имейла си и натиснете Изпрати заявка. Връзка за активация ще Ви бъде изпратена.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Nemůžete najít kód? Zkontrolujte složku «Spam».",
|
||||
"Or": "NEBO",
|
||||
"PasswordRecoveryTitle": "Obnovení hesla",
|
||||
"RecoverAccess": "Obnovit přístup",
|
||||
"RecoverContactEmailPlaceholder": "Kontaktní emailová adresa",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Popište svůj problém",
|
||||
"RecoverTextBody": "Pokud se nemůžete přihlásit pomocí stávajícího účtu nebo chcete být zaregistrováni jako nový uživatel, kontaktujte správce portálu. ",
|
||||
"RecoverTitle": "Obnovení přístupu",
|
||||
"Register": "Registrovat se",
|
||||
"RegisterSendButton": "Odeslat požadavek",
|
||||
"RegisterTextBodyAfterDomainsList": "Pro registraci zadejte svůj e-mail a klikněte na tlačítko Odeslat žádost. Bude vám zaslán aktivační odkaz. ",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Sie können den Code nicht finden? Prüfen Sie Ihren Spam-Ordner.",
|
||||
"Or": "oder",
|
||||
"PasswordRecoveryTitle": "Kennwort wiederherstellen",
|
||||
"RecoverAccess": "Zugriff wiederherstellen",
|
||||
"RecoverContactEmailPlaceholder": "E-Mail-Adresse",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Bitte Problem beschreiben",
|
||||
"RecoverTextBody": "Wenn Sie sich mit Ihren Anmeldeinformationen nicht einloggen können oder ein neues Profil erstellen möchten, wenden Sie sich an den Administrator des Portals.",
|
||||
"RecoverTitle": "Zugriffswiederherstellung",
|
||||
"Register": "Registrieren",
|
||||
"RegisterSendButton": "Anfrage senden",
|
||||
"RegisterTextBodyAfterDomainsList": "Für Registrierung geben Sie Ihre E-Mail-Adresse ein und klicken Sie auf Anfrage senden. Der Link zur Aktivierung Ihres Kontos wird an dieser Adresse gesendet.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Δεν μπορείτε να βρείτε τον κωδικό; Ελέγξτε τον φάκελο «Ανεπιθύμητα»",
|
||||
"Or": "ή",
|
||||
"PasswordRecoveryTitle": "Ανάκτηση κωδικού πρόσβασης",
|
||||
"RecoverAccess": "Ανάκτηση πρόσβασης",
|
||||
"RecoverContactEmailPlaceholder": "Email επικοινωνίας",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Περιγράψτε το πρόβλημά σας",
|
||||
"RecoverTextBody": "Εάν δεν μπορείτε να συνδεθείτε με τον υπάρχοντα λογαριασμό σας ή θέλετε να εγγραφείτε ως νέος χρήστης, επικοινωνήστε με τον διαχειριστή της πύλης.",
|
||||
"RecoverTitle": "Ανάκτηση πρόσβασης",
|
||||
"Register": "Εγγραφή",
|
||||
"RegisterSendButton": "Αποστολή αιτήματος",
|
||||
"RegisterTextBodyAfterDomainsList": "Για να εγγραφείτε, πληκτρολογήστε το email σας και κάντε κλικ στο κουμπί Αποστολή αιτήματος. Εκεί θα σας αποσταλεί ένας σύνδεσμος ενεργοποίησης.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Can't find the code? Check your «Spam» folder.",
|
||||
"Or": "OR",
|
||||
"PasswordRecoveryTitle": "Password recovery",
|
||||
"RecoverAccess": "Recover access",
|
||||
"RecoverContactEmailPlaceholder": "Contact email",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Describe your problem",
|
||||
"RecoverTextBody": "If you can't log in with your existing account or want to be registered as a new user, contact the portal administrator.",
|
||||
"RecoverTitle": "Access recovery",
|
||||
"Register": "Register",
|
||||
"RegisterSendButton": "Send request",
|
||||
"RegisterTextBodyAfterDomainsList": "To register, enter your email and click Send request. A message with a link to activate your account will be sent to the specified address.",
|
||||
@ -22,5 +27,7 @@
|
||||
"RegistrationEmailWatermark": "Email",
|
||||
"Remember": "Remember me",
|
||||
"RememberHelper": "The default session lifetime is 20 minutes. Check this option to set it to 1 year. To set your own value, go to Settings.",
|
||||
"ResendCode": "Resend code"
|
||||
"ResendCode": "Resend code",
|
||||
"SignInWithCode": "Sign in with secret code",
|
||||
"SignInWithPassword": "Sign in with a password"
|
||||
}
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "¿No encuentra el código? Compruebe su carpeta de correo no deseado.",
|
||||
"Or": "O",
|
||||
"PasswordRecoveryTitle": "Recuperación de contraseña",
|
||||
"RecoverAccess": "Recuperar acceso",
|
||||
"RecoverContactEmailPlaceholder": "E-mail de contacto",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Describa su problema",
|
||||
"RecoverTextBody": "Si no puede conectarse con su cuenta actual o quiere registrarse como nuevo usuario, póngase en contacto con el administrador del portal. ",
|
||||
"RecoverTitle": "Recuperación de acceso",
|
||||
"Register": "Registrarse",
|
||||
"RegisterSendButton": "Enviar solicitud",
|
||||
"RegisterTextBodyAfterDomainsList": "Para registrarse, introduzca su email y haga clic en Enviar solicitud. Se le enviará un enlace de activación.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Etkö löydä koodia? Tarkista «Roskaposti» -kansiosi.",
|
||||
"Or": "TAI",
|
||||
"PasswordRecoveryTitle": "Salasanan palautus",
|
||||
"RecoverAccess": "Palauta käyttöoikeus",
|
||||
"RecoverContactEmailPlaceholder": "Sähköposti yhteydenottoa varten",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Kuvaile ongelmasi",
|
||||
"RecoverTextBody": "Jos et voi kirjautua sisään nykyisellä tililläsi tai haluat rekisteröityä uutena käyttäjänä, ota yhteyttä portaalin järjestelmänvalvojaan. ",
|
||||
"RecoverTitle": "Käytä palautusta",
|
||||
"Register": "Rekisteröidy",
|
||||
"RegisterSendButton": "Lähetä pyyntö",
|
||||
"RegisterTextBodyAfterDomainsList": "Rekisteröidy kirjoittamalla sähköpostiosoitteesi ja napsauttamalla Lähetä pyyntö. Sinulle lähetetään aktivointilinkki. ",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Vous n'avez pas trouvé le code ? Veuillez vérifier votre dossier 'Spam'.",
|
||||
"Or": "Ou",
|
||||
"PasswordRecoveryTitle": "Récupération du mot de passe",
|
||||
"RecoverAccess": "Récupérer l'accès",
|
||||
"RecoverContactEmailPlaceholder": "E-mail de contact",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Décrivez votre problème",
|
||||
"RecoverTextBody": "Si vous ne pouvez pas vous connecter avec votre compte existant ou que vous souhaitez en créer un nouveau, merci de contacter l'administrateur du portail.",
|
||||
"RecoverTitle": "Récupération d'accès",
|
||||
"Register": "Inscription",
|
||||
"RegisterSendButton": "Envoyer une demande",
|
||||
"RegisterTextBodyAfterDomainsList": "Pour vous inscrire, merci de saisir votre adresse e-mail et de cliquer sur Envoyer une demande. Un message avec un lien pour activer votre compte sera envoyé à cette adresse.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Չե՞ք կարողանում գտնել կոդը:Ստուգեք Ձեր «Սպամ» պանակը.",
|
||||
"Or": "Կամ",
|
||||
"PasswordRecoveryTitle": "Գաղտնաբառի վերականգնում",
|
||||
"RecoverAccess": "Վերականգնել մատչումը",
|
||||
"RecoverContactEmailPlaceholder": "Կոնտակտային էլ. փոստ",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Նկարագրեք Ձեր խնդիրը",
|
||||
"RecoverTextBody": "Եթե Դուք չեք կարող մուտք գործել Ձեր գոյություն ունեցող հաշիվ կամ ցանկանում եք գրանցվել որպես նոր օգտվող, կապվեք կայքէջի ադմինիստրատորի հետ:",
|
||||
"RecoverTitle": "Մատչման վերականգնում",
|
||||
"Register": "Գրանցվել",
|
||||
"RegisterSendButton": "Հայցում ուղարկել",
|
||||
"RegisterTextBodyAfterDomainsList": "Գրանցվելու համար մուտքագրեք Ձեր էլ.փոստը և սեղմեք Ուղարկել հայցումը: Ձեզ կուղարկվի ակտիվացման հղում:",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Non riesci a trovare il codice? Controlla la tua cartella «Spam».",
|
||||
"Or": "o",
|
||||
"PasswordRecoveryTitle": "Recupero della password",
|
||||
"RecoverAccess": "Rimuovi accesso",
|
||||
"RecoverContactEmailPlaceholder": "Email di contatto",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Descrivi il tuo problema",
|
||||
"RecoverTextBody": "Se non riesci ad accedere con il tuo account esistente o desideri essere registrato come un nuovo utente, contatta l'amministratore del portale.",
|
||||
"RecoverTitle": "Ripristino dell'accesso",
|
||||
"Register": "Registra",
|
||||
"RegisterSendButton": "Invia richiesta",
|
||||
"RegisterTextBodyAfterDomainsList": "Per registrarti, inserisci la tua email e clicca su Inviare la richiesta. Un messaggio con un link per attivare il tuo account verrà inviato all'indirizzo specificato.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "コードが見つかりませんか?「迷惑メール」のフォルダをご確認ください。",
|
||||
"Or": "または",
|
||||
"PasswordRecoveryTitle": "パスワード復旧",
|
||||
"RecoverAccess": "アクセス回復",
|
||||
"RecoverContactEmailPlaceholder": "連絡先メール",
|
||||
"RecoverDescribeYourProblemPlaceholder": "ご質問を説明ください",
|
||||
"RecoverTextBody": "既存のアカウントでログインできない場合や、新規ユーザーとして登録したい場合は、ポータル管理者にお問い合わせください。 ",
|
||||
"RecoverTitle": "アクセス回復機能",
|
||||
"Register": "登録",
|
||||
"RegisterSendButton": "リクエスト送信",
|
||||
"RegisterTextBodyAfterDomainsList": "登録するには、メールアドレスを入力して「リクエスト送信」をクリックします。指定されたアドレスに、アカウント有効化のためのリンクが添付されたメッセージが送信されます。",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "코드가 안 보이세요? «스팸» 폴더를 확인해 보세요.",
|
||||
"Or": "또는",
|
||||
"PasswordRecoveryTitle": "비밀번호 복원",
|
||||
"RecoverAccess": "액세스 복구",
|
||||
"RecoverContactEmailPlaceholder": "연락처 이메일",
|
||||
"RecoverDescribeYourProblemPlaceholder": "겪고 계신 문제를 설명해주세요",
|
||||
"RecoverTextBody": "기존 계정으로 로그인하실 수 없거나 새로운 사용자로 등록하시길 원하시는 경우, 포털 관리자에게 문의하세요. ",
|
||||
"RecoverTitle": "액세스 복원",
|
||||
"Register": "가입",
|
||||
"RegisterSendButton": "요청 전송",
|
||||
"RegisterTextBodyAfterDomainsList": "가입하시려면, 이메일을 입력한 뒤 가입 요청을 클릭하세요. 활성화 링크가 전송됩니다.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "ບໍ່ສາມາດຊອກຫາລະຫັດໄດ້ບໍ? ກວດເບິ່ງໂຟນເດີ «Spam» ຂອງທ່ານ.",
|
||||
"Or": "ຫຼື",
|
||||
"PasswordRecoveryTitle": "ກູ້ຄືນລະຫັດຜ່ານ",
|
||||
"RecoverAccess": "ກູ້ຄືນການເຂົ້າເຖິງ",
|
||||
"RecoverContactEmailPlaceholder": "ອີເມວຕິດຕໍ່",
|
||||
"RecoverDescribeYourProblemPlaceholder": "ອະທິບາຍບັນຫາຂອງທ່ານ",
|
||||
"RecoverTextBody": "ຖ້າທ່ານບໍ່ສາມາດເຂົ້າສູ່ລະບົບໄດ້ຫຼືຕ້ອງການລົງທະບຽນໃຫມ່, ໃຫ້ທ່ານຕິດຕໍ່ຜູ້ດູແລລະບົບ portal ",
|
||||
"RecoverTitle": "ການເຂົ້າເຖິງການກູ້ຄືນ",
|
||||
"Register": "ລົງທະບຽນ",
|
||||
"RegisterSendButton": "ສົ່ງຄຳຂໍ",
|
||||
"RegisterTextBodyAfterDomainsList": "ການລົງທະບຽນ, ໃຫ້ທ່ານປ້ອນອີເມວຂອງທ່ານແລ້ວກົດສົ່ງຄຳຮ້ອງຂໍ. ລີງຂໍເປີດໃຊ້ງານຈະສົ່ງຫາທ່ານຜ່ານທາງອີເມວ",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Vai nevarat atrast kodu? Pārbaudiet mapi Surogātpasts.",
|
||||
"Or": "VAI",
|
||||
"PasswordRecoveryTitle": "Paroles atgūšana",
|
||||
"RecoverAccess": "Atgūt piekļuvi",
|
||||
"RecoverContactEmailPlaceholder": "E-pasts saziņai",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Aprakstiet savu problēmu",
|
||||
"RecoverTextBody": "Ja nevarat pieteikties ar savu esošo kontu vai vēlaties reģistrēties kā jauns lietotājs, sazinieties ar portāla administratoru. ",
|
||||
"RecoverTitle": "Piekļuves atkopšana",
|
||||
"Register": "Reģistrēties",
|
||||
"RegisterSendButton": "Sūtīt pieprasījumu",
|
||||
"RegisterTextBodyAfterDomainsList": "Lai reģistrētos, ievadiet savu e-pastu un noklikšķiniet uz Sūtīt pieprasījumu. Jums tiks nosūtīta aktivizācijas saite.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Kunt u de code niet vinden? Bekijk uw \"Spam\" map.",
|
||||
"Or": "OR",
|
||||
"PasswordRecoveryTitle": "Wachtwoord herstellen",
|
||||
"RecoverAccess": "Toegang herstellen",
|
||||
"RecoverContactEmailPlaceholder": "Contact e-mail",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Beschrijf uw probleem",
|
||||
"RecoverTextBody": "Als u zich niet kunt aanmelden met uw bestaande account of als nieuwe gebruiker wilt worden geregistreerd, neem dan contact op met de portaalbeheerder.",
|
||||
"RecoverTitle": "Toegang herstel",
|
||||
"Register": "Registreren",
|
||||
"RegisterSendButton": "Stuur verzoek",
|
||||
"RegisterTextBodyAfterDomainsList": "Om te registreren, voert u uw e-mail in en klikt u op Stuur verzoek. U ontvangt een activatielink.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Nie możesz znaleźć kodu? Sprawdź swój folder ze spamem.",
|
||||
"Or": "LUB",
|
||||
"PasswordRecoveryTitle": "Odzyskaj hasło",
|
||||
"RecoverAccess": "Odzyskaj dostęp",
|
||||
"RecoverContactEmailPlaceholder": "E-mail kontaktowy",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Opisz swój problem",
|
||||
"RecoverTextBody": "Jeśli nie możesz się zalogować za pomocą istniejącego konta, lub chcesz zarejestrować się jako nowy użytkownik, skontaktuj się z administratorem portalu. ",
|
||||
"RecoverTitle": "Odzyskiwanie dostępu",
|
||||
"Register": "Zarejestruj",
|
||||
"RegisterSendButton": "Wyślij wniosek",
|
||||
"RegisterTextBodyAfterDomainsList": "Aby zarejestrować się, podaj swój adres e-mail i kliknij Wyślij wniosek. Link aktywacyjny zostanie wysłany na wskazany przez Ciebie adres. ",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Você não consegue encontrar o código? Verifique sua pasta «Spam».",
|
||||
"Or": "OU",
|
||||
"PasswordRecoveryTitle": "Recuperação de senha",
|
||||
"RecoverAccess": "Recuperar o acesso",
|
||||
"RecoverContactEmailPlaceholder": "E-mail de contato",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Descreva seu problema",
|
||||
"RecoverTextBody": "Se você não conseguir entrar com sua conta existente ou quiser ser registrado como um novo usuário, entre em contato com o administrador do portal. ",
|
||||
"RecoverTitle": "Recuperação de acesso",
|
||||
"Register": "Registro",
|
||||
"RegisterSendButton": "Enviar solicitação",
|
||||
"RegisterTextBodyAfterDomainsList": "Para registrar-se, digite seu e-mail e clique em Enviar solicitação. Um link de ativação será enviado a você. ",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Não encontra o código? Verifique a pasta de «Spam».",
|
||||
"Or": "OU",
|
||||
"PasswordRecoveryTitle": "Recuperação de palavra-passe",
|
||||
"RecoverAccess": "Recuperar o acesso",
|
||||
"RecoverContactEmailPlaceholder": "Email de contacto",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Descreva o seu problema",
|
||||
"RecoverTextBody": "Se não consegue iniciar sessão com a sua conta ou se quiser registar-se como um novo utilizador, contacte o administrador do portal.",
|
||||
"RecoverTitle": "Recuperação de acesso",
|
||||
"Register": "Registar",
|
||||
"RegisterSendButton": "Enviar pedido",
|
||||
"RegisterTextBodyAfterDomainsList": "Para registar-se, introduza o seu e-mail e clique em Enviar pedido. Ser-lhe-á enviado uma ligação de ativação.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Nu puteți găsi codul? Ar trebui să vă verificați folderul Spam.",
|
||||
"Or": "SAU",
|
||||
"PasswordRecoveryTitle": "Recuperarea parolei",
|
||||
"RecoverAccess": "Recuperarea accesului la cont",
|
||||
"RecoverContactEmailPlaceholder": "E-mail de contact",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Descrieți problema",
|
||||
"RecoverTextBody": "Dacă nu puteți să vă conectați la contul dvs curent sau doriți să vă înregistrați din nou, contactați administratorul portalului. ",
|
||||
"RecoverTitle": "Restabilirea accesului",
|
||||
"Register": "Înregistrare",
|
||||
"RegisterSendButton": "Trimite solicitarea",
|
||||
"RegisterTextBodyAfterDomainsList": "Pentru a vă înregistra, introduceți adresa de e-mail și dați clic pe Trimite solicitarea. Veți primi o scrisoare de activare.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Не можете найти код? Проверьте папку «Спам».",
|
||||
"Or": "ИЛИ",
|
||||
"PasswordRecoveryTitle": "Восстановление пароля",
|
||||
"RecoverAccess": "Доступ к порталу",
|
||||
"RecoverContactEmailPlaceholder": "Адрес email, по которому можно связаться с Вами",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Пожалуйста, опишите вашу проблему",
|
||||
"RecoverTextBody": "Если Вы уже зарегистрированы и у Вас есть проблемы с доступом к этому порталу, или Вы хотите зарегистрироваться как новый пользователь портала, пожалуйста, обратитесь к администратору портала, используя форму, расположенную ниже.",
|
||||
"RecoverTitle": "Доступ к порталу",
|
||||
"Register": "Регистрация",
|
||||
"RegisterSendButton": "Отправить запрос",
|
||||
"RegisterTextBodyAfterDomainsList": "Чтобы зарегистрироваться, введите свой email и нажмите кнопку Отправить запрос. Сообщение со ссылкой для активации вашей учётной записи будет отправлено на указанный адрес.",
|
||||
|
@ -13,6 +13,11 @@
|
||||
"NotFoundCode": "Nemôžete nájsť kód? Skontrolujte priečinok «Spam».",
|
||||
"Or": "Alebo",
|
||||
"PasswordRecoveryTitle": "Obnova hesla",
|
||||
"RecoverAccess": "Obnoviť prístup",
|
||||
"RecoverContactEmailPlaceholder": "Kontaktný email",
|
||||
"RecoverDescribeYourProblemPlaceholder": "Opíšte svoj problém",
|
||||
"RecoverTextBody": "Ak sa nemôžete prihlásiť so svojím existujúcim kontom, alebo sa chcete zaregistrovať ako nový užívateľ, kontaktujte admina portálu.",
|
||||
"RecoverTitle": "Obnova prístupu",
|
||||
"Register": "Registrovať sa",
|
||||
"RegisterSendButton": "Poslať žiadosť",
|
||||
"RegisterTextBodyAfterDomainsList": "Ak sa chcete zaregistrovať, zadajte svoj e-mail a kliknite na tlačidlo Poslať žiadosť. Bude vám zaslané aktivačné prepojenie. ",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user