Merge branch 'feature/public-room' of github.com:ONLYOFFICE/DocSpace into feature/public-room

This commit is contained in:
Vladimir Khvan 2023-07-14 20:17:01 +05:00
commit bda0d671e9
234 changed files with 5307 additions and 3114 deletions

View File

@ -305,15 +305,6 @@ COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Web.Ap
CMD ["ASC.Web.Api.dll", "ASC.Web.Api"]
## ASC.Webhooks.Service ##
# FROM dotnetrun AS webhooks-service
# WORKDIR ${BUILD_PATH}/services/ASC.Webhooks.Service/
# COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py
# COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Webhooks.Service/service/ .
# CMD ["ASC.Webhooks.Service.dll", "ASC.Webhooks.Service"]
## ASC.Web.Studio ##
FROM dotnetrun AS studio
WORKDIR ${BUILD_PATH}/studio/ASC.Web.Studio/

View File

@ -113,13 +113,6 @@ services:
target: ssoauth
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-ssoauth:${DOCKER_TAG}"
# onlyoffice-webhooks-service:
# build:
# context: ./
# dockerfile: "${DOCKERFILE}"
# target: webhooks-service
# image: "${REPO}/${DOCKER_IMAGE_PREFIX}-webhooks-service:${DOCKER_TAG}"
onlyoffice-proxy:
build:
context: ./

View File

@ -139,11 +139,6 @@ services:
- ${SERVICE_PORT}
- "9834"
# onlyoffice-webhooks-service:
# <<: *x-service-base
# image: "${REPO}/${DOCKER_IMAGE_PREFIX}-webhooks-service:${DOCKER_TAG}"
# container_name: ${WEBHOOKS_SERVICE_HOST}
onlyoffice-proxy:
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-proxy:${DOCKER_TAG}"
container_name: ${PROXY_HOST}
@ -159,7 +154,6 @@ services:
- onlyoffice-backup
# - onlyoffice-clear-events
# - onlyoffice-migration
# - webhooks-service
- onlyoffice-files
- onlyoffice-files-services
- onlyoffice-people-server
@ -177,7 +171,6 @@ services:
- SERVICE_FILES_SERVICES=${SERVICE_FILES_SERVICES}
# - SERVICE_CLEAR_EVENTS=${SERVICE_CLEAR_EVENTS}
# - SERVICE_MIGRATION=${SERVICE_MIGRATION}
# - SERVICE_WEBHOOKS_SERVICE=${SERVICE_WEBHOOKS_SERVICE}
- SERVICE_NOTIFY=${SERVICE_NOTIFY}
- SERVICE_PEOPLE_SERVER=${SERVICE_PEOPLE_SERVER}
- SERVICE_SOCKET=${SERVICE_SOCKET}

View File

@ -47,6 +47,7 @@ public class EmployeeFullDto : EmployeeDto
public string AvatarMedium { get; set; }
public string Avatar { get; set; }
public bool IsAdmin { get; set; }
public bool IsRoomAdmin { get; set; }
public bool IsLDAP { get; set; }
public List<string> ListAdminModules { get; set; }
public bool IsOwner { get; set; }
@ -182,6 +183,8 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
public async Task<EmployeeFullDto> GetFullAsync(UserInfo userInfo)
{
var currentType = await _userManager.GetUserTypeAsync(userInfo.Id);
var result = new EmployeeFullDto
{
UserName = userInfo.UserName,
@ -194,9 +197,10 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
WorkFrom = _apiDateTimeHelper.Get(userInfo.WorkFromDate),
Email = userInfo.Email,
IsVisitor = await _userManager.IsUserAsync(userInfo),
IsAdmin = await _userManager.IsDocSpaceAdminAsync(userInfo),
IsAdmin = currentType is EmployeeType.DocSpaceAdmin,
IsRoomAdmin = currentType is EmployeeType.RoomAdmin,
IsOwner = userInfo.IsOwner(_context.Tenant),
IsCollaborator = await _userManager.IsCollaboratorAsync(userInfo),
IsCollaborator = currentType is EmployeeType.Collaborator,
IsLDAP = userInfo.IsLDAP(),
IsSSO = userInfo.IsSSO()
};

View File

@ -29,7 +29,9 @@ using HttpContext = Microsoft.AspNetCore.Http.HttpContext;
namespace System.Web;
public static class HttpRequestExtensions
{
{
public static readonly string RequestTokenHeader = "Request-Token";
public static Uri Url(this HttpRequest request)
{
return request != null ? new Uri(request.GetDisplayUrl()) : null;

View File

@ -187,6 +187,7 @@ public class CachedUserService : IUserService, ICachedService
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
List<Tuple<List<List<Guid>>, List<Guid>>> combinedGroups,
EmployeeActivationStatus? activationStatus,
AccountLoginType? accountLoginType,
string text,
@ -197,7 +198,7 @@ public class CachedUserService : IUserService, ICachedService
out int total,
out int count)
{
return Service.GetUsers(tenant, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, activationStatus, accountLoginType, text, sortBy, sortOrderAsc, limit, offset, out total, out count);
return Service.GetUsers(tenant, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text, sortBy, sortOrderAsc, limit, offset, out total, out count);
}
public async Task<UserInfo> GetUserAsync(int tenant, Guid id)

View File

@ -186,6 +186,7 @@ public class UserManager
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
List<Tuple<List<List<Guid>>, List<Guid>>> combinedGroups,
EmployeeActivationStatus? activationStatus,
AccountLoginType? accountLoginType,
string text,
@ -196,7 +197,7 @@ public class UserManager
out int total,
out int count)
{
return _userService.GetUsers(Tenant.Id, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, activationStatus, accountLoginType, text, sortBy, sortOrderAsc, limit, offset, out total, out count);
return _userService.GetUsers(Tenant.Id, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text, sortBy, sortOrderAsc, limit, offset, out total, out count);
}
public async Task<string[]> GetUserNamesAsync(EmployeeStatus status)

View File

@ -34,6 +34,7 @@ public interface IUserService
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
List<Tuple<List<List<Guid>>, List<Guid>>> combinedGroups,
EmployeeActivationStatus? activationStatus,
AccountLoginType? accountLoginType,
string text,

View File

@ -232,16 +232,31 @@ public class EFUserService : IUserService
.ToListAsync();
}
public IQueryable<UserInfo> GetUsers(int tenant, bool isDocSpaceAdmin, EmployeeStatus? employeeStatus, List<List<Guid>> includeGroups, List<Guid> excludeGroups, EmployeeActivationStatus? activationStatus, AccountLoginType? accountLoginType, string text, string sortBy, bool sortOrderAsc, long limit, long offset, out int total, out int count)
public IQueryable<UserInfo> GetUsers(
int tenant,
bool isDocSpaceAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
List<Tuple<List<List<Guid>>, List<Guid>>> combinedGroups,
EmployeeActivationStatus? activationStatus,
AccountLoginType? accountLoginType,
string text,
string sortBy,
bool sortOrderAsc,
long limit,
long offset,
out int total,
out int count)
{
using var userDbContext = _dbContextFactory.CreateDbContext();
var userDbContext = _dbContextFactory.CreateDbContext();
var totalQuery = GetUserQuery(userDbContext, tenant);
totalQuery = GetUserQueryForFilter(userDbContext, totalQuery, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, activationStatus, accountLoginType, text);
totalQuery = GetUserQueryForFilter(userDbContext, totalQuery, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text);
total = totalQuery.Count();
var q = GetUserQuery(userDbContext, tenant);
q = GetUserQueryForFilter(userDbContext, q, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, activationStatus, accountLoginType, text);
q = GetUserQueryForFilter(userDbContext, q, isDocSpaceAdmin, employeeStatus, includeGroups, excludeGroups, combinedGroups, activationStatus, accountLoginType, text);
var orderedQuery = q.OrderBy(r => r.ActivationStatus == EmployeeActivationStatus.Pending);
q = orderedQuery;
@ -596,26 +611,61 @@ public class EFUserService : IUserService
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
List<Tuple<List<List<Guid>>, List<Guid>>> combinedGroups,
EmployeeActivationStatus? activationStatus,
AccountLoginType? accountLoginType,
string text)
{
q = q.Where(r => !r.Removed);
if (includeGroups != null && includeGroups.Count > 0)
if (includeGroups != null && includeGroups.Count > 0 || excludeGroups != null && excludeGroups.Count > 0)
{
foreach (var ig in includeGroups)
if (includeGroups != null && includeGroups.Count > 0)
{
q = q.Where(r => userDbContext.UserGroups.Any(a => !a.Removed && a.TenantId == r.TenantId && a.Userid == r.Id && ig.Any(r => r == a.UserGroupId)));
foreach (var ig in includeGroups)
{
q = q.Where(r => userDbContext.UserGroups.Any(a => !a.Removed && a.TenantId == r.TenantId && a.Userid == r.Id && ig.Any(r => r == a.UserGroupId)));
}
}
if (excludeGroups != null && excludeGroups.Count > 0)
{
foreach (var eg in excludeGroups)
{
q = q.Where(r => !userDbContext.UserGroups.Any(a => !a.Removed && a.TenantId == r.TenantId && a.Userid == r.Id && a.UserGroupId == eg));
}
}
}
if (excludeGroups != null && excludeGroups.Count > 0)
else if (combinedGroups != null && combinedGroups.Any())
{
foreach (var eg in excludeGroups)
Expression<Func<User, bool>> a = r => false;
foreach (var cg in combinedGroups)
{
q = q.Where(r => !userDbContext.UserGroups.Any(a => !a.Removed && a.TenantId == r.TenantId && a.Userid == r.Id && a.UserGroupId == eg));
Expression<Func<User, bool>> b = r => true;
var cgIncludeGroups = cg.Item1;
var cgExcludeGroups = cg.Item2;
if (cgIncludeGroups != null && cgIncludeGroups.Count > 0)
{
foreach (var ig in cgIncludeGroups)
{
b = b.And(r => userDbContext.UserGroups.Any(a => !a.Removed && a.TenantId == r.TenantId && a.Userid == r.Id && ig.Any(r => r == a.UserGroupId)));
}
}
if (cgExcludeGroups != null && cgExcludeGroups.Count > 0)
{
foreach (var eg in cgExcludeGroups)
{
b = b.And(r => !userDbContext.UserGroups.Any(a => !a.Removed && a.TenantId == r.TenantId && a.Userid == r.Id && a.UserGroupId == eg));
}
}
a = a.Or(b);
}
q = q.Where(a);
}
if (!isDocSpaceAdmin && employeeStatus == null)

View File

@ -0,0 +1,66 @@
// (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.Core.Common.EF;
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b)
{
var p = a.Parameters[0];
var visitor = new SubstExpressionVisitor();
visitor.Subst[b.Parameters[0]] = p;
Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b)
{
var p = a.Parameters[0];
var visitor = new SubstExpressionVisitor();
visitor.Subst[b.Parameters[0]] = p;
Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
}
internal class SubstExpressionVisitor : ExpressionVisitor
{
internal Dictionary<Expression, Expression> Subst = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node)
{
if (Subst.TryGetValue(node, out var newValue))
{
return newValue;
}
return node;
}
}

View File

@ -203,9 +203,9 @@ public enum MessageAction
RoomUpdateAccessForUser = 5075,
RoomRemoveUser = 5084,
RoomCreateUser = 5085,// last
RoomLinkUpdated = 5082,
RoomLinkCreated = 5086,
RoomLinkDeleted = 5087,
RoomInvitationLinkUpdated = 5082,
RoomInvitationLinkCreated = 5086,
RoomInvitationLinkDeleted = 5087,
TagCreated = 5076,
TagsDeleted = 5077,
@ -214,6 +214,10 @@ public enum MessageAction
RoomLogoCreated = 5080,
RoomLogoDeleted = 5081,
ExternalLinkCreated = 5088,
ExternalLinkUpdated = 5089,
ExternalLinkDeleted = 5090,
#endregion

View File

@ -33,4 +33,5 @@ public static class Constants
public const string QueryAuth = "auth";
public const string QueryExpire = "expire";
public const string QueryHeader = "headers";
public const string SecureKeyHeader = "secure-key";
}

View File

@ -111,6 +111,11 @@ public class S3Storage : BaseStorage
foreach (var h in headers)
{
if (h.StartsWith(Constants.SecureKeyHeader))
{
continue;
}
if (h.StartsWith("Content-Disposition"))
{
headersOverrides.ContentDisposition = (h.Substring("Content-Disposition".Length + 1));

View File

@ -41,4 +41,39 @@ public static class SecureHelper
return false;
}
}
public static string GenerateSecureKeyHeader(string path, EmailValidationKeyProvider keyProvider)
{
var ticks = DateTime.UtcNow.Ticks;
var data = path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar) + '.' + ticks;
var key = keyProvider.GetEmailKey(data);
return Constants.SecureKeyHeader + ':' + ticks + '-' + key;
}
public static async Task<bool> CheckSecureKeyHeader(string queryHeaders, string path, EmailValidationKeyProvider keyProvider)
{
if (string.IsNullOrEmpty(queryHeaders))
{
return false;
}
var headers = queryHeaders.Length > 0 ? queryHeaders.Split('&').Select(HttpUtility.UrlDecode) : Array.Empty<string>();
var headerKey = headers.FirstOrDefault(h => h.StartsWith(Constants.SecureKeyHeader))?.
Replace(Constants.SecureKeyHeader + ':', string.Empty);
if (string.IsNullOrEmpty(headerKey))
{
return false;
}
var separatorPosition = headerKey.IndexOf('-');
var ticks = headerKey[..separatorPosition];
var key = headerKey[(separatorPosition + 1)..];
var result = await keyProvider.ValidateEmailKeyAsync(path + '.' + ticks, key);
return result == EmailValidationKeyProvider.ValidationResult.Ok;
}
}

View File

@ -51,12 +51,12 @@ public class StorageHandler
public async ValueTask InvokeAsync(HttpContext context, TenantManager tenantManager, SecurityContext securityContext, StorageFactory storageFactory, EmailValidationKeyProvider emailValidationKeyProvider)
{
var storage = await storageFactory.GetStorageAsync((await tenantManager.GetCurrentTenantAsync()).Id, _module);
var path = CrossPlatform.PathCombine(_path, GetRouteValue("pathInfo", context).Replace('/', Path.DirectorySeparatorChar));
var path = CrossPlatform.PathCombine(_path, GetRouteValue("pathInfo", context).Replace('/', Path.DirectorySeparatorChar));
var header = context.Request.Query[Constants.QueryHeader].FirstOrDefault() ?? "";
var auth = context.Request.Query[Constants.QueryAuth].FirstOrDefault() ?? "";
var storageExpire = storage.GetExpire(_domain);
if (_checkAuth && !securityContext.IsAuthenticated && String.IsNullOrEmpty(auth))
if (_checkAuth && !securityContext.IsAuthenticated && !await SecureHelper.CheckSecureKeyHeader(header, path, emailValidationKeyProvider))
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
@ -74,31 +74,31 @@ public class StorageHandler
if (validateResult != EmailValidationKeyProvider.ValidationResult.Ok)
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
return;
}
}
if (!await storage.IsFileAsync(_domain, path))
{
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
return;
return;
}
var headers = header.Length > 0 ? header.Split('&').Select(HttpUtility.UrlDecode) : Array.Empty<string>();
var headers = header.Length > 0 ? header.Split('&').Select(HttpUtility.UrlDecode) : Array.Empty<string>();
const int bigSize = 5 * 1024 * 1024;
var fileSize = await storage.GetFileSizeAsync(_domain, path);
if (storage.IsSupportInternalUri && bigSize < fileSize)
{
var uri = await storage.GetInternalUriAsync(_domain, path, TimeSpan.FromMinutes(15), headers);
var uri = await storage.GetInternalUriAsync(_domain, path, TimeSpan.FromMinutes(15), headers);
//TODO
//context.Response.Cache.SetAllowResponseInBrowserHistory(false);
//context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.Redirect(uri.ToString());
return;
return;
}
//if (!String.IsNullOrEmpty(context.Request.Query["_"]))
@ -119,7 +119,7 @@ public class StorageHandler
string encoding = null;
if (storage is DiscDataStore && await storage.IsFileAsync(_domain, path + ".gz"))
if (storage is DiscDataStore && await storage.IsFileAsync(_domain, path + ".gz"))
{
path += ".gz";
encoding = "gzip";
@ -162,7 +162,7 @@ public class StorageHandler
{
var responseBufferingFeature = context.Features.Get<IHttpResponseBodyFeature>();
responseBufferingFeature?.DisableBuffering();
await stream.CopyToAsync(context.Response.Body);
}
@ -233,4 +233,4 @@ public static class StorageHandlerExtensions
return builder;
}
}
}

View File

@ -28,7 +28,7 @@
return;
}
if (!session.user) {
if (!session.user && !session.anonymous) {
logger.error("invalid session: unknown user");
return;
}
@ -45,14 +45,18 @@
return `${tenantId}-${roomPart}`;
};
logger.info(
`connect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}'`
);
const connectMessage = !session.anonymous ?
`connect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}'` :
`connect anonymous user by share key on tenant='${tenantId}' socketId='${socket.id}'`;
logger.info(connectMessage);
socket.on("disconnect", (reason) => {
logger.info(
`disconnect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}' due to ${reason}`
);
const disconnectMessage = !session.anonymous ?
`disconnect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}' due to ${reason}` :
`disconnect anonymous user by share key on tenant='${tenantId}' socketId='${socket.id}' due to ${reason}`;
logger.info(disconnectMessage)
});
socket.on("subscribe", ({ roomParts, individual }) => {
@ -80,7 +84,7 @@
changeFunc(roomParts);
if (individual) {
if (individual && !session.anonymous) {
if (Array.isArray(roomParts)) {
changeFunc(roomParts.map((p) => `${p}-${userId}`));
} else {

View File

@ -9,9 +9,10 @@ module.exports = (socket, next) => {
const cookie = req?.cookies?.authorization || req?.cookies?.asc_auth_key;
const token = req?.headers?.authorization;
const share = socket.handshake.query?.share;
if (!cookie && !token) {
const err = new Error("Authentication error (not token or cookie)");
if (!cookie && !token && !share) {
const err = new Error("Authentication error (not token or cookie or share key)");
logger.error(err);
socket.disconnect("unauthorized");
next(err);
@ -31,15 +32,13 @@ module.exports = (socket, next) => {
return;
}
let headers;
if (cookie)
headers = {
Authorization: cookie,
};
const basePath = portalManager(req)?.replace(/\/$/g, "");
let headers = {};
logger.info(`API basePath='${basePath}' Authorization='${cookie}'`);
if (cookie) {
headers.Authorization = cookie;
logger.info(`API basePath='${basePath}' Authorization='${cookie}'`);
const getUser = () => {
return request({
@ -59,17 +58,53 @@ module.exports = (socket, next) => {
});
};
return Promise.all([getUser(), getPortal()])
.then(([user, portal]) => {
logger.info("Get account info", { user, portal });
session.user = user;
session.portal = portal;
session.save();
next();
})
.catch((err) => {
logger.error("Error of getting account info", err);
return Promise.all([getUser(), getPortal()])
.then(([user, portal]) => {
logger.info("Get account info", { user, portal });
session.user = user;
session.portal = portal;
session.save();
next();
})
.catch((err) => {
logger.error("Error of getting account info", err);
socket.disconnect("Unauthorized");
next(err);
});
}
if (share) {
if (req?.cookies) {
const pairs = Object.entries(req.cookies).map(([key, value]) => `${key}=${value}`);
if (pairs.length > 0) {
let cookie = pairs.join(';');
cookie += ';';
headers.Cookie = cookie;
}
}
return request({
method: "get",
url: `/files/share/${share}`,
headers,
basePath,
}).then(validation => {
if (validation.status !== 0) {
const err = new Error("Invalid share key");
logger.error("Share key validation failure:", err);
next(err);
} else {
logger.info(`Share key validation successful: key=${share}`)
session.anonymous = true;
session.portal = { tenantId: validation.tenantId }
session.save();
next();
}
}).catch(err => {
logger.error(err);
socket.disconnect("Unauthorized");
next(err);
});
};
})
}
};

View File

@ -66,7 +66,8 @@ const options = {
const token =
req?.headers?.authorization ||
req?.cookies?.authorization ||
req?.cookies?.asc_auth_key;
req?.cookies?.asc_auth_key ||
req?._query?.share;
if (!token) {
winston.info(`not allowed request: empty token`);

View File

@ -90,18 +90,28 @@ public class DbWorker
return toAdd;
}
public IAsyncEnumerable<WebhooksConfigWithStatus> GetTenantWebhooksWithStatus()
public async IAsyncEnumerable<WebhooksConfigWithStatus> GetTenantWebhooksWithStatus()
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
return Queries.WebhooksConfigWithStatusAsync(webhooksDbContext, Tenant);
var q = Queries.WebhooksConfigWithStatusAsync(webhooksDbContext, Tenant);
await foreach (var webhook in q)
{
yield return webhook;
}
}
public IAsyncEnumerable<WebhooksConfig> GetWebhookConfigs()
public async IAsyncEnumerable<WebhooksConfig> GetWebhookConfigs()
{
var webhooksDbContext = _dbContextFactory.CreateDbContext();
return Queries.WebhooksConfigsAsync(webhooksDbContext, Tenant);
var q = Queries.WebhooksConfigsAsync(webhooksDbContext, Tenant);
await foreach (var webhook in q)
{
yield return webhook;
}
}
public async Task<WebhooksConfig> UpdateWebhookConfig(int id, string name, string uri, string key, bool? enabled, bool? ssl)

View File

@ -20,6 +20,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ASC.Core.Common\ASC.Core.Common.csproj" />
<ProjectReference Include="..\..\ASC.MessagingSystem\ASC.MessagingSystem.csproj" />
</ItemGroup>

View File

@ -154,9 +154,9 @@ internal class RoomsActionMapper : IModuleActionMapper
MessageAction.RoomCreateUser,
MessageAction.RoomUpdateAccessForUser,
MessageAction.RoomRemoveUser,
MessageAction.RoomLinkCreated,
MessageAction.RoomLinkUpdated,
MessageAction.RoomLinkDeleted
MessageAction.RoomInvitationLinkCreated,
MessageAction.RoomInvitationLinkUpdated,
MessageAction.RoomInvitationLinkDeleted
}
},
{

View File

@ -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 ASC.Core.Common.EF;
namespace ASC.AuditTrail.Repositories;
[Scope(Additional = typeof(AuditEventsRepositoryExtensions))]
@ -57,20 +59,20 @@ public class AuditEventsRepository
DateTime? from = null,
DateTime? to = null,
int startIndex = 0,
int limit = 0,
int limit = 0,
Guid? withoutUserId = null)
{
return await GetByFilterWithActionsAsync(
userId,
productType,
productType,
moduleType,
actionType,
new List<MessageAction?> { action },
entry,
target,
from,
target,
from,
to,
startIndex,
startIndex,
limit,
withoutUserId);
}
@ -108,7 +110,7 @@ public class AuditEventsRepository
if (userId.HasValue && userId.Value != Guid.Empty)
{
query = query.Where(r => r.Event.UserId == userId.Value);
}
}
else if (withoutUserId.HasValue && withoutUserId.Value != Guid.Empty)
{
query = query.Where(r => r.Event.UserId != withoutUserId.Value);
@ -228,34 +230,6 @@ public class AuditEventsRepository
}
}
internal static class PredicateBuilder
{
internal static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b)
{
var p = a.Parameters[0];
var visitor = new SubstExpressionVisitor();
visitor.Subst[b.Parameters[0]] = p;
Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
}
internal class SubstExpressionVisitor : ExpressionVisitor
{
internal Dictionary<Expression, Expression> Subst = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node)
{
if (Subst.TryGetValue(node, out var newValue))
{
return newValue;
}
return node;
}
}
public static class AuditEventsRepositoryExtensions
{
public static void Register(DIHelper services)

View File

@ -248,23 +248,43 @@ const RootFolderContainer = (props) => {
<span>
<div className="empty-folder_container-links">
<StyledPlusIcon
className="empty-folder_container-image"
className="plus-document empty-folder_container-image"
data-format="docx"
onClick={onCreate}
alt="plus_icon"
/>
<Box className="flex-wrapper_container">
<Link data-format="docx" onClick={onCreate} {...linkStyles}>
<Link
id="document"
data-format="docx"
onClick={onCreate}
{...linkStyles}
>
{t("Document")},
</Link>
<Link data-format="xlsx" onClick={onCreate} {...linkStyles}>
<Link
id="spreadsheet"
data-format="xlsx"
onClick={onCreate}
{...linkStyles}
>
{t("Spreadsheet")},
</Link>
<Link data-format="pptx" onClick={onCreate} {...linkStyles}>
<Link
id="presentation"
data-format="pptx"
onClick={onCreate}
{...linkStyles}
>
{t("Presentation")},
</Link>
<Link data-format="docxf" onClick={onCreate} {...linkStyles}>
<Link
id="form-template"
data-format="docxf"
onClick={onCreate}
{...linkStyles}
>
{t("Translations:NewForm")}
</Link>
</Box>
@ -272,11 +292,11 @@ const RootFolderContainer = (props) => {
<div className="empty-folder_container-links">
<StyledPlusIcon
className="empty-folder_container-image"
className="plus-folder empty-folder_container-image"
onClick={onCreate}
alt="plus_icon"
/>
<Link {...linkStyles} onClick={onCreate}>
<Link id="folder" {...linkStyles} onClick={onCreate}>
{t("Folder")}
</Link>
</div>

View File

@ -132,6 +132,7 @@ const Dialog = ({
{isCreateDialog && extension && (
<Box displayProp="flex" alignItems="center" paddingProp="16px 0 0">
<Checkbox
className="dont-ask-again"
label={t("Common:DontAskAgain")}
isChecked={isChecked}
onChange={onChangeCheckbox}
@ -150,6 +151,7 @@ const Dialog = ({
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="submit"
key="GlobalSendBtn"
label={isCreateDialog ? t("Common:Create") : t("Common:SaveButton")}
size="normal"
@ -160,6 +162,7 @@ const Dialog = ({
onClick={onSaveAction}
/>
<Button
className="cancel-button"
key="CloseBtn"
label={t("Common:CancelButton")}
size="normal"

View File

@ -36,7 +36,8 @@ const AvatarEditorDialog = (props) => {
"CreateEditRoomDialog",
]);
const { visible, onClose, profile, updateCreatedAvatar, setHasAvatar } = props;
const { visible, onClose, profile, updateCreatedAvatar, setHasAvatar } =
props;
const [avatar, setAvatar] = useState({
uploadedFile: profile.hasAvatar ? profile.avatarMax : DefaultUserAvatarMax,
x: 0.5,
@ -54,7 +55,7 @@ const AvatarEditorDialog = (props) => {
if (!avatar.uploadedFile) {
const res = await deleteAvatar(profile.id);
updateCreatedAvatar(res);
setHasAvatar(false)
setHasAvatar(false);
onClose();
return;
}
@ -70,7 +71,7 @@ const AvatarEditorDialog = (props) => {
if (res.success) {
res.data && updateCreatedAvatar(res.data);
setHasAvatar(true)
setHasAvatar(true);
toastr.success(t("Common:ChangesSavedSuccessfully"));
} else {
throw new Error(t("Common:ErrorInternalServer"));
@ -113,6 +114,7 @@ const AvatarEditorDialog = (props) => {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="save"
key="AvatarEditorSaveBtn"
label={t("Common:SaveButton")}
size="normal"
@ -122,6 +124,7 @@ const AvatarEditorDialog = (props) => {
isLoading={isLoading}
/>
<Button
className="cancel-button"
key="AvatarEditorCloseBtn"
label={t("Common:CancelButton")}
size="normal"
@ -136,7 +139,11 @@ const AvatarEditorDialog = (props) => {
export default inject(({ peopleStore }) => {
const { targetUserStore } = peopleStore;
const { targetUser: profile, updateCreatedAvatar, setHasAvatar } = targetUserStore;
const {
targetUser: profile,
updateCreatedAvatar,
setHasAvatar,
} = targetUserStore;
return {
profile,

View File

@ -175,6 +175,7 @@ class ChangeEmailDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="send"
key="ChangeEmailSendBtn"
label={t("Common:SendButton")}
size="normal"
@ -184,6 +185,7 @@ class ChangeEmailDialogComponent extends React.Component {
isLoading={isRequestRunning}
/>
<Button
className="cancel-button"
key="CloseBtn"
label={t("Common:CancelButton")}
size="normal"

View File

@ -74,6 +74,7 @@ const ChangeNameDialog = (props) => {
className="field"
>
<TextInput
className="first-name"
scale={true}
isAutoFocussed={true}
value={firstName}
@ -91,6 +92,7 @@ const ChangeNameDialog = (props) => {
className="field"
>
<TextInput
className="last-name"
scale={true}
value={lastName}
onChange={(e) => setLastName(e.target.value)}
@ -103,6 +105,7 @@ const ChangeNameDialog = (props) => {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="save"
key="ChangeNameSaveBtn"
label={t("Common:SaveButton")}
size="normal"
@ -113,6 +116,7 @@ const ChangeNameDialog = (props) => {
tabIndex={3}
/>
<Button
className="cancel-button"
key="CloseBtn"
label={t("Common:CancelButton")}
size="normal"

View File

@ -57,14 +57,8 @@ class ChangePasswordDialogComponent extends React.Component {
render() {
// console.log("ChangePasswordDialog render");
const {
t,
tReady,
visible,
email,
onClose,
currentColorScheme,
} = this.props;
const { t, tReady, visible, email, onClose, currentColorScheme } =
this.props;
const { isRequestRunning } = this.state;
return (
@ -84,6 +78,7 @@ class ChangePasswordDialogComponent extends React.Component {
>
Send the password change instructions to the
<Link
className="email-link"
type="page"
href={`mailto:${email}`}
noHover
@ -98,6 +93,7 @@ class ChangePasswordDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="send"
key="ChangePasswordSendBtn"
label={t("Common:SendButton")}
size="normal"
@ -107,6 +103,7 @@ class ChangePasswordDialogComponent extends React.Component {
isLoading={isRequestRunning}
/>
<Button
className="cancel-button"
key="CloseBtn"
label={t("Common:CancelButton")}
size="normal"

View File

@ -87,6 +87,7 @@ const ChangePricingPlanDialog = ({
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="ok-button"
label={t("Common:OKButton")}
size="normal"
primary={true}

View File

@ -273,6 +273,7 @@ const PureConnectDialogContainer = (props) => {
errorMessage={t("Common:RequiredField")}
>
<TextInput
id="connection-url-input"
isAutoFocussed={true}
hasError={!isUrlValid}
isDisabled={isLoading}
@ -292,6 +293,7 @@ const PureConnectDialogContainer = (props) => {
errorMessage={t("Common:RequiredField")}
>
<TextInput
id="login-input"
isAutoFocussed={!showUrlField}
hasError={!isLoginValid}
isDisabled={isLoading}
@ -310,6 +312,7 @@ const PureConnectDialogContainer = (props) => {
style={roomCreation ? { margin: "0" } : {}}
>
<PasswordInput
id="password-input"
hasError={!isPasswordValid}
isDisabled={isLoading}
tabIndex={3}
@ -342,6 +345,7 @@ const PureConnectDialogContainer = (props) => {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
id="save"
tabIndex={5}
label={t("Common:SaveButton")}
size="normal"
@ -352,6 +356,7 @@ const PureConnectDialogContainer = (props) => {
isLoading={isLoading}
/>
<Button
id="cancel"
tabIndex={5}
label={t("Common:CancelButton")}
size="normal"

View File

@ -29,8 +29,6 @@ export const getRoomTypeDescriptionTranslation = (roomType = 1, t) => {
return t("CreateEditRoomDialog:ViewOnlyRoomDescription");
case RoomsType.CustomRoom:
return t("CreateEditRoomDialog:CustomRoomDescription");
case RoomsType.PublicRoom:
return t("CreateEditRoomDialog:PublicRoomDescription");
}
};

View File

@ -38,6 +38,7 @@ const DeletePortalDialog = (props) => {
Before you delete the portal, please make sure that automatic billing
is turned off. You may check the status of automatic billing in
<ColorTheme
className="stripe-url-link"
tag="a"
themeId={ThemeType.Link}
fontSize="13px"
@ -51,6 +52,7 @@ const DeletePortalDialog = (props) => {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="delete-button"
key="DeletePortalBtn"
label={t("Common:Delete")}
size="normal"
@ -59,6 +61,7 @@ const DeletePortalDialog = (props) => {
onClick={onDeleteClick}
/>
<Button
className="cancel-button"
key="CancelDeleteBtn"
label={t("Common:CancelButton")}
size="normal"

View File

@ -366,6 +366,7 @@ class DownloadDialogComponent extends React.Component {
<ModalDialog.Footer>
<Button
key="DownloadButton"
className="download-button"
label={t("Common:Download")}
size="normal"
primary
@ -375,6 +376,7 @@ class DownloadDialogComponent extends React.Component {
/>
<Button
key="CancelButton"
className="cancel-button"
label={t("Common:CancelButton")}
size="normal"
onClick={this.onClose}

View File

@ -36,12 +36,17 @@ const LogoutAllConnectionDialog = ({
{t("Profile:DescriptionForSecurity")}
</Text>
<Box displayProp="flex" alignItems="center">
<Checkbox isChecked={isChecked} onChange={onChangeCheckbox} />
<Checkbox
className="change-password"
isChecked={isChecked}
onChange={onChangeCheckbox}
/>
{t("Profile:ChangePasswordAfterLoggingOut")}
</Box>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="logout"
key="LogoutBtn"
label={t("Profile:LogoutBtn")}
size="normal"
@ -53,6 +58,7 @@ const LogoutAllConnectionDialog = ({
isLoading={loading}
/>
<Button
className="cancel-button"
key="CloseBtn"
label={t("Common:CancelButton")}
size="normal"

View File

@ -110,6 +110,7 @@ const SalesDepartmentRequestDialog = ({
errorMessage={t("Common:RequiredField")}
>
<TextInput
id="your-name"
hasError={!isValidName}
name="name"
type="text"
@ -134,7 +135,7 @@ const SalesDepartmentRequestDialog = ({
>
<TextInput
hasError={!isValidEmail}
id="e-mail"
id="registration-email"
name="e-mail"
type="text"
size="base"
@ -156,6 +157,7 @@ const SalesDepartmentRequestDialog = ({
errorMessage={t("Common:RequiredField")}
>
<Textarea
id="request-details"
heightScale={false}
hasError={!isValidDescription}
placeholder={t("RequestDetails")}
@ -168,6 +170,7 @@ const SalesDepartmentRequestDialog = ({
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="send-button"
label={isLoading ? t("Common:Sending") : t("Common:SendButton")}
size="normal"
primary={true}
@ -177,6 +180,7 @@ const SalesDepartmentRequestDialog = ({
tabIndex={3}
/>
<Button
className="cancel-button"
label={t("Common:CancelButton")}
size="normal"
onClick={onCloseModal}

View File

@ -302,6 +302,7 @@ const InvitePanel = ({
{hasInvitedUsers && (
<StyledButtons>
<Button
className="send-invitation"
scale={true}
size={"normal"}
isDisabled={hasErrors}
@ -311,6 +312,7 @@ const InvitePanel = ({
isLoading={isLoading}
/>
<Button
className="cancel-button"
scale={true}
size={"normal"}
onClick={onClose}
@ -366,12 +368,8 @@ export default inject(({ auth, peopleStore, filesStore, dialogsStore }) => {
setInvitePanelOptions,
} = dialogsStore;
const {
getFolderInfo,
setRoomSecurity,
getRoomSecurityInfo,
folders,
} = filesStore;
const { getFolderInfo, setRoomSecurity, getRoomSecurityInfo, folders } =
filesStore;
return {
folders,

View File

@ -15,6 +15,7 @@ const AccessSelector = ({
withRemove = false,
filteredAccesses,
setIsOpenItemAccess,
className,
}) => {
const [horizontalOrientation, setHorizontalOrientation] = useState(false);
const width = containerRef?.current?.offsetWidth - 32;
@ -53,6 +54,7 @@ const AccessSelector = ({
<StyledAccessSelector className="invite-panel_access-selector">
{!(isMobileOnly && !isMobileHorizontalOrientation) && (
<AccessRightSelect
className={className}
selectedOption={selectedOption}
onSelect={onSelectAccess}
accessOptions={filteredAccesses ? filteredAccesses : accessOptions}
@ -70,6 +72,7 @@ const AccessSelector = ({
{isMobileOnly && !isMobileHorizontalOrientation && (
<AccessRightSelect
className={className}
selectedOption={selectedOption}
onSelect={onSelectAccess}
accessOptions={filteredAccesses ? filteredAccesses : accessOptions}

View File

@ -188,6 +188,7 @@ const ExternalLinks = ({
</div>
)}
<StyledToggleButton
className="invite-via-link"
isChecked={externalLinksVisible}
onChange={toggleLinks}
/>
@ -212,6 +213,7 @@ const ExternalLinks = ({
/>
</StyledInviteInput>
<AccessSelector
className="invite-via-link-access"
t={t}
roomType={roomType}
defaultAccess={activeLink.access}

View File

@ -242,6 +242,7 @@ const InviteInput = ({
{t("AddManually")}
{!hideSelector && (
<StyledLink
className="link-list"
fontWeight="600"
type="action"
isHovered
@ -260,6 +261,7 @@ const InviteInput = ({
<StyledInviteInputContainer ref={inputsRef}>
<StyledInviteInput ref={searchRef}>
<TextInput
className="invite-input"
scale
onChange={onChange}
placeholder={
@ -288,6 +290,7 @@ const InviteInput = ({
foundUsers
) : (
<DropDownItem
className="add-item"
style={{ width: "inherit" }}
textOverflow
onClick={addEmail}
@ -300,6 +303,7 @@ const InviteInput = ({
)}
<AccessSelector
className="add-manually-access"
t={t}
roomType={roomType}
defaultAccess={selectedAccess}

View File

@ -130,10 +130,15 @@ const Item = ({
size={16}
color="#F21C0E"
/>
<StyledDeleteIcon size="medium" onClick={removeItem} />
<StyledDeleteIcon
className="delete-icon"
size="medium"
onClick={removeItem}
/>
</>
) : (
<AccessSelector
className="user-access"
t={t}
roomType={roomType}
defaultAccess={defaultAccess?.access}

View File

@ -0,0 +1,138 @@
import React from "react";
import styled, { css } from "styled-components";
import Row from "@docspace/components/row";
import LinkWithDropdown from "@docspace/components/link-with-dropdown";
import ToggleButton from "@docspace/components/toggle-button";
import { StyledLinkRow } from "../StyledPanels";
import AccessComboBox from "./AccessComboBox";
import { ShareAccessRights } from "@docspace/common/constants";
import AccessEditIcon from "PUBLIC_DIR/images/access.edit.react.svg";
import CopyIcon from "PUBLIC_DIR/images/copy.react.svg";
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
import { Base } from "@docspace/components/themes";
const StyledAccessEditIcon = styled(AccessEditIcon)`
${commonIconsStyles}
path {
fill: ${(props) => props.theme.filesPanels.sharing.fill};
}
`;
StyledAccessEditIcon.defaultProps = { theme: Base };
const StyledCopyIcon = styled(CopyIcon)`
${commonIconsStyles}
cursor: pointer;
${(props) =>
props.isDisabled &&
css`
cursor: default;
pointer-events: none;
`}
`;
class LinkRow extends React.Component {
onToggleButtonChange = () => {
const { onToggleLink, item } = this.props;
onToggleLink(item);
};
render() {
const {
linkText,
options,
index,
t,
item,
withToggle,
externalAccessOptions,
onChangeItemAccess,
isLoading,
theme,
onCopyLink,
} = this.props;
const isChecked = item.access !== ShareAccessRights.DenyAccess;
const disableLink = withToggle ? !isChecked : false;
const isDisabled = isLoading || disableLink;
return (
<StyledLinkRow
theme={theme}
withToggle={withToggle}
isDisabled={isDisabled}
className="link-row__container"
>
<Row
theme={theme}
className="link-row"
key={`${linkText.replace(" ", "-")}-key_${index}`}
element={
withToggle ? (
<AccessComboBox
theme={theme}
t={t}
access={item.access}
directionX="left"
accessOptions={externalAccessOptions}
onAccessChange={onChangeItemAccess}
itemId={item.sharedTo.id}
isDisabled={isDisabled}
disableLink={disableLink}
/>
) : (
<StyledAccessEditIcon
theme={theme}
size="medium"
className="sharing_panel-owner-icon"
/>
)
}
contextButtonSpacerWidth="0px"
>
<>
<div className="sharing_panel-link-container">
<LinkWithDropdown
theme={theme}
className="sharing_panel-link"
color={theme.filesPanels.sharing.dropdownColor}
dropdownType="alwaysDashed"
data={options}
fontSize="13px"
fontWeight={600}
isDisabled={isDisabled}
>
{linkText}
</LinkWithDropdown>
{onCopyLink && (
<StyledCopyIcon
theme={theme}
isDisabled={isDisabled}
size="medium"
onClick={onCopyLink}
title={t("CopyExternalLink")}
/>
)}
</div>
{withToggle && (
<div>
<ToggleButton
theme={theme}
isChecked={isChecked}
onChange={this.onToggleButtonChange}
isDisabled={isLoading}
className="sharing-row__toggle-button"
/>
</div>
)}
</>
</Row>
</StyledLinkRow>
);
}
}
export default LinkRow;

View File

@ -196,7 +196,7 @@ export const getCategoryUrl = (categoryType, folderId = null) => {
return "/accounts/filter";
case CategoryType.Settings:
return "/settings/common";
return "/settings/personal";
default:
throw new Error("Unknown category type");

View File

@ -144,7 +144,10 @@ const AboutContent = (props) => {
</ColorTheme>
<Text className="row-el select-el" fontSize="13px" fontWeight="600">
v.{buildVersionInfo.docspace}
v.
<span className="version-document-management">
{buildVersionInfo.docspace}
</span>
</Text>
</div>
@ -166,7 +169,10 @@ const AboutContent = (props) => {
&nbsp;ONLYOFFICE Docs&nbsp;
</ColorTheme>
<Text className="row-el select-el" fontSize="13px" fontWeight="600">
v.{buildVersionInfo.documentServer}
v.
<span className="version-online-editors">
{buildVersionInfo.documentServer}
</span>
</Text>
</div>

View File

@ -53,7 +53,9 @@ const DebugInfoDialog = (props) => {
<ModalDialog.Header>Debug Info</ModalDialog.Header>
<ModalDialog.Body className="debug-info-body">
{/* <Text>{`# Build version: ${BUILD_VERSION}`}</Text> */}
<Text>{`# Version: ${VERSION}`}</Text>
<Text>
# Version: <span className="version">{VERSION}</span>
</Text>
<Text>{`# Build date: ${BUILD_AT}`}</Text>
{user && (
<Text>{`# Current User: ${user?.displayName} (id:${user?.id})`}</Text>

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from "react";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import toastr from "@docspace/components/toast/toastr";
import { FolderType } from "@docspace/common/constants";
import Loaders from "@docspace/common/components/Loaders";
import PersonPlusReactSvgUrl from "PUBLIC_DIR/images/person+.react.svg?url";
@ -195,6 +196,7 @@ const Members = ({
/>
)}
</StyledUserTypeHeader>
<StyledUserList>
{Object.values(members.inRoom).map((user) => (
<User
@ -215,6 +217,7 @@ const Members = ({
/>
))}
</StyledUserList>
{!!members.expected.length && (
<StyledUserTypeHeader isExpect>
<Text className="title">{t("PendingInvitations")}</Text>
@ -230,6 +233,7 @@ const Members = ({
)}
</StyledUserTypeHeader>
)}
<StyledUserList>
{Object.values(members.expected).map((user, i) => (
<User

View File

@ -1,10 +1,13 @@
import CrossReactSvgUrl from "PUBLIC_DIR/images/cross.react.svg?url";
import React, { useState, useEffect } from "react";
import { inject } from "mobx-react";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import { isMobile as isMobileRDD } from "react-device-detect";
import IconButton from "@docspace/components/icon-button";
import Text from "@docspace/components/text";
import Loaders from "@docspace/common/components/Loaders";
import withLoader from "@docspace/client/src/HOCs/withLoader";
import Submenu from "@docspace/components/submenu";
import {
isDesktop as isDesktopUtils,
@ -15,7 +18,7 @@ import {
import { ColorTheme, ThemeType } from "@docspace/components/ColorTheme";
import { StyledInfoPanelHeader } from "./styles/common";
import { RoomsType } from "@docspace/common/constants";
import { FolderType } from "@docspace/common/constants";
const InfoPanelHeaderContent = (props) => {
const {
@ -64,6 +67,8 @@ const InfoPanelHeaderContent = (props) => {
const setHistory = () => setView("info_history");
const setDetails = () => setView("info_details");
//const isArchiveRoot = rootFolderType === FolderType.Archive;
const submenuData = [
{
id: "info_members",
@ -84,6 +89,9 @@ const InfoPanelHeaderContent = (props) => {
content: null,
},
];
// const selectionRoomRights = selectionParentRoom
// ? selectionParentRoom.security?.Read
// : selection?.security?.Read;
const roomsSubmenu = [...submenuData];
@ -144,8 +152,12 @@ export default inject(({ auth, selectedFolderStore }) => {
getIsGallery,
getIsAccounts,
getIsTrash,
//selectionParentRoom,
} = auth.infoPanelStore;
const { isRootFolder, roomType } = selectedFolderStore;
const {
isRootFolder,
// rootFolderType
} = selectedFolderStore;
return {
selection,
@ -158,6 +170,14 @@ export default inject(({ auth, selectedFolderStore }) => {
getIsGallery,
getIsAccounts,
getIsTrash,
isRootFolder,
};
})(withTranslation(["Common", "InfoPanel"])(InfoPanelHeaderContent));
})(
withTranslation(["Common", "InfoPanel"])(
InfoPanelHeaderContent
// withLoader(observer(InfoPanelHeaderContent))(
// <Loaders.InfoPanelHeaderLoader />
// )
)
);

View File

@ -27,7 +27,7 @@ const GeneralSettings = ({
{t("StoringFileVersion")}
</Heading>
<ToggleButton
className="toggle-btn"
className="intermediate-version toggle-btn"
label={t("IntermediateVersion")}
onChange={onChangeStoreForceSave}
isChecked={storeForceSave}

View File

@ -104,21 +104,21 @@ const PersonalSettings = ({
/>
{!isVisitor && (
<ToggleButton
className="toggle-btn"
className="ask-again toggle-btn"
label={t("Common:DontAskAgain")}
onChange={onChangeKeepNewFileName}
isChecked={keepNewFileName}
/>
)}
<ToggleButton
className="toggle-btn"
className="save-copy-original toggle-btn"
label={t("OriginalCopy")}
onChange={onChangeOriginalCopy}
isChecked={storeOriginalFiles}
/>
{!isVisitor && (
<ToggleButton
className="toggle-btn"
className="display-notification toggle-btn"
label={t("DisplayNotification")}
onChange={onChangeDeleteConfirm}
isChecked={confirmDelete}
@ -161,7 +161,7 @@ const PersonalSettings = ({
</Heading>
{!isVisitor && (
<ToggleButton
className="toggle-btn"
className="update-or-create toggle-btn"
label={t("UpdateOrCreate")}
onChange={onChangeUpdateIfExist}
isChecked={updateIfExist}
@ -169,7 +169,7 @@ const PersonalSettings = ({
)}
{!isVisitor && (
<ToggleButton
className="toggle-btn"
className="keep-intermediate-version toggle-btn"
label={t("KeepIntermediateVersion")}
onChange={onChangeForceSave}
isChecked={forceSave}

View File

@ -30,18 +30,18 @@ const SectionBodyContent = ({ isErrorSettings, user }) => {
const navigate = useNavigate();
const setting = window.location.pathname.endsWith("/settings/common")
? "common"
: "admin";
const setting = window.location.pathname.endsWith("/settings/personal")
? "personal"
: "general";
const commonSettings = {
id: "common",
id: "personal",
name: t("Common:SettingsPersonal"),
content: <PersonalSettings t={t} />,
};
const adminSettings = {
id: "admin",
id: "general",
name: t("Common:SettingsGeneral"),
content: <GeneralSettings t={t} />,
};
@ -80,7 +80,7 @@ const SectionBodyContent = ({ isErrorSettings, user }) => {
) : (
<Submenu
data={data}
startSelect={setting === "common" ? commonSettings : adminSettings}
startSelect={setting === "personal" ? commonSettings : adminSettings}
onSelect={onSelect}
/>
)}

View File

@ -16,13 +16,13 @@ const SettingsView = ({
const inLoad = (!isLoadedSettingsTree && isLoading) || isLoading;
const setting = location.pathname.includes("/settings/common")
? "common"
: "admin";
const setting = location.pathname.includes("/settings/general")
? "general"
: "personal";
return (
<>
{inLoad ? (
setting === "common" ? (
setting === "personal" ? (
<Loaders.SettingsCommon isAdmin={isAdmin} />
) : (
<Loaders.SettingsAdmin />

View File

@ -31,7 +31,7 @@ const DailyFeedContainer = ({
<Text {...textDescriptionsProps}>{t("DailyFeedDescription")}</Text>
</div>
<ToggleButton
className="toggle-btn"
className="daily-feed toggle-btn"
onChange={onChangeEmailSubscription}
isChecked={dailyFeedSubscriptions}
/>

View File

@ -38,7 +38,7 @@ const RoomsActionsContainer = ({
</Text>
</div>
<ToggleButton
className="toggle-btn"
className="rooms-actions toggle-btn"
onChange={onChangeBadgeSubscription}
isChecked={badgesSubscription}
/>

View File

@ -31,7 +31,7 @@ const RoomsActivityContainer = ({
<Text {...textDescriptionsProps}>{t("RoomsActivityDescription")}</Text>
</div>
<ToggleButton
className="toggle-btn"
className="rooms-activity toggle-btn"
onChange={onChangeEmailSubscription}
isChecked={roomsActivitySubscription}
/>

View File

@ -30,7 +30,7 @@ const UsefulTipsContainer = ({
<Text {...textDescriptionsProps}>{t("UsefulTipsDescription")}</Text>
</div>
<ToggleButton
className="toggle-btn"
className="useful-tips toggle-btn"
onChange={onChangeEmailSubscription}
isChecked={usefulTipsSubscription}
/>

View File

@ -56,11 +56,8 @@ const AdditionalResources = (props) => {
const [hasChange, setHasChange] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const {
feedbackAndSupportEnabled,
videoGuidesEnabled,
helpCenterEnabled,
} = additionalSettings;
const { feedbackAndSupportEnabled, videoGuidesEnabled, helpCenterEnabled } =
additionalSettings;
const getSettings = () => {
const additionalSettings = getFromSessionStorage("additionalSettings");
@ -213,7 +210,7 @@ const AdditionalResources = (props) => {
<div className="branding-checkbox">
<Checkbox
tabIndex={12}
className="checkbox"
className="show-feedback-support checkbox"
isDisabled={!isSettingPaid}
label={t("ShowFeedbackAndSupport")}
isChecked={feedbackAndSupportEnabled}
@ -222,7 +219,7 @@ const AdditionalResources = (props) => {
<Checkbox
tabIndex={13}
className="checkbox"
className="show-video-guides checkbox"
isDisabled={!isSettingPaid}
label={t("ShowVideoGuides")}
isChecked={videoGuidesEnabled}
@ -230,7 +227,7 @@ const AdditionalResources = (props) => {
/>
<Checkbox
tabIndex={14}
className="checkbox"
className="show-help-center checkbox"
isDisabled={!isSettingPaid}
label={t("ShowHelpCenter")}
isChecked={helpCenterEnabled}
@ -247,6 +244,8 @@ const AdditionalResources = (props) => {
reminderTest={t("YouHaveUnsavedChanges")}
showReminder={(isSettingPaid && hasChange) || isLoading}
disableRestoreToDefault={additionalResourcesIsDefault || isLoading}
additionalClassSaveButton="additional-resources-save"
additionalClassCancelButton="additional-resources-cancel"
/>
</StyledComponent>
</>
@ -256,10 +255,8 @@ const AdditionalResources = (props) => {
export default inject(({ auth, common }) => {
const { settingsStore } = auth;
const {
setIsLoadedAdditionalResources,
isLoadedAdditionalResources,
} = common;
const { setIsLoadedAdditionalResources, isLoadedAdditionalResources } =
common;
const {
getAdditionalResources,

View File

@ -59,7 +59,9 @@ const CompanyInfoSettings = (props) => {
};
const [companySettings, setCompanySettings] = useState({});
const [companySettingsError, setCompanySettingsError] = useState(defaultCompanySettingsError);
const [companySettingsError, setCompanySettingsError] = useState(
defaultCompanySettingsError
);
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [showModal, setShowModal] = useState(false);
@ -112,7 +114,9 @@ const CompanyInfoSettings = (props) => {
}, [isLoading]);
useEffect(() => {
const defaultCompanySettings = getFromSessionStorage("defaultCompanySettings");
const defaultCompanySettings = getFromSessionStorage(
"defaultCompanySettings"
);
const newSettings = {
address: companySettings.address,
@ -190,7 +194,10 @@ const CompanyInfoSettings = (props) => {
const companyName = e.target.value;
validateEmpty(companyName, "companyName");
setCompanySettings({ ...companySettings, companyName });
saveToSessionStorage("companySettings", {...companySettings, companyName });
saveToSessionStorage("companySettings", {
...companySettings,
companyName,
});
};
const onChangePhone = (e) => {
@ -411,6 +418,8 @@ const CompanyInfoSettings = (props) => {
displaySettings={true}
showReminder={(isSettingPaid && showReminder) || isLoading}
disableRestoreToDefault={companyInfoSettingsIsDefault || isLoading}
additionalClassSaveButton="company-info-save"
additionalClassCancelButton="company-info-cancel"
/>
</StyledComponent>
</>

View File

@ -12,6 +12,7 @@ const Logo = (props) => {
isSettingPaid,
onChangeText,
inputId,
linkId,
imageClass,
isEditor,
} = props;
@ -58,6 +59,7 @@ const Logo = (props) => {
disabled={!isSettingPaid}
/>
<Link
id={linkId}
fontWeight="600"
isHovered
type="action"

View File

@ -37,9 +37,8 @@ const WhiteLabel = (props) => {
} = props;
const [isLoadedData, setIsLoadedData] = useState(false);
const [logoTextWhiteLabel, setLogoTextWhiteLabel] = useState("");
const [defaultLogoTextWhiteLabel, setDefaultLogoTextWhiteLabel] = useState(
""
);
const [defaultLogoTextWhiteLabel, setDefaultLogoTextWhiteLabel] =
useState("");
const [logoUrlsWhiteLabel, setLogoUrlsWhiteLabel] = useState(null);
const [isSaving, setIsSaving] = useState(false);
@ -234,7 +233,7 @@ const WhiteLabel = (props) => {
className="settings_unavailable"
>
<TextInput
className="input"
className="company-name input"
value={logoTextWhiteLabel}
onChange={onChangeCompanyName}
isDisabled={!isSettingPaid}
@ -272,6 +271,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[0].path.light}
imageClass="logo-header background-light"
inputId="logoUploader_1_light"
linkId="link-space-header-light"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -281,6 +281,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[0].path.dark}
imageClass="logo-header background-dark"
inputId="logoUploader_1_dark"
linkId="link-space-header-dark"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -303,6 +304,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[5].path.light}
imageClass="border-img logo-compact background-light"
inputId="logoUploader_6_light"
linkId="link-compact-left-menu-light"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -312,6 +314,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[5].path.dark}
imageClass="border-img logo-compact background-dark"
inputId="logoUploader_6_dark"
linkId="link-compact-left-menu-dark"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -334,6 +337,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[1].path.light}
imageClass="border-img logo-big background-white"
inputId="logoUploader_2_light"
linkId="link-login-emails-light"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -343,6 +347,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[1].path.dark}
imageClass="border-img logo-big background-dark"
inputId="logoUploader_2_dark"
linkId="link-login-emails-dark"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -365,6 +370,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[6].path.light}
imageClass="border-img logo-about background-white"
inputId="logoUploader_7_light"
linkId="link-about-light"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -374,6 +380,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[6].path.dark}
imageClass="border-img logo-about background-dark"
inputId="logoUploader_7_dark"
linkId="link-about-dark"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -394,6 +401,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[2].path.light}
imageClass="border-img logo-favicon"
inputId="logoUploader_3_light"
linkId="link-favicon"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -413,6 +421,7 @@ const WhiteLabel = (props) => {
isEditor={true}
src={logoUrlsWhiteLabel[3].path.light}
inputId="logoUploader_4_light"
linkId="link-editors-header"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -432,6 +441,7 @@ const WhiteLabel = (props) => {
src={logoUrlsWhiteLabel[4].path.light}
imageClass="border-img logo-embedded-editor background-white"
inputId="logoUploader_5_light"
linkId="link-embedded-editor"
onChangeText={t("ChangeLogoButton")}
onChange={onChangeLogo}
isSettingPaid={isSettingPaid}
@ -450,6 +460,8 @@ const WhiteLabel = (props) => {
showReminder={isSettingPaid}
saveButtonDisabled={isEqualLogo && isEqualText}
isSaving={isSaving}
additionalClassSaveButton="white-label-save"
additionalClassCancelButton="white-label-cancel"
/>
</WhiteLabelWrapper>
);

View File

@ -83,13 +83,11 @@ const LanguageAndTimeZone = (props) => {
React.useEffect(() => {
languageFromSessionStorage = getFromSessionStorage("language");
languageDefaultFromSessionStorage = getFromSessionStorage(
"languageDefault"
);
languageDefaultFromSessionStorage =
getFromSessionStorage("languageDefault");
timezoneFromSessionStorage = getFromSessionStorage("timezone");
timezoneDefaultFromSessionStorage = getFromSessionStorage(
"timezoneDefault"
);
timezoneDefaultFromSessionStorage =
getFromSessionStorage("timezoneDefault");
setDocumentTitle(t("StudioTimeLanguageSettings"));
@ -240,9 +238,8 @@ const LanguageAndTimeZone = (props) => {
}
// TODO: Remove div with height 64 and remove settings-mobile class
const settingsMobile = document.getElementsByClassName(
"settings-mobile"
)[0];
const settingsMobile =
document.getElementsByClassName("settings-mobile")[0];
if (settingsMobile) {
settingsMobile.style.display = "none";
@ -499,6 +496,7 @@ const LanguageAndTimeZone = (props) => {
{t("StudioTimeLanguageSettings")}
</div>
<HelpButton
className="language-time-zone-help-button"
offsetRight={0}
iconName={CombinedShapeSvgUrl}
size={12}
@ -522,6 +520,8 @@ const LanguageAndTimeZone = (props) => {
cancelButtonLabel={t("Common:CancelButton")}
displaySettings={true}
hasScroll={hasScroll}
additionalClassSaveButton="language-time-zone-save"
additionalClassCancelButton="language-time-zone-cancel"
/>
</StyledSettingsComponent>
);
@ -544,12 +544,8 @@ export default inject(({ auth, setup, common }) => {
const { user } = auth.userStore;
const { setLanguageAndTime } = setup;
const {
isLoaded,
setIsLoadedLngTZSettings,
initSettings,
setIsLoaded,
} = common;
const { isLoaded, setIsLoadedLngTZSettings, initSettings, setIsLoaded } =
common;
return {
theme: auth.settingsStore.theme,
user,

View File

@ -313,6 +313,7 @@ const PortalRenaming = (props) => {
<div className="category-item-heading">
<div className="category-item-title">{t("PortalRenaming")}</div>
<HelpButton
className="portal-renaming-help-button"
offsetRight={0}
iconName={CombinedShapeSvgUrl}
size={12}
@ -337,6 +338,8 @@ const PortalRenaming = (props) => {
reminderTest={t("YouHaveUnsavedChanges")}
displaySettings={true}
hasScroll={hasScroll}
additionalClassSaveButton="portal-renaming-save"
additionalClassCancelButton="portal-renaming-cancel"
/>
<PortalRenamingDialog
visible={isShowModal}

View File

@ -326,6 +326,7 @@ const WelcomePageSettings = (props) => {
<div className="category-item-heading">
<div className="category-item-title">{t("CustomTitlesWelcome")}</div>
<HelpButton
className="welcome-page-help-button"
offsetRight={0}
iconName={CombinedShapeSvgUrl}
size={12}
@ -351,6 +352,8 @@ const WelcomePageSettings = (props) => {
displaySettings={true}
hasScroll={state.hasScroll}
disableRestoreToDefault={greetingSettingsIsDefault}
additionalClassSaveButton="welcome-page-save"
additionalClassCancelButton="welcome-page-cancel"
/>
</StyledSettingsComponent>
);

View File

@ -107,6 +107,7 @@ const Appearance = (props) => {
const array_items = useMemo(
() => [
{
id: "light-theme",
key: "0",
title: t("Profile:LightTheme"),
content: (
@ -120,6 +121,7 @@ const Appearance = (props) => {
),
},
{
id: "dark-theme",
key: "1",
title: t("Profile:DarkTheme"),
content: (
@ -733,7 +735,7 @@ const Appearance = (props) => {
<div className="buttons-container">
<Button
className="button"
className="save button"
label={t("Common:SaveButton")}
onClick={onSave}
primary
@ -742,7 +744,7 @@ const Appearance = (props) => {
/>
<Button
className="button"
className="edit-current-theme button"
label={t("Settings:EditCurrentTheme")}
onClick={onClickEdit}
size="small"
@ -750,7 +752,7 @@ const Appearance = (props) => {
/>
{isShowDeleteButton && (
<Button
className="button"
className="delete-theme button"
label={t("Settings:DeleteTheme")}
onClick={onOpenDialogDelete}
size="small"

View File

@ -123,6 +123,7 @@ const ColorSchemeDialog = (props) => {
{showSaveButtonDialog && (
<>
<Button
className="save"
label={t("Common:SaveButton")}
size="normal"
primary
@ -130,6 +131,7 @@ const ColorSchemeDialog = (props) => {
onClick={onSaveColorSchemeDialog}
/>
<Button
className="cancel-button"
label={t("Common:CancelButton")}
size="normal"
scale

View File

@ -49,7 +49,7 @@ export const LanguageTimeSettingsTooltip = ({
button at the bottom of the section.
<Link
color={currentColorScheme.main.accent}
className="display-block font-size"
className="tooltip-link display-block font-size"
isHovered={true}
target="_blank"
href={`${helpLink}/administration/docspace-settings.aspx#DocSpacelanguage`}
@ -120,7 +120,7 @@ export const DNSSettingsTooltip = ({
</Trans>
<Link
color={currentColorScheme.main.accent}
className="display-block font-size"
className="tooltip-link display-block font-size"
isHovered={true}
target="_blank"
href={`${helpLink}/administration/docspace-settings.aspx#alternativeurl`}

View File

@ -119,7 +119,7 @@ const HexColorPickerComponent = (props) => {
/>
<Button
label={t("Common:CancelButton")}
className="button"
className="cancel-button button"
size="small"
scale={true}
onClick={onCloseHexColorPicker}

View File

@ -33,14 +33,14 @@ const ModalDialogDelete = (props) => {
<ModalDialog.Body>{t("Settings:DeleteThemeNotice")}</ModalDialog.Body>
<ModalDialog.Footer>
<Button
className="button-modal"
className="delete button-modal"
label={t("Common:Delete")}
onClick={onClickDelete}
primary
size="normal"
/>
<Button
className="button-modal"
className="cancel-button button-modal"
label={t("Common:CancelButton")}
size="normal"
onClick={onClose}

View File

@ -441,7 +441,8 @@ class AutomaticBackup extends React.PureComponent {
{renderTooltip(
t("AutoBackupHelp") +
" " +
t("AutoBackupHelpNote", { organizationName })
t("AutoBackupHelpNote", { organizationName }),
"automatic-backup"
)}
{!isEnableAuto && (
<Badge
@ -457,7 +458,7 @@ class AutomaticBackup extends React.PureComponent {
</Text>
<div className="backup_toggle-wrapper">
<ToggleButton
className="backup_toggle-btn"
className="enable-automatic-backup backup_toggle-btn"
label={t("EnableAutomaticBackup")}
onChange={this.onClickPermissions}
isChecked={selectedEnableSchedule}
@ -472,6 +473,7 @@ class AutomaticBackup extends React.PureComponent {
<StyledModules>
<RadioButton
{...commonRadioButtonProps}
id="backup-room"
label={t("RoomsModule")}
name={`${DocumentModuleType}`}
key={0}
@ -493,6 +495,7 @@ class AutomaticBackup extends React.PureComponent {
>
<RadioButton
{...commonRadioButtonProps}
id="third-party-resource"
label={t("ThirdPartyResource")}
name={`${ResourcesModuleType}`}
isChecked={isCheckedThirdParty}
@ -515,6 +518,7 @@ class AutomaticBackup extends React.PureComponent {
<StyledModules>
<RadioButton
{...commonRadioButtonProps}
id="third-party-storage"
label={t("Common:ThirdPartyStorage")}
name={`${StorageModuleType}`}
isChecked={isCheckedThirdPartyStorage}

View File

@ -13,8 +13,6 @@ const ButtonContainer = ({
}) => {
const prevChange = useRef();
useEffect(() => {
prevChange.current = isChanged;
}, [isChanged]);
@ -32,6 +30,7 @@ const ButtonContainer = ({
/>
<Button
className="cancel-button"
label={t("Common:CancelButton")}
isDisabled={isLoadingData}
onClick={onCancelModuleSettings}

View File

@ -364,6 +364,7 @@ const DirectThirdPartyConnection = (props) => {
{!connectedThirdPartyAccount?.id || !isTheSameThirdPartyAccount ? (
<Button
id="connect-button"
primary
label={t("Common:Connect")}
onClick={onConnect}

View File

@ -254,10 +254,11 @@ class AmazonSettings extends React.Component {
} = this.props;
const { region } = this.state;
const renderTooltip = (helpInfo) => {
const renderTooltip = (helpInfo, className) => {
return (
<>
<HelpButton
className={className}
offsetRight={0}
iconName={HelpReactSvgUrl}
tooltipContent={
@ -291,9 +292,10 @@ class AmazonSettings extends React.Component {
<StyledBody>
<div className="backup_storage-tooltip">
<Text isBold>{this.bucketPlaceholder}</Text>
{renderTooltip(t("AmazonBucketTip"))}
{renderTooltip(t("AmazonBucketTip"), "bucket-tooltip")}
</div>
<TextInput
id="bucket-input"
name={bucket}
className="backup_text-input"
scale
@ -307,10 +309,10 @@ class AmazonSettings extends React.Component {
<StyledBody>
<div className="backup_storage-tooltip">
<Text isBold>{this.regionPlaceholder}</Text>
{renderTooltip(t("AmazonRegionTip"))}
{renderTooltip(t("AmazonRegionTip"), "region-tooltip")}
</div>
<ComboBox
className="backup_text-input"
className="region-combo-box backup_text-input"
options={this.regions}
selectedOption={{
key: 0,
@ -330,9 +332,10 @@ class AmazonSettings extends React.Component {
<StyledBody>
<div className="backup_storage-tooltip">
<Text isBold>{this.serviceUrlPlaceholder}</Text>
{renderTooltip(t("AmazonServiceTip"))}
{renderTooltip(t("AmazonServiceTip"), "service-tooltip")}
</div>
<TextInput
id="service-url-input"
name={serviceurl}
className="backup_text-input"
scale
@ -346,6 +349,7 @@ class AmazonSettings extends React.Component {
<StyledBody theme={theme}>
<Checkbox
id="force-path-style"
name={forcepathstyle}
label={this.forcePathStylePlaceholder}
isChecked={formSettings[forcepathstyle] === "false" ? false : true}
@ -355,13 +359,17 @@ class AmazonSettings extends React.Component {
tabIndex={4}
helpButton={
<div className="backup_storage-tooltip">
{renderTooltip(t("AmazonForcePathStyleTip"))}
{renderTooltip(
t("AmazonForcePathStyleTip"),
"force-path-style-tooltip"
)}
</div>
}
/>
</StyledBody>
<StyledBody theme={theme}>
<Checkbox
id="use-http"
className="backup_checkbox"
name={usehttp}
label={this.useHttpPlaceholder}
@ -372,7 +380,7 @@ class AmazonSettings extends React.Component {
tabIndex={5}
helpButton={
<div className="backup_storage-tooltip">
{renderTooltip(t("AmazonHTTPTip"))}
{renderTooltip(t("AmazonHTTPTip"), "http-tooltip")}
</div>
}
/>
@ -380,10 +388,10 @@ class AmazonSettings extends React.Component {
<StyledBody>
<div className="backup_storage-tooltip">
<Text isBold>{this.SSEPlaceholder}</Text>
{renderTooltip(t("AmazonSSETip"))}
{renderTooltip(t("AmazonSSETip"), "sse-method-tooltip")}
</div>
<ComboBox
className="backup_text-input"
className="sse-method-combo-box backup_text-input"
options={this.availableEncryptions}
selectedOption={{
key: 0,
@ -403,6 +411,7 @@ class AmazonSettings extends React.Component {
{selectedEncryption === this.serverSideEncryption && (
<>
<RadioButton
id="sse-s3"
className="backup_radio-button-settings"
value=""
label={this.sse_s3}
@ -413,6 +422,7 @@ class AmazonSettings extends React.Component {
/>
<RadioButton
id="sse-kms"
className="backup_radio-button-settings"
value=""
label={this.sse_kms}
@ -426,7 +436,7 @@ class AmazonSettings extends React.Component {
<>
<Text isBold>{"Managed CMK"}</Text>
<ComboBox
className="backup_text-input"
className="managed-cmk-combo-box backup_text-input"
options={this.managedKeys}
selectedOption={{
key: 0,
@ -447,6 +457,7 @@ class AmazonSettings extends React.Component {
<>
<Text isBold>{"KMS Key Id:"}</Text>
<TextInput
id="customer-manager-kms-key-id"
name={sse_key}
className="backup_text-input"
scale
@ -467,6 +478,7 @@ class AmazonSettings extends React.Component {
<>
<Text isBold>{"KMS Key Id:"}</Text>
<TextInput
id="client-side-encryption-kms-key-id"
name={sse_key}
className="backup_text-input"
scale
@ -481,6 +493,7 @@ class AmazonSettings extends React.Component {
{isNeedFilePath && (
<TextInput
id="file-path-input"
name="filePath"
className="backup_text-input"
scale

View File

@ -47,6 +47,7 @@ class GoogleCloudSettings extends React.Component {
return (
<>
<TextInput
id="bucket-input"
name={bucket}
className="backup_text-input"
scale
@ -60,6 +61,7 @@ class GoogleCloudSettings extends React.Component {
{isNeedFilePath && (
<TextInput
id="file-path-input"
name={filePath}
className="backup_text-input"
scale

View File

@ -61,6 +61,7 @@ class RackspaceSettings extends React.Component {
return (
<>
<TextInput
id="private-container-input"
name={private_container}
className="backup_text-input"
scale
@ -72,6 +73,7 @@ class RackspaceSettings extends React.Component {
tabIndex={1}
/>
<TextInput
id="public-container-input"
name={public_container}
className="backup_text-input"
scale
@ -83,6 +85,7 @@ class RackspaceSettings extends React.Component {
tabIndex={2}
/>
<TextInput
id="region-input"
name={region}
className="backup_text-input"
scale
@ -95,6 +98,7 @@ class RackspaceSettings extends React.Component {
/>
{isNeedFilePath && (
<TextInput
id="file-path-input"
name={filePath}
className="backup_text-input"
scale

View File

@ -57,6 +57,7 @@ class SelectelSettings extends React.Component {
return (
<>
<TextInput
id="private-container-input"
name={private_container}
className="backup_text-input"
scale={true}
@ -68,6 +69,7 @@ class SelectelSettings extends React.Component {
tabIndex={1}
/>
<TextInput
id="public-container-input"
name={public_container}
className="backup_text-input"
scale={true}
@ -81,6 +83,7 @@ class SelectelSettings extends React.Component {
{isNeedFilePath && (
<TextInput
id="file-path-input"
name={filePath}
className="backup_text-input"
scale

View File

@ -35,6 +35,7 @@ const Backup = ({
</Trans>
<div>
<Link
id="link-tooltip"
as="a"
href={automaticBackupUrl}
target="_blank"
@ -90,11 +91,8 @@ export default inject(({ auth }) => {
const { settingsStore, currentTariffStatusStore } = auth;
const { isNotPaidPeriod } = currentTariffStatusStore;
const {
automaticBackupUrl,
isTabletView,
currentColorScheme,
} = settingsStore;
const { automaticBackupUrl, isTabletView, currentColorScheme } =
settingsStore;
const buttonSize = isTabletView ? "normal" : "small";

View File

@ -278,13 +278,14 @@ class ManualBackup extends React.Component {
<Text isBold fontSize="16px">
{t("DataBackup")}
</Text>
{renderTooltip(t("ManualBackupHelp"))}
{renderTooltip(t("ManualBackupHelp"), "data-backup")}
</div>
<Text className="backup_modules-description">
{t("ManualBackupDescription")}
</Text>
<StyledModules>
<RadioButton
id="temporary-storage"
label={t("TemporaryStorage")}
name={"isCheckedTemporaryStorage"}
key={0}
@ -298,6 +299,7 @@ class ManualBackup extends React.Component {
{isCheckedTemporaryStorage && (
<div className="manual-backup_buttons">
<Button
id="create-button"
label={t("Common:Create")}
onClick={this.onMakeTemporaryBackup}
primary
@ -306,6 +308,7 @@ class ManualBackup extends React.Component {
/>
{temporaryLink?.length > 0 && isMaxProgress && (
<Button
id="download-copy"
label={t("DownloadCopy")}
onClick={this.onClickDownloadBackup}
isDisabled={false}
@ -326,6 +329,7 @@ class ManualBackup extends React.Component {
</StyledModules>
<StyledModules isDisabled={isNotPaidPeriod}>
<RadioButton
id="backup-room"
label={t("RoomsModule")}
name={"isCheckedDocuments"}
key={1}
@ -348,6 +352,7 @@ class ManualBackup extends React.Component {
<StyledModules isDisabled={isNotPaidPeriod}>
<RadioButton
id="third-party-resource"
label={t("ThirdPartyResource")}
name={"isCheckedThirdParty"}
key={2}
@ -362,6 +367,7 @@ class ManualBackup extends React.Component {
</StyledModules>
<StyledModules isDisabled={isNotPaidPeriod}>
<RadioButton
id="third-party-storage"
label={t("Common:ThirdPartyStorage")}
name={"isCheckedThirdPartyStorage"}
key={3}

View File

@ -87,6 +87,7 @@ class RoomsModule extends React.Component {
</div>
<div className="manual-backup_buttons">
<Button
id="create-copy"
label={t("Common:CreateCopy")}
onClick={this.onMakeCopy}
primary

View File

@ -8,11 +8,8 @@ import { ThirdPartyStorages } from "@docspace/common/constants";
class AmazonStorage extends React.Component {
constructor(props) {
super(props);
const {
selectedStorage,
setCompletedFormFields,
storageRegions,
} = this.props;
const { selectedStorage, setCompletedFormFields, storageRegions } =
this.props;
const basicValues = AmazonSettings.formNames(storageRegions[0].systemName);
@ -50,6 +47,7 @@ class AmazonStorage extends React.Component {
<div className="manual-backup_buttons">
<Button
id="create-copy"
label={t("Common:CreateCopy")}
onClick={onMakeCopyIntoStorage}
primary

View File

@ -47,6 +47,7 @@ class GoogleCloudStorage extends React.Component {
<div className="manual-backup_buttons">
<Button
id="create-copy"
label={t("Common:CreateCopy")}
onClick={onMakeCopyIntoStorage}
primary

View File

@ -48,6 +48,7 @@ class RackspaceStorage extends React.Component {
<div className="manual-backup_buttons">
<Button
id="create-copy"
label={t("Common:CreateCopy")}
onClick={onMakeCopyIntoStorage}
primary

View File

@ -47,6 +47,7 @@ class SelectelStorage extends React.Component {
<div className="manual-backup_buttons">
<Button
id="create-copy"
label={t("Common:CreateCopy")}
onClick={onMakeCopyIntoStorage}
primary

View File

@ -142,10 +142,18 @@ const RestoreBackup = (props) => {
fontWeight="400"
className="backup_radio-button"
options={[
{ value: LOCAL_FILE, label: t("LocalFile") },
{ value: BACKUP_ROOM, label: t("RoomsModule") },
{ value: DISK_SPACE, label: t("ThirdPartyResource") },
{ value: STORAGE_SPACE, label: t("Common:ThirdPartyStorage") },
{ id: "local-file", value: LOCAL_FILE, label: t("LocalFile") },
{ id: "backup-room", value: BACKUP_ROOM, label: t("RoomsModule") },
{
id: "third-party-resource",
value: DISK_SPACE,
label: t("ThirdPartyResource"),
},
{
id: "third-party-storage",
value: STORAGE_SPACE,
label: t("Common:ThirdPartyStorage"),
},
]}
onClick={onChangeRadioButton}
selected={radioButtonState}

View File

@ -209,6 +209,7 @@ const BackupListModalDialog = (props) => {
{t("BackupListWarningText")}
</Text>
<Link
id="delete-backups"
onClick={this.onCleanBackupList}
fontWeight={600}
style={{ textDecoration: "underline dotted" }}
@ -269,6 +270,7 @@ const BackupListModalDialog = (props) => {
<div className="restore_dialog-button">
<Button
className="restore"
primary
size="normal"
label={t("Common:Restore")}
@ -276,6 +278,7 @@ const BackupListModalDialog = (props) => {
isDisabled={isCopyingToLocal || !isChecked}
/>
<Button
className="close"
size="normal"
label={t("Common:CloseButton")}
onClick={onModalClose}

View File

@ -39,13 +39,14 @@ const DataManagementWrapper = (props) => {
};
}, []);
const renderTooltip = (helpInfo) => {
const renderTooltip = (helpInfo, className) => {
const isAutoBackupPage = window.location.pathname.includes(
"portal-settings/backup/auto-backup"
);
return (
<>
<HelpButton
className={className}
place="bottom"
iconName={HelpReactSvgUrl}
tooltipContent={
@ -55,6 +56,7 @@ const DataManagementWrapper = (props) => {
</Trans>
<div>
<Link
id="link-tooltip"
as="a"
href={isAutoBackupPage ? automaticBackupUrl : dataBackupUrl}
target="_blank"

View File

@ -63,7 +63,7 @@ const PortalDeactivation = (props) => {
<Text className="helper">{t("PortalDeactivationHelper")}</Text>
<ButtonWrapper>
<Button
className="button"
className="deactivate-button button"
label={t("Deactivate")}
primary
size={isDesktopView ? "small" : "normal"}

View File

@ -75,7 +75,7 @@ const PortalDeletion = (props) => {
<Text className="helper">{t("PortalDeletionHelper")}</Text>
<ButtonWrapper>
<Button
className="button"
className="delete-button button"
label={t("Common:Delete")}
primary
size={isDesktopView ? "small" : "normal"}

View File

@ -261,6 +261,7 @@ const PortalIntegration = (props) => {
<ControlsGroup>
<Label className="label" text={t("FrameId")} />
<TextInput
id="frame-id-input"
scale={true}
onChange={onChangeFrameId}
placeholder={t("EnterId")}
@ -270,6 +271,7 @@ const PortalIntegration = (props) => {
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Width")} />
<TextInput
id="width-input"
scale={true}
onChange={onChangeWidth}
placeholder={t("EnterWidth")}
@ -279,6 +281,7 @@ const PortalIntegration = (props) => {
<ControlsGroup>
<Label className="label" text={t("EmbeddingPanel:Height")} />
<TextInput
id="height-input"
scale={true}
onChange={onChangeHeight}
placeholder={t("EnterHeight")}
@ -286,21 +289,25 @@ const PortalIntegration = (props) => {
/>
</ControlsGroup>
<Checkbox
id="header-checkbox"
label={t("Header")}
onChange={onChangeShowHeader}
isChecked={config.showHeader}
/>
<Checkbox
id="title-checkbox"
label={t("Common:Title")}
onChange={onChangeShowTitle}
isChecked={config.showTitle}
/>
<Checkbox
id="menu-checkbox"
label={t("Menu")}
onChange={onChangeShowArticle}
isChecked={config.showArticle}
/>
<Checkbox
id="filter-checkbox"
label={t("Files:Filter")}
onChange={onChangeShowFilter}
isChecked={config.showFilter}
@ -311,6 +318,7 @@ const PortalIntegration = (props) => {
<ControlsGroup>
<Label className="label" text={t("FolderId")} />
<TextInput
id="folder-id-input"
scale={true}
onChange={onChangeFolderId}
placeholder={t("EnterId")}
@ -320,6 +328,7 @@ const PortalIntegration = (props) => {
<ControlsGroup>
<Label className="label" text={t("ItemsCount")} />
<TextInput
id="items-count-input"
scale={true}
onChange={onChangeCount}
placeholder={t("EnterCount")}
@ -329,6 +338,7 @@ const PortalIntegration = (props) => {
<ControlsGroup>
<Label className="label" text={t("Page")} />
<TextInput
id="enter-page-input"
scale={true}
onChange={onChangePage}
placeholder={t("EnterPage")}
@ -341,12 +351,14 @@ const PortalIntegration = (props) => {
style={{ flexDirection: "row", display: "flex", gap: "16px" }}
>
<TextInput
id="search-term-input"
scale={true}
onChange={onChangeSearch}
placeholder={t("Common:Search")}
value={config.search}
/>
<Checkbox
id="with-subfolders-checkbox"
label={t("Files:WithSubfolders")}
onChange={onChangeWithSubfolders}
isChecked={withSubfolders}
@ -356,6 +368,7 @@ const PortalIntegration = (props) => {
<ControlsGroup>
<Label className="label" text={t("Files:ByAuthor")} />
<TextInput
id="author-input"
scale={true}
onChange={onChangeAuthor}
placeholder={t("Common:EnterName")}
@ -365,6 +378,7 @@ const PortalIntegration = (props) => {
<ControlsGroup>
<Label className="label" text={t("Common:SortBy")} />
<ComboBox
id="sort-by-combo-box"
onSelect={onChangeSortBy}
options={dataSortBy}
scaled={true}
@ -376,6 +390,7 @@ const PortalIntegration = (props) => {
<ControlsGroup>
<Label className="label" text={t("SortOrder")} />
<ComboBox
id="sort-order-combo-box"
onSelect={onChangeSortOrder}
options={dataSortOrder}
scaled={true}
@ -394,12 +409,14 @@ const PortalIntegration = (props) => {
<Buttons>
<Button
id="preview-button"
primary
size="normal"
label={t("Common:Preview")}
onClick={loadFrame}
/>
<Button
id="destroy-button"
primary
size="normal"
label={t("Destroy")}

View File

@ -69,7 +69,13 @@ const DetailsNavigationHeader = (props) => {
<Headline type="content" truncate={true} className="headline">
{t("WebhookDetails")}
</Headline>
<IconButton iconName={RetryIcon} size="17" isFill={true} onClick={handleRetryEvent} />
<IconButton
className="retry"
iconName={RetryIcon}
size="17"
isFill={true}
onClick={handleRetryEvent}
/>
<NoBoxShadowToast />
</HeaderContainer>

View File

@ -75,6 +75,7 @@ const RequestDetails = ({ eventDetails }) => {
<Textarea isDisabled />
) : (
<Textarea
classNameCopyIcon="request-header-copy"
value={eventDetails.requestHeaders}
enableCopy
hasNumeration
@ -89,6 +90,7 @@ const RequestDetails = ({ eventDetails }) => {
</Text>
{isJSON(eventDetails.requestPayload) ? (
<Textarea
classNameCopyIcon="request-body-copy"
value={eventDetails.requestPayload}
isJSONField
enableCopy
@ -97,7 +99,11 @@ const RequestDetails = ({ eventDetails }) => {
copyInfoText={t("RequestBodyCopied")}
/>
) : (
<Textarea value={eventDetails.requestPayload} heightScale className="textareaBody" />
<Textarea
value={eventDetails.requestPayload}
heightScale
className="textareaBody"
/>
)}
</DetailsWrapper>
);

View File

@ -79,7 +79,9 @@ const ResponseDetails = ({ eventDetails }) => {
const openRawPayload = () => {
const rawPayload = window.open("");
isJSON(responsePayload)
? rawPayload.document.write(beautifiedJSON.replace(/(?:\r\n|\r|\n)/g, "<br/>"))
? rawPayload.document.write(
beautifiedJSON.replace(/(?:\r\n|\r|\n)/g, "<br/>")
)
: rawPayload.document.write(responsePayload);
rawPayload.focus();
};
@ -91,6 +93,7 @@ const ResponseDetails = ({ eventDetails }) => {
</Text>
{isJSON(eventDetails.responseHeaders) ? (
<Textarea
classNameCopyIcon="response-header-copy"
value={eventDetails.responseHeaders}
enableCopy
hasNumeration
@ -99,7 +102,11 @@ const ResponseDetails = ({ eventDetails }) => {
copyInfoText={t("ResponseHeaderCopied")}
/>
) : (
<Textarea value={eventDetails.responseHeaders} heightScale className="textareaBody" />
<Textarea
value={eventDetails.responseHeaders}
heightScale
className="textareaBody"
/>
)}
<Text as="h3" fontWeight={600} className="mb-4 mt-16">
{t("ResponsePostBody")}
@ -110,6 +117,7 @@ const ResponseDetails = ({ eventDetails }) => {
{t("PayloadIsTooLarge")}
</Text>
<Button
className="view-raw-payload"
size="small"
onClick={openRawPayload}
label={t("ViewRawPayload")}
@ -120,6 +128,7 @@ const ResponseDetails = ({ eventDetails }) => {
<Textarea isDisabled />
) : isJSON(responsePayload) ? (
<Textarea
classNameCopyIcon="response-body-copy"
value={responsePayload}
isJSONField
enableCopy
@ -129,6 +138,7 @@ const ResponseDetails = ({ eventDetails }) => {
/>
) : (
<Textarea
classNameCopyIcon="response-body-copy"
value={responsePayload}
enableCopy
heightScale

View File

@ -119,7 +119,7 @@ const DeliveryDatePicker = ({ filters, setFilters, isApplied, setIsApplied }) =>
return (
<div>
<SelectedItem
className="selectedItem"
className="selectedItem delete-delivery-date-button"
onClose={deleteSelectedDate}
label={filters.deliveryDate.format("DD MMM YYYY") + formattedTime}
onClick={toggleCalendar}
@ -134,7 +134,6 @@ const DeliveryDatePicker = ({ filters, setFilters, isApplied, setIsApplied }) =>
!calendarRef?.current?.contains(e.target) &&
setIsCalendarOpen(false);
};
const isEqualDates = (firstDate, secondDate) => {
return firstDate.format("YYYY-MM-D HH:mm") === secondDate.format("YYYY-MM-D HH:mm");
};
@ -176,13 +175,19 @@ const DeliveryDatePicker = ({ filters, setFilters, isApplied, setIsApplied }) =>
<Text isInline fontWeight={600} color="#A3A9AE" className="mr-8">
{t("From")}
</Text>
<TimePicker onChange={setDeliveryFrom} hasError={!isTimeValid} tabIndex={1} />
<TimePicker
classNameInput="from-time"
onChange={setDeliveryFrom}
hasError={!isTimeValid}
tabIndex={1}
/>
</span>
<Text isInline fontWeight={600} color="#A3A9AE" className="mr-8">
{t("Before")}
</Text>
<TimePicker
classNameInput="before-time"
date={filters.deliveryTo}
setDate={setDeliveryTo}
hasError={!isTimeValid}
@ -191,7 +196,11 @@ const DeliveryDatePicker = ({ filters, setFilters, isApplied, setIsApplied }) =>
</TimePickerCell>
) : (
<TimePickerCell>
<SelectorAddButton title={t("Add")} onClick={showTimePicker} className="mr-8" />
<SelectorAddButton
title={t("Add")}
onClick={showTimePicker}
className="mr-8 add-delivery-time-button"
/>
<Text isInline fontWeight={600} color="#A3A9AE">
{t("SelectDeliveryTime")}
</Text>

View File

@ -23,10 +23,15 @@ const Selectors = styled.div`
margin-bottom: 16px;
`;
const StatusBadgeSelector = ({ label, statusCode, isStatusSelected, handleStatusClick }) => {
const StatusBadgeSelector = ({ label, statusCode, isStatusSelected, handleStatusClick, id }) => {
const handleOnClick = () => handleStatusClick(statusCode);
return (
<RoundedButton label={label} onClick={handleOnClick} primary={isStatusSelected(statusCode)} />
<RoundedButton
id={id}
label={label}
onClick={handleOnClick}
primary={isStatusSelected(statusCode)}
/>
);
};
@ -50,6 +55,7 @@ const StatusPicker = ({ filters, setFilters }) => {
const StatusBadgeElements = StatusCodes.map((code) =>
code === "Not sent" ? (
<StatusBadgeSelector
id="not-sent"
label={t("NotSent")}
statusCode={code}
isStatusSelected={isStatusSelected}
@ -58,13 +64,14 @@ const StatusPicker = ({ filters, setFilters }) => {
/>
) : (
<StatusBadgeSelector
id={code}
label={code}
statusCode={code}
isStatusSelected={isStatusSelected}
handleStatusClick={handleStatusClick}
key={code}
/>
),
)
);
return (

View File

@ -54,8 +54,14 @@ function areArraysEqual(array1, array2) {
}
const FilterDialog = (props) => {
const { visible, closeModal, applyFilters, formatFilters, setHistoryFilters, historyFilters } =
props;
const {
visible,
closeModal,
applyFilters,
formatFilters,
setHistoryFilters,
historyFilters,
} = props;
const { t } = useTranslation(["Webhooks", "Files", "Common"]);
const { id } = useParams();
const navigate = useNavigate();
@ -130,12 +136,18 @@ const FilterDialog = (props) => {
<ModalDialog.Footer>
<Footer>
<Button
className="apply-button"
label={t("Common:ApplyButton")}
size="normal"
primary={true}
onClick={handleApplyFilters}
/>
<Button label={t("Common:CancelButton")} size="normal" onClick={closeModal} />
<Button
className="cancel-button"
label={t("Common:CancelButton")}
size="normal"
onClick={closeModal}
/>
</Footer>
</ModalDialog.Footer>
)}

View File

@ -54,7 +54,8 @@ const FilterButton = styled.div`
z-index: ${(props) => (props.isGroupMenuVisible ? 199 : 201)};
border: 1px solid;
border-color: ${(props) => (props.theme.isBase ? "#d0d5da" : "rgb(71, 71, 71)")};
border-color: ${(props) =>
props.theme.isBase ? "#d0d5da" : "rgb(71, 71, 71)"};
border-radius: 3px;
cursor: pointer;
@ -107,7 +108,11 @@ const HistoryFilterHeader = (props) => {
{t("Webhook")} {id}
</ListHeading>
<FilterButton onClick={openFiltersModal} isGroupMenuVisible={isGroupMenuVisible}>
<FilterButton
id="filter-button"
onClick={openFiltersModal}
isGroupMenuVisible={isGroupMenuVisible}
>
<IconButton iconName={FilterReactSvrUrl} size={16} />
<span hidden={historyFilters === null}></span>
</FilterButton>

View File

@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import styled, { css } from "styled-components";
import { useNavigate } from "react-router-dom";
import { inject, observer } from "mobx-react";
@ -8,7 +8,7 @@ import RetryIcon from "PUBLIC_DIR/images/refresh.react.svg?url";
import Headline from "@docspace/common/components/Headline";
import IconButton from "@docspace/components/icon-button";
import { Hint } from "../../styled-components";
// import { Hint } from "../../styled-components";
import { tablet } from "@docspace/components/utils/device";
@ -20,7 +20,7 @@ import toastr from "@docspace/components/toast/toastr";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { showLoader, hideLoader } from "@docspace/common/utils";
import FloatingButton from "@docspace/components/floating-button";
const HeaderContainer = styled.div`
position: sticky;
@ -78,6 +78,14 @@ const HeaderContainer = styled.div`
width: calc(100% + 40px);
height: 69px;
.combo-button_selected-icon {
svg {
path {
fill: ${(props) => (props.isDisabled ? "#d0d5da" : "#333")};
}
}
}
${() =>
isMobile &&
css`
@ -109,6 +117,9 @@ const HistoryHeader = (props) => {
theme,
historyFilters,
formatFilters,
isRetryPending,
setRetryPendingFalse,
setRetryPendingTrue,
} = props;
const navigate = useNavigate();
const onBack = () => {
@ -117,28 +128,37 @@ const HistoryHeader = (props) => {
const { t } = useTranslation(["Webhooks", "Common", "InfoPanel"]);
const { id } = useParams();
const [isPendingVisible, setIsPendingVisible] = useState(false);
const handleGroupSelection = (isChecked) => {
isChecked ? checkAllIds() : emptyCheckedIds();
};
const handleRetryAll = async () => {
try {
setRetryPendingTrue();
const timeout = setTimeout(() => {
setIsPendingVisible(true);
}, 300);
await retryWebhookEvents(checkedEventIds);
await emptyCheckedIds();
const tempIds = checkedEventIds;
showLoader();
await retryWebhookEvents(tempIds);
hideLoader();
clearTimeout(timeout);
setRetryPendingFalse();
setIsPendingVisible(false);
await fetchHistoryItems({
...(historyFilters ? formatFilters(historyFilters) : {}),
configId: id,
});
toastr.success(
`${t("WebhookRedilivered")}: ${checkedEventIds.length}`,
<b>{t("Common:Done")}</b>,
<b>{t("Common:Done")}</b>
);
} catch (error) {
console.log(error);
toastr.error(error);
} finally {
setRetryPendingFalse();
setIsPendingVisible(false);
}
};
@ -151,7 +171,8 @@ const HistoryHeader = (props) => {
},
];
const onKeyPress = (e) => (e.key === "Esc" || e.key === "Escape") && emptyCheckedIds();
const onKeyPress = (e) =>
(e.key === "Esc" || e.key === "Escape") && emptyCheckedIds();
useEffect(() => {
window.addEventListener("keyup", onKeyPress);
@ -161,12 +182,14 @@ const HistoryHeader = (props) => {
const menuItems = (
<>
<DropDownItem
id="select-all"
key="select-all-event-ids"
label={t("Common:SelectAll")}
data-index={0}
onClick={checkAllIds}
/>
<DropDownItem
id="unselect-all"
key="unselect-all-event-ids"
label={t("UnselectAll")}
data-index={1}
@ -203,6 +226,7 @@ const HistoryHeader = (props) => {
isChecked={areAllIdsChecked}
isIndeterminate={isIndeterminate}
withoutInfoPanelToggler
isBlocked={isRetryPending}
/>
);
@ -211,7 +235,7 @@ const HistoryHeader = (props) => {
}, []);
return (
<HeaderContainer>
<HeaderContainer isDisabled={isRetryPending}>
{isMobileOnly ? (
<>
{isGroupMenuVisible && <GroupMenu />}
@ -222,6 +246,8 @@ const HistoryHeader = (props) => {
) : (
<NavigationHeader />
)}
{isPendingVisible && <FloatingButton icon="refresh" />}
</HeaderContainer>
);
};
@ -238,6 +264,9 @@ export default inject(({ webhooksStore, auth }) => {
fetchHistoryItems,
historyFilters,
formatFilters,
isRetryPending,
setRetryPendingFalse,
setRetryPendingTrue,
} = webhooksStore;
const { settingsStore } = auth;
@ -256,5 +285,8 @@ export default inject(({ webhooksStore, auth }) => {
theme,
historyFilters,
formatFilters,
isRetryPending,
setRetryPendingFalse,
setRetryPendingTrue,
};
})(observer(HistoryHeader));

View File

@ -23,12 +23,14 @@ const HistoryRow = (props) => {
fetchHistoryItems,
historyFilters,
formatFilters,
isRetryPending,
} = props;
const { t } = useTranslation(["Webhooks", "Common"]);
const navigate = useNavigate();
const { id } = useParams();
const redirectToDetails = () => navigate(window.location.pathname + `/${historyItem.id}`);
const redirectToDetails = () =>
navigate(window.location.pathname + `/${historyItem.id}`);
const handleRetryEvent = async () => {
await retryWebhookEvent(historyItem.id);
await fetchHistoryItems({
@ -54,16 +56,19 @@ const HistoryRow = (props) => {
const contextOptions = [
{
id: "webhook-details",
key: "Webhook details dropdownItem",
label: t("WebhookDetails"),
icon: InfoIcon,
onClick: redirectToDetails,
},
{
id: "retry",
key: "Retry dropdownItem",
label: t("Retry"),
icon: RetryIcon,
onClick: handleRetryEvent,
disabled: isRetryPending,
},
];
@ -75,9 +80,15 @@ const HistoryRow = (props) => {
checkbox
checked={isIdChecked(historyItem.id)}
onSelect={handleOnSelect}
className={isIdChecked(historyItem.id) ? "row-item selected-row-item" : "row-item "}
onClick={handleRowClick}>
<HistoryRowContent sectionWidth={sectionWidth} historyItem={historyItem} />
className={
isIdChecked(historyItem.id) ? "row-item selected-row-item" : "row-item "
}
onClick={handleRowClick}
>
<HistoryRowContent
sectionWidth={sectionWidth}
historyItem={historyItem}
/>
</Row>
);
};
@ -90,6 +101,7 @@ export default inject(({ webhooksStore }) => {
fetchHistoryItems,
historyFilters,
formatFilters,
isRetryPending,
} = webhooksStore;
return {
@ -99,5 +111,6 @@ export default inject(({ webhooksStore }) => {
fetchHistoryItems,
historyFilters,
formatFilters,
isRetryPending,
};
})(observer(HistoryRow));

View File

@ -25,6 +25,17 @@ const StyledTableRow = styled(TableRow)`
text-overflow: ellipsis;
}
.p-menuitem-icon {
svg {
path {
fill: red;
}
}
}
.p-menuitem-text {
color: red;
}
${(props) =>
props.isHighlight &&
css`
@ -48,13 +59,18 @@ const HistoryTableRow = (props) => {
fetchHistoryItems,
historyFilters,
formatFilters,
isRetryPending,
} = props;
const { t } = useTranslation(["Webhooks", "Common"]);
const navigate = useNavigate();
const { id } = useParams();
const redirectToDetails = () => navigate(window.location.pathname + `/${item.id}`);
const redirectToDetails = () =>
navigate(window.location.pathname + `/${item.id}`);
const handleRetryEvent = async () => {
if (isRetryPending) {
return;
}
await retryWebhookEvent(item.id);
await fetchHistoryItems({
...(historyFilters ? formatFilters(historyFilters) : {}),
@ -65,20 +81,24 @@ const HistoryTableRow = (props) => {
const contextOptions = [
{
id: "webhook-details",
key: "Webhook details dropdownItem",
label: t("WebhookDetails"),
icon: InfoIcon,
onClick: redirectToDetails,
},
{
id: "retry",
key: "Retry dropdownItem",
label: t("Retry"),
icon: RetryIcon,
onClick: handleRetryEvent,
disabled: isRetryPending,
},
];
const formattedDelivery = moment(item.delivery).format("MMM D, YYYY, h:mm:ss A") + " UTC";
const formattedDelivery =
moment(item.delivery).format("MMM D, YYYY, h:mm:ss A") + " UTC";
const onChange = (e) => {
if (
@ -96,11 +116,22 @@ const HistoryTableRow = (props) => {
const isChecked = isIdChecked(item.id);
return (
<StyledWrapper className={isChecked ? "selected-table-row" : ""} onClick={onChange}>
<StyledTableRow contextOptions={contextOptions} checked={isChecked} hideColumns={hideColumns}>
<StyledWrapper
className={isChecked ? "selected-table-row" : ""}
onClick={onChange}
>
<StyledTableRow
contextOptions={contextOptions}
checked={isChecked}
hideColumns={hideColumns}
>
<TableCell>
<TableCell checked={isChecked} className="checkboxWrapper">
<Checkbox onChange={onChange} isChecked={isChecked} />
<Checkbox
className="checkbox"
onChange={onChange}
isChecked={isChecked}
/>
</TableCell>
<Text fontWeight={600}>{item.id}</Text>
@ -126,6 +157,7 @@ export default inject(({ webhooksStore }) => {
fetchHistoryItems,
historyFilters,
formatFilters,
isRetryPending,
} = webhooksStore;
return {
@ -135,5 +167,6 @@ export default inject(({ webhooksStore }) => {
fetchHistoryItems,
historyFilters,
formatFilters,
isRetryPending,
};
})(observer(HistoryTableRow));

View File

@ -111,11 +111,20 @@ const Webhooks = (props) => {
/>
</ButtonSeating>
) : (
<Button label={t("CreateWebhook")} primary size={"small"} onClick={openCreateModal} />
<Button
id="create-webhook-button"
label={t("CreateWebhook")}
primary
size={"small"}
onClick={openCreateModal}
/>
)}
{!isWebhooksEmpty && (
<WebhooksTable openSettingsModal={openSettingsModal} openDeleteModal={openDeleteModal} />
<WebhooksTable
openSettingsModal={openSettingsModal}
openDeleteModal={openDeleteModal}
/>
)}
<WebhookDialog
visible={isCreateOpened}

View File

@ -20,8 +20,14 @@ const Footer = styled.div`
}
`;
export const DeleteWebhookDialog = ({ visible, onClose, header, handleSubmit }) => {
const onKeyPress = (e) => (e.key === "Esc" || e.key === "Escape") && onClose();
export const DeleteWebhookDialog = ({
visible,
onClose,
header,
handleSubmit,
}) => {
const onKeyPress = (e) =>
(e.key === "Esc" || e.key === "Escape") && onClose();
const { t } = useTranslation(["Webhooks", "Common", "EmptyTrashDialog"]);
@ -47,12 +53,18 @@ export const DeleteWebhookDialog = ({ visible, onClose, header, handleSubmit })
<ModalDialog.Footer>
<Footer>
<Button
id="delete-forever-button"
label={t("EmptyTrashDialog:DeleteForeverButton")}
size="normal"
primary={true}
onClick={handleDeleteClick}
/>
<Button label={t("Common:CancelButton")} size="normal" onClick={onClose} />
<Button
id="cancel-button"
label={t("Common:CancelButton")}
size="normal"
onClick={onClose}
/>
</Footer>
</ModalDialog.Footer>
</ModalDialog>

View File

@ -26,10 +26,12 @@ export const LabledInput = ({
hasError,
className,
required = false,
id,
}) => {
return (
<StyledLabel text={label} className={className}>
<TextInput
id={id}
name={name}
placeholder={placeholder}
tabIndex={1}

View File

@ -33,18 +33,27 @@ export const SSLVerification = ({ onChange, value }) => {
const { t } = useTranslation(["Webhooks"]);
const handleOnChange = (e) => {
onChange({ target: { name: e.target.name, value: e.target.value === "true" } });
onChange({
target: { name: e.target.name, value: e.target.value === "true" },
});
};
const toggleHint = () => setIsHintVisible((prevIsHintVisible) => !prevIsHintVisible);
const toggleHint = () =>
setIsHintVisible((prevIsHintVisible) => !prevIsHintVisible);
return (
<Label
text={
<Header>
{t("SSLVerification")}{" "}
<StyledInfoIcon src={InfoIcon} alt="infoIcon" onClick={toggleHint} />
<StyledInfoIcon
className="ssl-verification-tooltip"
src={InfoIcon}
alt="infoIcon"
onClick={toggleHint}
/>
</Header>
}>
}
>
<Hint isTooltip hidden={!isHintVisible} onClick={toggleHint}>
{t("SSLHint")}
</Hint>
@ -55,10 +64,12 @@ export const SSLVerification = ({ onChange, value }) => {
onClick={handleOnChange}
options={[
{
id: "enable-ssl",
label: t("EnableSSL"),
value: "true",
},
{
id: "disable-ssl",
label: t("DisableSSL"),
value: "false",
},

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