Merge branch 'develop' into feature/webhooks-page

This commit is contained in:
Vladimir Khvan 2023-03-07 00:41:32 +05:00
commit b2546b1c96
302 changed files with 7282 additions and 6906 deletions

View File

@ -12,4 +12,4 @@ Write-Host "Run Document server" -ForegroundColor Green
$DOCUMENT_SERVER_IMAGE_NAME = "onlyoffice/documentserver-de:latest" $DOCUMENT_SERVER_IMAGE_NAME = "onlyoffice/documentserver-de:latest"
docker run -i -t -d -p 8085:80 -e JWT_ENABLED=false -e JWT_IN_BODY=false --restart=always -v $RootDir/Data:/var/www/onlyoffice/Data $DOCUMENT_SERVER_IMAGE_NAME docker run -i -t -d -p 8085:80 -e JWT_ENABLED=true -e JWT_SECRET=secret -e JWT_HEADER=AuthorizationJwt --restart=always -v $RootDir/Data:/var/www/onlyoffice/Data $DOCUMENT_SERVER_IMAGE_NAME

View File

@ -186,7 +186,7 @@ WORKDIR ${BUILD_PATH}/services/ASC.Data.Backup.BackgroundTasks/
COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py
COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Data.Backup.BackgroundTasks/service/ . COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Data.Backup.BackgroundTasks/service/ .
CMD ["ASC.Data.Backup.BackgroundTasks.dll", "ASC.Data.Backup.BackgroundTasks"] CMD ["ASC.Data.Backup.BackgroundTasks.dll", "ASC.Data.Backup.BackgroundTasks", "core:eventBus:subscriptionClientName=asc_event_bus_backup_queue"]
# ASC.ApiSystem ## # ASC.ApiSystem ##
FROM dotnetrun AS api_system FROM dotnetrun AS api_system
@ -231,7 +231,7 @@ WORKDIR ${BUILD_PATH}/products/ASC.Files/service/
COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py
COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Files.Service/service/ . COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Files.Service/service/ .
CMD ["ASC.Files.Service.dll", "ASC.Files.Service"] CMD ["ASC.Files.Service.dll", "ASC.Files.Service", "core:eventBus:subscriptionClientName=asc_event_bus_files_service_queue"]
## ASC.Notify ## ## ASC.Notify ##
FROM dotnetrun AS notify FROM dotnetrun AS notify
@ -240,7 +240,7 @@ WORKDIR ${BUILD_PATH}/services/ASC.Notify/service
COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py
COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Notify/service/ . COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Notify/service/ .
CMD ["ASC.Notify.dll", "ASC.Notify"] CMD ["ASC.Notify.dll", "ASC.Notify", "core:eventBus:subscriptionClientName=asc_event_bus_notify_queue"]
## ASC.People ## ## ASC.People ##
FROM dotnetrun AS people_server FROM dotnetrun AS people_server

View File

@ -38,8 +38,9 @@ ELK_PORT = os.environ["ELK_PORT"] if environ.get("ELK_PORT") else "9200"
ELK_THREADS = os.environ["ELK_THREADS"] if environ.get("ELK_THREADS") else "1" ELK_THREADS = os.environ["ELK_THREADS"] if environ.get("ELK_THREADS") else "1"
KAFKA_HOST = os.environ["KAFKA_HOST"] if environ.get("KAFKA_HOST") else "kafka:9092" KAFKA_HOST = os.environ["KAFKA_HOST"] if environ.get("KAFKA_HOST") else "kafka:9092"
RUN_FILE = sys.argv[1] if sys.argv[1] else "none" RUN_FILE = sys.argv[1] if (len(sys.argv) > 1) else "none"
LOG_FILE = sys.argv[2] if sys.argv[2] else "none" LOG_FILE = sys.argv[2] if (len(sys.argv) > 2) else "none"
CORE_EVENT_BUS = sys.argv[3] if (len(sys.argv) > 3) else ""
REDIS_HOST = os.environ["REDIS_HOST"] if environ.get("REDIS_HOST") else "onlyoffice-redis" REDIS_HOST = os.environ["REDIS_HOST"] if environ.get("REDIS_HOST") else "onlyoffice-redis"
REDIS_PORT = os.environ["REDIS_PORT"] if environ.get("REDIS_PORT") else "6379" REDIS_PORT = os.environ["REDIS_PORT"] if environ.get("REDIS_PORT") else "6379"
@ -84,7 +85,8 @@ class RunServices:
" --log:dir=" + LOG_DIR +\ " --log:dir=" + LOG_DIR +\
" --log:name=" + LOG_FILE +\ " --log:name=" + LOG_FILE +\
" core:products:folder=/var/www/products/" +\ " core:products:folder=/var/www/products/" +\
" core:products:subfolder=server") " core:products:subfolder=server" + " " +\
CORE_EVENT_BUS)
else: else:
os.system("dotnet " + RUN_FILE + " --urls=" + URLS + self.SERVICE_PORT +\ os.system("dotnet " + RUN_FILE + " --urls=" + URLS + self.SERVICE_PORT +\
" --\'$STORAGE_ROOT\'=" + APP_STORAGE_ROOT +\ " --\'$STORAGE_ROOT\'=" + APP_STORAGE_ROOT +\
@ -93,7 +95,8 @@ class RunServices:
" --log:name=" + LOG_FILE +\ " --log:name=" + LOG_FILE +\
" --ENVIRONMENT=" + ENV_EXTENSION +\ " --ENVIRONMENT=" + ENV_EXTENSION +\
" core:products:folder=/var/www/products/" +\ " core:products:folder=/var/www/products/" +\
" core:products:subfolder=server") " core:products:subfolder=server" + " " +\
CORE_EVENT_BUS)
def openJsonFile(filePath): def openJsonFile(filePath):
try: try:

View File

@ -57,13 +57,13 @@ public class LdapNotifyService : BackgroundService
{ {
var tId = t.Id; var tId = t.Id;
var ldapSettings = settingsManager.LoadForTenant<LdapSettings>(tId); var ldapSettings = settingsManager.Load<LdapSettings>(tId);
if (!ldapSettings.EnableLdapAuthentication) if (!ldapSettings.EnableLdapAuthentication)
{ {
continue; continue;
} }
var cronSettings = settingsManager.LoadForTenant<LdapCronSettings>(tId); var cronSettings = settingsManager.Load<LdapCronSettings>(tId);
if (string.IsNullOrEmpty(cronSettings.Cron)) if (string.IsNullOrEmpty(cronSettings.Cron))
{ {
continue; continue;
@ -103,13 +103,13 @@ public class LdapNotifyService : BackgroundService
{ {
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
var settingsManager = scope.ServiceProvider.GetRequiredService<SettingsManager>(); var settingsManager = scope.ServiceProvider.GetRequiredService<SettingsManager>();
var ldapSettings = settingsManager.LoadForTenant<LdapSettings>(tenant.Id); var ldapSettings = settingsManager.Load<LdapSettings>(tenant.Id);
if (!ldapSettings.EnableLdapAuthentication) if (!ldapSettings.EnableLdapAuthentication)
{ {
var cronSettings = settingsManager.LoadForTenant<LdapCronSettings>(tenant.Id); var cronSettings = settingsManager.Load<LdapCronSettings>(tenant.Id);
cronSettings.Cron = ""; cronSettings.Cron = "";
settingsManager.SaveForTenant(cronSettings, tenant.Id); settingsManager.Save(cronSettings, tenant.Id);
UnregisterAutoSync(tenant); UnregisterAutoSync(tenant);
return; return;
} }

View File

@ -151,7 +151,7 @@ public class LdapUserManager
var quotaSettings = _settingsManager.Load<TenantUserQuotaSettings>(); var quotaSettings = _settingsManager.Load<TenantUserQuotaSettings>();
if (quotaSettings.EnableUserQuota) if (quotaSettings.EnableUserQuota)
{ {
_settingsManager.SaveForUser(new UserQuotaSettings { UserQuota = ldapUserInfo.LdapQouta }, ldapUserInfo.Id); _settingsManager.Save(new UserQuotaSettings { UserQuota = ldapUserInfo.LdapQouta }, ldapUserInfo.Id);
} }

View File

@ -70,19 +70,20 @@ public abstract class BaseStartup
services.AddHttpClient(); services.AddHttpClient();
services.AddScoped<EFLoggerFactory>(); services.AddScoped<EFLoggerFactory>();
services.AddBaseDbContextPool<AccountLinkContext>();
services.AddBaseDbContextPool<CoreDbContext>(); services.AddBaseDbContextPool<AccountLinkContext>()
services.AddBaseDbContextPool<TenantDbContext>(); .AddBaseDbContextPool<CoreDbContext>()
services.AddBaseDbContextPool<UserDbContext>(); .AddBaseDbContextPool<TenantDbContext>()
services.AddBaseDbContextPool<TelegramDbContext>(); .AddBaseDbContextPool<UserDbContext>()
services.AddBaseDbContextPool<FirebaseDbContext>(); .AddBaseDbContextPool<TelegramDbContext>()
services.AddBaseDbContextPool<CustomDbContext>(); .AddBaseDbContextPool<FirebaseDbContext>()
services.AddBaseDbContextPool<WebstudioDbContext>(); .AddBaseDbContextPool<CustomDbContext>()
services.AddBaseDbContextPool<InstanceRegistrationContext>(); .AddBaseDbContextPool<WebstudioDbContext>()
services.AddBaseDbContextPool<IntegrationEventLogContext>(); .AddBaseDbContextPool<InstanceRegistrationContext>()
services.AddBaseDbContextPool<FeedDbContext>(); .AddBaseDbContextPool<IntegrationEventLogContext>()
services.AddBaseDbContextPool<MessagesContext>(); .AddBaseDbContextPool<FeedDbContext>()
services.AddBaseDbContextPool<WebhooksDbContext>(); .AddBaseDbContextPool<MessagesContext>()
.AddBaseDbContextPool<WebhooksDbContext>();
if (AddAndUseSession) if (AddAndUseSession)
{ {
@ -296,18 +297,33 @@ public abstract class BaseStartup
app.UseEndpoints(async endpoints => app.UseEndpoints(async endpoints =>
{ {
await endpoints.MapCustom(WebhooksEnabled, app.ApplicationServices); await endpoints.MapCustomAsync(WebhooksEnabled, app.ApplicationServices);
endpoints.MapHealthChecks("/health", new HealthCheckOptions() endpoints.MapHealthChecks("/health", new HealthCheckOptions()
{ {
Predicate = _ => true, Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
}); });
endpoints.MapHealthChecks("/ready", new HealthCheckOptions
{
Predicate = r => r.Name.Contains("services")
});
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
{ {
Predicate = r => r.Name.Contains("self") Predicate = r => r.Name.Contains("self")
}); });
}); });
app.Map("/switch", appBuilder =>
{
appBuilder.Run(async context =>
{
CustomHealthCheck.Running = !CustomHealthCheck.Running;
await context.Response.WriteAsync($"{Environment.MachineName} running {CustomHealthCheck.Running}");
});
});
} }
public void ConfigureContainer(ContainerBuilder builder) public void ConfigureContainer(ContainerBuilder builder)

View File

@ -83,7 +83,7 @@ public static class EndpointExtension
"DELETE" "DELETE"
}; };
public static async Task<IEndpointRouteBuilder> MapCustom(this IEndpointRouteBuilder endpoints, bool webhooksEnabled = false, IServiceProvider serviceProvider = null) public static async Task<IEndpointRouteBuilder> MapCustomAsync(this IEndpointRouteBuilder endpoints, bool webhooksEnabled = false, IServiceProvider serviceProvider = null)
{ {
endpoints.MapControllers(); endpoints.MapControllers();

View File

@ -28,62 +28,30 @@ namespace ASC.Api.Core.Core;
public static class CustomHealthCheck public static class CustomHealthCheck
{ {
public static bool Running { get; set;}
static CustomHealthCheck()
{
Running = true;
}
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration)
{ {
var hcBuilder = services.AddHealthChecks(); var hcBuilder = services.AddHealthChecks();
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); hcBuilder.AddCheck("self", () => Running ? HealthCheckResult.Healthy()
: HealthCheckResult.Unhealthy())
.AddDatabase(configuration)
.AddDistibutedCache(configuration)
.AddMessageQueue(configuration)
.AddSearch(configuration);
var configurationExtension = new ConfigurationExtension(configuration); return services;
var connectionString = configurationExtension.GetConnectionStrings("default");
if (string.Equals(connectionString.ProviderName, "MySql.Data.MySqlClient"))
{
hcBuilder.AddMySql(connectionString.ConnectionString,
name: "mysqldb",
tags: new string[] { "mysqldb" },
timeout: new TimeSpan(0, 0, 15));
} }
if (string.Equals(connectionString.ProviderName, "Npgsql")) public static IHealthChecksBuilder AddDistibutedCache(
this IHealthChecksBuilder hcBuilder, IConfiguration configuration)
{ {
hcBuilder.AddNpgSql(connectionString.ConnectionString,
name: "postgredb",
tags: new string[] { "postgredb" },
timeout: new TimeSpan(0, 0, 15));
}
var kafkaSettings = configurationExtension.GetSetting<KafkaSettings>("kafka");
if (kafkaSettings != null && !string.IsNullOrEmpty(kafkaSettings.BootstrapServers))
{
var clientConfig = new ClientConfig { BootstrapServers = kafkaSettings.BootstrapServers };
hcBuilder.AddKafka(new ProducerConfig(clientConfig),
name: "kafka",
tags: new string[] { "kafka" },
timeout: new TimeSpan(0,0,15)
);
}
var elasticSettings = configuration.GetSection("elastic");
if (elasticSettings != null && elasticSettings.GetChildren().Any())
{
var host = elasticSettings.GetSection("Host").Value ?? "localhost";
var scheme = elasticSettings.GetSection("Scheme").Value ?? "http";
var port = elasticSettings.GetSection("Port").Value ?? "9200";
var elasticSearchUri = $"{scheme}://{host}:{port}";
if (Uri.IsWellFormedUriString(elasticSearchUri, UriKind.Absolute))
{
hcBuilder.AddElasticsearch(elasticSearchUri,
name: "elasticsearch",
tags: new string[] { "elasticsearch" });
}
}
var redisConfiguration = configuration.GetSection("Redis").Get<RedisConfiguration>(); var redisConfiguration = configuration.GetSection("Redis").Get<RedisConfiguration>();
if (redisConfiguration != null) if (redisConfiguration != null)
@ -97,20 +65,97 @@ public static class CustomHealthCheck
hcBuilder.AddRedis(redisConfiguration.ConfigurationOptions.ToString(), hcBuilder.AddRedis(redisConfiguration.ConfigurationOptions.ToString(),
name: "redis", name: "redis",
tags: new string[] { "redis" }, tags: new string[] { "redis", "services" },
timeout: new TimeSpan(0, 0, 15)); timeout: new TimeSpan(0, 0, 15));
} }
return hcBuilder;
}
public static IHealthChecksBuilder AddSearch(
this IHealthChecksBuilder hcBuilder, IConfiguration configuration)
{
var elasticSettings = configuration.GetSection("elastic");
if (elasticSettings != null && elasticSettings.GetChildren().Any())
{
var host = elasticSettings.GetSection("Host").Value ?? "localhost";
var scheme = elasticSettings.GetSection("Scheme").Value ?? "http";
var port = elasticSettings.GetSection("Port").Value ?? "9200";
var elasticSearchUri = $"{scheme}://{host}:{port}";
if (Uri.IsWellFormedUriString(elasticSearchUri, UriKind.Absolute))
{
hcBuilder.AddElasticsearch(elasticSearchUri,
name: "elasticsearch",
tags: new string[] { "elasticsearch", "services" },
timeout: new TimeSpan(0, 0, 15));
}
}
return hcBuilder;
}
public static IHealthChecksBuilder AddDatabase(
this IHealthChecksBuilder hcBuilder, IConfiguration configuration)
{
var configurationExtension = new ConfigurationExtension(configuration);
var connectionString = configurationExtension.GetConnectionStrings("default");
if (string.Equals(connectionString.ProviderName, "MySql.Data.MySqlClient"))
{
hcBuilder.AddMySql(connectionString.ConnectionString,
name: "mysqldb",
tags: new string[] { "mysqldb", "services" },
timeout: new TimeSpan(0, 0, 15));
}
else if (string.Equals(connectionString.ProviderName, "Npgsql"))
{
hcBuilder.AddNpgSql(connectionString.ConnectionString,
name: "postgredb",
tags: new string[] { "postgredb", "services" },
timeout: new TimeSpan(0, 0, 15));
}
return hcBuilder;
}
public static IHealthChecksBuilder AddMessageQueue(
this IHealthChecksBuilder hcBuilder, IConfiguration configuration)
{
var rabbitMQConfiguration = configuration.GetSection("RabbitMQ").Get<RabbitMQSettings>(); var rabbitMQConfiguration = configuration.GetSection("RabbitMQ").Get<RabbitMQSettings>();
if (rabbitMQConfiguration != null) if (rabbitMQConfiguration != null)
{ {
hcBuilder.AddRabbitMQ(x => rabbitMQConfiguration.GetConnectionFactory(), hcBuilder.AddRabbitMQ(x => rabbitMQConfiguration.GetConnectionFactory(),
name: "rabbitMQ", name: "rabbitMQ",
tags: new string[] { "rabbitMQ" }, tags: new string[] { "rabbitMQ", "services" },
timeout: new TimeSpan(0, 0, 15)); timeout: new TimeSpan(0, 0, 15));
} }
else
{
var configurationExtension = new ConfigurationExtension(configuration);
var kafkaSettings = configurationExtension.GetSetting<KafkaSettings>("kafka");
if (kafkaSettings != null && !string.IsNullOrEmpty(kafkaSettings.BootstrapServers))
{
var clientConfig = new ClientConfig { BootstrapServers = kafkaSettings.BootstrapServers };
hcBuilder.AddKafka(new ProducerConfig(clientConfig),
name: "kafka",
tags: new string[] { "kafka", "services" },
timeout: new TimeSpan(0, 0, 15)
);
return services;
} }
}
return hcBuilder;
}
} }

View File

@ -29,11 +29,11 @@ public static class QuotaExtension
{ {
public static IServiceCollection RegisterFeature(this IServiceCollection services) public static IServiceCollection RegisterFeature(this IServiceCollection services)
{ {
services.AddScoped<ITenantQuotaFeatureChecker, CountRoomAdminChecker>(); services.AddScoped<ITenantQuotaFeatureChecker, CountPaidUserChecker>();
services.AddScoped<TenantQuotaFeatureCheckerCount<CountRoomAdminFeature>, CountRoomAdminChecker>(); services.AddScoped<TenantQuotaFeatureCheckerCount<CountPaidUserFeature>, CountPaidUserChecker>();
services.AddScoped<CountRoomAdminChecker>(); services.AddScoped<CountPaidUserChecker>();
services.AddScoped<ITenantQuotaFeatureStat<CountRoomAdminFeature, int>, CountRoomAdminStatistic>(); services.AddScoped<ITenantQuotaFeatureStat<CountPaidUserFeature, int>, CountPaidUserStatistic>();
services.AddScoped<CountRoomAdminStatistic>(); services.AddScoped<CountPaidUserStatistic>();
services.AddScoped<ITenantQuotaFeatureChecker, CountUserChecker>(); services.AddScoped<ITenantQuotaFeatureChecker, CountUserChecker>();
services.AddScoped<TenantQuotaFeatureCheckerCount<CountUserFeature>, CountUserChecker>(); services.AddScoped<TenantQuotaFeatureCheckerCount<CountUserFeature>, CountUserChecker>();

View File

@ -49,7 +49,7 @@ public static class ServiceCollectionExtension
{ {
services.AddSingleton(typeof(ICacheNotify<>), typeof(RabbitMQCache<>)); services.AddSingleton(typeof(ICacheNotify<>), typeof(RabbitMQCache<>));
} }
else if (kafkaConfiguration != null) else if (kafkaConfiguration != null && !string.IsNullOrEmpty(kafkaConfiguration.BootstrapServers))
{ {
services.AddSingleton(typeof(ICacheNotify<>), typeof(KafkaCacheNotify<>)); services.AddSingleton(typeof(ICacheNotify<>), typeof(KafkaCacheNotify<>));
} }

View File

@ -51,6 +51,7 @@ public class EmployeeFullDto : EmployeeDto
public List<string> ListAdminModules { get; set; } public List<string> ListAdminModules { get; set; }
public bool IsOwner { get; set; } public bool IsOwner { get; set; }
public bool IsVisitor { get; set; } public bool IsVisitor { get; set; }
public bool IsCollaborator { get; set; }
public string CultureName { get; set; } public string CultureName { get; set; }
public string MobilePhone { get; set; } public string MobilePhone { get; set; }
public MobilePhoneActivationStatus MobilePhoneActivationStatus { get; set; } public MobilePhoneActivationStatus MobilePhoneActivationStatus { get; set; }
@ -193,9 +194,9 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
IsVisitor = _userManager.IsUser(userInfo), IsVisitor = _userManager.IsUser(userInfo),
IsAdmin = _userManager.IsDocSpaceAdmin(userInfo), IsAdmin = _userManager.IsDocSpaceAdmin(userInfo),
IsOwner = userInfo.IsOwner(_context.Tenant), IsOwner = userInfo.IsOwner(_context.Tenant),
IsCollaborator = _userManager.IsCollaborator(userInfo),
IsLDAP = userInfo.IsLDAP(), IsLDAP = userInfo.IsLDAP(),
IsSSO = userInfo.IsSSO() IsSSO = userInfo.IsSSO()
}; };
await Init(result, userInfo); await Init(result, userInfo);
@ -205,7 +206,7 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
if (quotaSettings.EnableUserQuota) if (quotaSettings.EnableUserQuota)
{ {
result.UsedSpace = Math.Max(0, _quotaService.FindUserQuotaRows(_context.Tenant.Id, userInfo.Id).Where(r => !string.IsNullOrEmpty(r.Tag)).Sum(r => r.Counter)); result.UsedSpace = Math.Max(0, _quotaService.FindUserQuotaRows(_context.Tenant.Id, userInfo.Id).Where(r => !string.IsNullOrEmpty(r.Tag)).Sum(r => r.Counter));
var userQuotaSettings = _settingsManager.LoadForUser<UserQuotaSettings>(userInfo); var userQuotaSettings = _settingsManager.Load<UserQuotaSettings>(userInfo);
result.QuotaLimit = userQuotaSettings != null ? userQuotaSettings.UserQuota : quotaSettings.DefaultUserQuota; result.QuotaLimit = userQuotaSettings != null ? userQuotaSettings.UserQuota : quotaSettings.DefaultUserQuota;
} }

View File

@ -106,3 +106,4 @@ global using RabbitMQ.Client.Events;
global using StackExchange.Redis.Extensions.Core.Abstractions; global using StackExchange.Redis.Extensions.Core.Abstractions;
global using ILogger = Microsoft.Extensions.Logging.ILogger; global using ILogger = Microsoft.Extensions.Logging.ILogger;
global using System.Threading.Channels;

View File

@ -28,19 +28,12 @@ namespace ASC.Common.Security.Authorizing;
public static class Constants public static class Constants
{ {
public static readonly Role DocSpaceAdmin = new Role(new Guid("cd84e66b-b803-40fc-99f9-b2969a54a1de"), "Admin"); public static readonly Role DocSpaceAdmin = new Role(new Guid("cd84e66b-b803-40fc-99f9-b2969a54a1de"), "DocSpaceAdmin");
public static readonly Role Everyone = new Role(new Guid("c5cc67d1-c3e8-43c0-a3ad-3928ae3e5b5e"), "Everyone"); public static readonly Role Everyone = new Role(new Guid("c5cc67d1-c3e8-43c0-a3ad-3928ae3e5b5e"), "Everyone");
public static readonly Role RoomAdmin = new Role(new Guid("abef62db-11a8-4673-9d32-ef1d8af19dc0"), "RoomAdmin");
public static readonly Role Collaborator = new Role(new Guid("88f11e7c-7407-4bea-b4cb-070010cdbb6b"), "Collaborator");
public static readonly Role RoomAdmin = new Role(new Guid("abef62db-11a8-4673-9d32-ef1d8af19dc0"), "User"); public static readonly Role User = new Role(new Guid("aced04fa-dd96-4b35-af3e-346bf1eb972d"), "User");
public static readonly Role User = new Role(new Guid("aced04fa-dd96-4b35-af3e-346bf1eb972d"), "Visitor");
public static readonly Role Member = new Role(new Guid("ba74ca02-873f-43dc-8470-8620c156bc67"), "Member"); public static readonly Role Member = new Role(new Guid("ba74ca02-873f-43dc-8470-8620c156bc67"), "Member");
public static readonly Role Owner = new Role(new Guid("bba32183-a14d-48ed-9d39-c6b4d8925fbf"), "Owner"); public static readonly Role Owner = new Role(new Guid("bba32183-a14d-48ed-9d39-c6b4d8925fbf"), "Owner");
public static readonly Role Self = new Role(new Guid("5d5b7260-f7f7-49f1-a1c9-95fbb6a12604"), "Self"); public static readonly Role Self = new Role(new Guid("5d5b7260-f7f7-49f1-a1c9-95fbb6a12604"), "Self");
} }

View File

@ -0,0 +1,114 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Common.Threading;
public static class ChannelExtension
{
public static IList<ChannelReader<T>> Split<T>(this ChannelReader<T> ch, int n, Func<int, int, T, int> selector = null, CancellationToken cancellationToken = default)
{
var outputs = new Channel<T>[n];
for (var i = 0; i < n; i++)
{
outputs[i] = Channel.CreateUnbounded<T>();
}
Task.Run(async () =>
{
try
{
cancellationToken.ThrowIfCancellationRequested();
var index = 0;
await foreach (var item in ch.ReadAllAsync(cancellationToken))
{
if (selector == null)
{
index = (index + 1) % n;
}
else
{
index = selector(n, index, item);
}
await outputs[index].Writer.WriteAsync(item, cancellationToken);
}
}
catch (OperationCanceledException)
{
// TODO: catch error via additional channel
// var error = Channel.CreateUnbounded<T>();
// await error.Writer.WriteAsync(ex);
}
finally
{
foreach (var ch in outputs)
{
ch.Writer.Complete();
}
}
});
return outputs.Select(ch => ch.Reader).ToArray();
}
public static ChannelReader<T> Merge<T>(this IEnumerable<ChannelReader<T>> inputs, CancellationToken cancellationToken = default)
{
var output = Channel.CreateUnbounded<T>();
Task.Run(async () =>
{
try
{
cancellationToken.ThrowIfCancellationRequested();
async Task Redirect(ChannelReader<T> input)
{
await foreach (var item in input.ReadAllAsync(cancellationToken))
{
await output.Writer.WriteAsync(item, cancellationToken);
}
}
await Task.WhenAll(inputs.Select(i => Redirect(i)).ToArray());
}
catch (OperationCanceledException)
{
// TODO: catch error via additional channel
// var error = Channel.CreateUnbounded<T>();
// await error.Writer.WriteAsync(ex);
}
finally
{
output.Writer.Complete();
}
});
return output;
}
}

View File

@ -39,7 +39,8 @@ public class SubscriptionManager
{ {
Constants.DocSpaceAdmin.ID, Constants.DocSpaceAdmin.ID,
Constants.Everyone.ID, Constants.Everyone.ID,
Constants.RoomAdmin.ID Constants.RoomAdmin.ID,
Constants.Collaborator.ID,
}; };
public SubscriptionManager(CachedSubscriptionService service, TenantManager tenantManager, ICache cache) public SubscriptionManager(CachedSubscriptionService service, TenantManager tenantManager, ICache cache)

View File

@ -45,6 +45,8 @@ public class TenantManager
internal CoreBaseSettings CoreBaseSettings { get; set; } internal CoreBaseSettings CoreBaseSettings { get; set; }
internal CoreSettings CoreSettings { get; set; } internal CoreSettings CoreSettings { get; set; }
private readonly static object _lock = new object();
static TenantManager() static TenantManager()
{ {
_thisCompAddresses.Add("localhost"); _thisCompAddresses.Add("localhost");
@ -332,9 +334,12 @@ public class TenantManager
} }
public void SetTenantQuotaRow(TenantQuotaRow row, bool exchange) public void SetTenantQuotaRow(TenantQuotaRow row, bool exchange)
{
lock (_lock)
{ {
QuotaService.SetTenantQuotaRow(row, exchange); QuotaService.SetTenantQuotaRow(row, exchange);
} }
}
public List<TenantQuotaRow> FindTenantQuotaRows(int tenantId) public List<TenantQuotaRow> FindTenantQuotaRows(int tenantId)
{ {

View File

@ -61,7 +61,7 @@ public class UserManager
private readonly CardDavAddressbook _cardDavAddressbook; private readonly CardDavAddressbook _cardDavAddressbook;
private readonly ILogger<UserManager> _log; private readonly ILogger<UserManager> _log;
private readonly ICache _cache; private readonly ICache _cache;
private readonly TenantQuotaFeatureCheckerCount<CountRoomAdminFeature> _countRoomAdminChecker; private readonly TenantQuotaFeatureCheckerCount<CountPaidUserFeature> _countPaidUserChecker;
private readonly TenantQuotaFeatureCheckerCount<CountUserFeature> _activeUsersFeatureChecker; private readonly TenantQuotaFeatureCheckerCount<CountUserFeature> _activeUsersFeatureChecker;
private readonly Constants _constants; private readonly Constants _constants;
private readonly UserFormatter _userFormatter; private readonly UserFormatter _userFormatter;
@ -86,7 +86,7 @@ public class UserManager
CardDavAddressbook cardDavAddressbook, CardDavAddressbook cardDavAddressbook,
ILogger<UserManager> log, ILogger<UserManager> log,
ICache cache, ICache cache,
TenantQuotaFeatureCheckerCount<CountRoomAdminFeature> countRoomAdrminChecker, TenantQuotaFeatureCheckerCount<CountPaidUserFeature> countPaidUserChecker,
TenantQuotaFeatureCheckerCount<CountUserFeature> activeUsersFeatureChecker, TenantQuotaFeatureCheckerCount<CountUserFeature> activeUsersFeatureChecker,
UserFormatter userFormatter UserFormatter userFormatter
) )
@ -102,7 +102,7 @@ public class UserManager
_cardDavAddressbook = cardDavAddressbook; _cardDavAddressbook = cardDavAddressbook;
_log = log; _log = log;
_cache = cache; _cache = cache;
_countRoomAdminChecker = countRoomAdrminChecker; _countPaidUserChecker = countPaidUserChecker;
_activeUsersFeatureChecker = activeUsersFeatureChecker; _activeUsersFeatureChecker = activeUsersFeatureChecker;
_constants = _userManagerConstants.Constants; _constants = _userManagerConstants.Constants;
_userFormatter = userFormatter; _userFormatter = userFormatter;
@ -120,7 +120,7 @@ public class UserManager
CardDavAddressbook cardDavAddressbook, CardDavAddressbook cardDavAddressbook,
ILogger<UserManager> log, ILogger<UserManager> log,
ICache cache, ICache cache,
TenantQuotaFeatureCheckerCount<CountRoomAdminFeature> tenantQuotaFeatureChecker, TenantQuotaFeatureCheckerCount<CountPaidUserFeature> tenantQuotaFeatureChecker,
TenantQuotaFeatureCheckerCount<CountUserFeature> activeUsersFeatureChecker, TenantQuotaFeatureCheckerCount<CountUserFeature> activeUsersFeatureChecker,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
UserFormatter userFormatter) UserFormatter userFormatter)
@ -157,10 +157,16 @@ public class UserManager
switch (type) switch (type)
{ {
case EmployeeType.RoomAdmin: case EmployeeType.RoomAdmin:
users = users.Where(u => !this.IsUser(u)); users = users.Where(u => !this.IsUser(u) && !this.IsCollaborator(u) && !this.IsDocSpaceAdmin(u));
break;
case EmployeeType.DocSpaceAdmin:
users = users.Where(this.IsDocSpaceAdmin);
break;
case EmployeeType.Collaborator:
users = users.Where(this.IsCollaborator);
break; break;
case EmployeeType.User: case EmployeeType.User:
users = users.Where(u => this.IsUser(u)); users = users.Where(this.IsUser);
break; break;
} }
@ -354,15 +360,14 @@ public class UserManager
return newUser; return newUser;
} }
public async Task<UserInfo> SaveUserInfo(UserInfo u, bool isVisitor = false, bool syncCardDav = false) public async Task<UserInfo> SaveUserInfo(UserInfo u, EmployeeType type = EmployeeType.RoomAdmin, bool syncCardDav = false, bool paidUserQuotaCheck = true)
{ {
if (IsSystemUser(u.Id)) if (IsSystemUser(u.Id))
{ {
return SystemUsers[u.Id]; return SystemUsers[u.Id];
} }
_permissionContext.DemandPermissions(new UserSecurityProvider(u.Id, isVisitor ? EmployeeType.User : EmployeeType.RoomAdmin), _permissionContext.DemandPermissions(new UserSecurityProvider(u.Id, type), Constants.Action_AddRemoveUser);
Constants.Action_AddRemoveUser);
if (!_coreBaseSettings.Personal) if (!_coreBaseSettings.Personal)
{ {
@ -379,13 +384,13 @@ public class UserManager
throw new InvalidOperationException("User already exist."); throw new InvalidOperationException("User already exist.");
} }
if (isVisitor) if (type is EmployeeType.User)
{ {
await _activeUsersFeatureChecker.CheckAppend(); await _activeUsersFeatureChecker.CheckAppend();
} }
else else if (paidUserQuotaCheck)
{ {
await _countRoomAdminChecker.CheckAppend(); await _countPaidUserChecker.CheckAppend();
} }
var newUser = _userService.SaveUser(_tenantManager.GetCurrentTenant().Id, u); var newUser = _userService.SaveUser(_tenantManager.GetCurrentTenant().Id, u);
@ -886,16 +891,23 @@ public class UserManager
} }
UserGroupRef r; UserGroupRef r;
if (groupId == Constants.GroupManager.ID || groupId == Constants.GroupUser.ID) if (groupId == Constants.GroupManager.ID || groupId == Constants.GroupUser.ID || groupId == Constants.GroupCollaborator.ID)
{ {
var user = refs.TryGetValue(UserGroupRef.CreateKey(Tenant.Id, userId, Constants.GroupUser.ID, UserGroupRefType.Contains), out r) && !r.Removed; var isUser = refs.TryGetValue(UserGroupRef.CreateKey(Tenant.Id, userId, Constants.GroupUser.ID, UserGroupRefType.Contains), out r) && !r.Removed;
if (groupId == Constants.GroupUser.ID) if (groupId == Constants.GroupUser.ID)
{ {
return user; return isUser;
} }
var isCollaborator = refs.TryGetValue(UserGroupRef.CreateKey(Tenant.Id, userId, Constants.GroupCollaborator.ID, UserGroupRefType.Contains), out r) && !r.Removed;
if (groupId == Constants.GroupCollaborator.ID)
{
return isCollaborator;
}
if (groupId == Constants.GroupManager.ID) if (groupId == Constants.GroupManager.ID)
{ {
return !user; return !isUser && !isCollaborator;
} }
} }

View File

@ -38,4 +38,5 @@ public enum EmployeeType
RoomAdmin = 1, RoomAdmin = 1,
User = 2, User = 2,
DocSpaceAdmin = 3, DocSpaceAdmin = 3,
Collaborator = 4,
} }

View File

@ -88,16 +88,9 @@ public class DbLoginEventsManager
{ {
using var loginEventContext = await _dbContextFactory.CreateDbContextAsync(); using var loginEventContext = await _dbContextFactory.CreateDbContextAsync();
var events = await loginEventContext.LoginEvents await loginEventContext.LoginEvents
.Where(r => r.Id == loginEventId) .Where(r => r.Id == loginEventId)
.ToListAsync(); .ExecuteUpdateAsync(r => r.SetProperty(p => p.Active, false));
foreach (var e in events)
{
e.Active = false;
}
await loginEventContext.SaveChangesAsync();
ResetCache(); ResetCache();
} }
@ -106,16 +99,9 @@ public class DbLoginEventsManager
{ {
using var loginEventContext = await _dbContextFactory.CreateDbContextAsync(); using var loginEventContext = await _dbContextFactory.CreateDbContextAsync();
var events = await loginEventContext.LoginEvents await loginEventContext.LoginEvents
.Where(r => r.TenantId == tenantId && r.UserId == userId && r.Active) .Where(r => r.TenantId == tenantId && r.UserId == userId && r.Active)
.ToListAsync(); .ExecuteUpdateAsync(r => r.SetProperty(p => p.Active, false));
foreach (var e in events)
{
e.Active = false;
}
await loginEventContext.SaveChangesAsync();
ResetCache(tenantId, userId); ResetCache(tenantId, userId);
} }
@ -124,32 +110,18 @@ public class DbLoginEventsManager
{ {
using var loginEventContext = await _dbContextFactory.CreateDbContextAsync(); using var loginEventContext = await _dbContextFactory.CreateDbContextAsync();
var events = await loginEventContext.LoginEvents await loginEventContext.LoginEvents
.Where(r => r.TenantId == tenantId && r.Active) .Where(r => r.TenantId == tenantId && r.Active)
.ToListAsync(); .ExecuteUpdateAsync(r => r.SetProperty(p => p.Active, false));
foreach (var e in events)
{
e.Active = false;
}
await loginEventContext.SaveChangesAsync();
} }
public async Task LogOutAllActiveConnectionsExceptThis(int loginEventId, int tenantId, Guid userId) public async Task LogOutAllActiveConnectionsExceptThis(int loginEventId, int tenantId, Guid userId)
{ {
using var loginEventContext = await _dbContextFactory.CreateDbContextAsync(); using var loginEventContext = await _dbContextFactory.CreateDbContextAsync();
var events = await loginEventContext.LoginEvents await loginEventContext.LoginEvents
.Where(r => r.TenantId == tenantId && r.UserId == userId && r.Id != loginEventId && r.Active) .Where(r => r.TenantId == tenantId && r.UserId == userId && r.Id != loginEventId && r.Active)
.ToListAsync(); .ExecuteUpdateAsync(r => r.SetProperty(p => p.Active, false));
foreach (var e in events)
{
e.Active = false;
}
await loginEventContext.SaveChangesAsync();
ResetCache(tenantId, userId); ResetCache(tenantId, userId);
} }

View File

@ -24,7 +24,7 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Core.Data; namespace ASC.Core.Common.Settings;
[Singletone] [Singletone]
public class DbSettingsManagerCache public class DbSettingsManagerCache
@ -46,11 +46,11 @@ public class DbSettingsManagerCache
} }
[Scope] [Scope]
public class DbSettingsManager public class SettingsManager
{ {
private readonly TimeSpan _expirationTimeout = TimeSpan.FromMinutes(5); private readonly TimeSpan _expirationTimeout = TimeSpan.FromMinutes(5);
private readonly ILogger<DbSettingsManager> _logger; private readonly ILogger<SettingsManager> _logger;
private readonly ICache _cache; private readonly ICache _cache;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly DbSettingsManagerCache _dbSettingsManagerCache; private readonly DbSettingsManagerCache _dbSettingsManagerCache;
@ -58,10 +58,10 @@ public class DbSettingsManager
private readonly TenantManager _tenantManager; private readonly TenantManager _tenantManager;
private readonly IDbContextFactory<WebstudioDbContext> _dbContextFactory; private readonly IDbContextFactory<WebstudioDbContext> _dbContextFactory;
public DbSettingsManager( public SettingsManager(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
DbSettingsManagerCache dbSettingsManagerCache, DbSettingsManagerCache dbSettingsManagerCache,
ILogger<DbSettingsManager> logger, ILogger<SettingsManager> logger,
AuthContext authContext, AuthContext authContext,
TenantManager tenantManager, TenantManager tenantManager,
IDbContextFactory<WebstudioDbContext> dbContextFactory) IDbContextFactory<WebstudioDbContext> dbContextFactory)
@ -100,96 +100,100 @@ public class DbSettingsManager
} }
} }
public bool SaveSettings<T>(T settings, int tenantId) where T : class, ISettings<T> public void ClearCache<T>() where T : class, ISettings<T>
{ {
return SaveSettingsFor(settings, tenantId, Guid.Empty); ClearCache<T>(TenantID);
}
public T LoadSettings<T>(int tenantId) where T : class, ISettings<T>
{
return LoadSettingsFor<T>(tenantId, Guid.Empty);
} }
public void ClearCache<T>(int tenantId) where T : class, ISettings<T> public void ClearCache<T>(int tenantId) where T : class, ISettings<T>
{ {
var settings = LoadSettings<T>(tenantId); var settings = Load<T>(tenantId, Guid.Empty);
var key = settings.ID.ToString() + tenantId + Guid.Empty; var key = settings.ID.ToString() + tenantId + Guid.Empty;
_dbSettingsManagerCache.Remove(key); _dbSettingsManagerCache.Remove(key);
} }
public T GetDefault<T>() where T : class, ISettings<T>
public bool SaveSettingsFor<T>(T settings, int tenantId, Guid userId) where T : class, ISettings<T>
{ {
ArgumentNullException.ThrowIfNull(settings); var settingsInstance = ActivatorUtilities.CreateInstance<T>(_serviceProvider);
return settingsInstance.GetDefault();
using var webstudioDbContext = _dbContextFactory.CreateDbContext();
try
{
var key = settings.ID.ToString() + tenantId + userId;
var data = Serialize(settings);
var def = GetDefault<T>();
var defaultData = Serialize(def);
if (data.SequenceEqual(defaultData))
{
var strategy = webstudioDbContext.Database.CreateExecutionStrategy();
strategy.Execute(() =>
{
using var tr = webstudioDbContext.Database.BeginTransaction();
// remove default settings
var s = webstudioDbContext.WebstudioSettings
.Where(r => r.Id == settings.ID)
.Where(r => r.TenantId == tenantId)
.Where(r => r.UserId == userId)
.FirstOrDefault();
if (s != null)
{
webstudioDbContext.WebstudioSettings.Remove(s);
} }
webstudioDbContext.SaveChanges(); public T Load<T>() where T : class, ISettings<T>
tr.Commit();
});
}
else
{ {
var s = new DbWebstudioSettings return Load<T>(TenantID, Guid.Empty);
}
public T Load<T>(Guid userId) where T : class, ISettings<T>
{ {
Id = settings.ID, return Load<T>(TenantID, userId);
UserId = userId,
TenantId = tenantId,
Data = data
};
webstudioDbContext.AddOrUpdate(webstudioDbContext.WebstudioSettings, s);
webstudioDbContext.SaveChanges();
} }
_dbSettingsManagerCache.Remove(key); public T Load<T>(UserInfo user) where T : class, ISettings<T>
_cache.Insert(key, settings, _expirationTimeout);
return true;
}
catch (Exception ex)
{ {
_logger.ErrorSaveSettingsFor(ex); return Load<T>(TenantID, user.Id);
return false;
}
} }
internal T LoadSettingsFor<T>(int tenantId, Guid userId) where T : class, ISettings<T> public T Load<T>(int tenantId) where T : class, ISettings<T>
{
return Load<T>(tenantId, Guid.Empty);
}
public T LoadForDefaultTenant<T>() where T : class, ISettings<T>
{
return Load<T>(Tenant.DefaultTenant);
}
public T LoadForCurrentUser<T>() where T : class, ISettings<T>
{
return Load<T>(CurrentUserID);
}
public bool Save<T>(T data) where T : class, ISettings<T>
{
return Save(data, TenantID, Guid.Empty);
}
public bool Save<T>(T data, Guid userId) where T : class, ISettings<T>
{
return Save(data, TenantID, userId);
}
public bool Save<T>(T data, UserInfo user) where T : class, ISettings<T>
{
return Save(data, TenantID, user.Id);
}
public bool Save<T>(T data, int tenantId) where T : class, ISettings<T>
{
return Save(data, tenantId, Guid.Empty);
}
public bool SaveForDefaultTenant<T>(T data) where T : class, ISettings<T>
{
return Save(data, Tenant.DefaultTenant);
}
public bool SaveForCurrentUser<T>(T data) where T : class, ISettings<T>
{
return Save(data, CurrentUserID);
}
public bool Manage<T>(Action<T> action) where T : class, ISettings<T>
{
var settings = Load<T>();
action(settings);
return Save(settings);
}
public bool ManageForCurrentUser<T>(Action<T> action) where T : class, ISettings<T>
{
var settings = LoadForCurrentUser<T>();
action(settings);
return SaveForCurrentUser(settings);
}
internal T Load<T>(int tenantId, Guid userId) where T : class, ISettings<T>
{ {
var def = GetDefault<T>(); var def = GetDefault<T>();
var key = def.ID.ToString() + tenantId + userId; var key = def.ID.ToString() + tenantId + userId;
@ -231,75 +235,71 @@ public class DbSettingsManager
return def; return def;
} }
public T GetDefault<T>() where T : class, ISettings<T> private bool Save<T>(T settings, int tenantId, Guid userId) where T : class, ISettings<T>
{ {
var settingsInstance = ActivatorUtilities.CreateInstance<T>(_serviceProvider); ArgumentNullException.ThrowIfNull(settings);
return settingsInstance.GetDefault();
using var webstudioDbContext = _dbContextFactory.CreateDbContext();
try
{
var key = settings.ID.ToString() + tenantId + userId;
var data = Serialize(settings);
var def = GetDefault<T>();
var defaultData = Serialize(def);
if (data.SequenceEqual(defaultData))
{
var strategy = webstudioDbContext.Database.CreateExecutionStrategy();
strategy.Execute(() =>
{
using var tr = webstudioDbContext.Database.BeginTransaction();
// remove default settings
var s = webstudioDbContext.WebstudioSettings
.Where(r => r.Id == settings.ID)
.Where(r => r.TenantId == tenantId)
.Where(r => r.UserId == userId)
.FirstOrDefault();
if (s != null)
{
webstudioDbContext.WebstudioSettings.Remove(s);
} }
public T Load<T>() where T : class, ISettings<T> webstudioDbContext.SaveChanges();
tr.Commit();
});
}
else
{ {
return LoadSettings<T>(TenantID); var s = new DbWebstudioSettings
{
Id = settings.ID,
UserId = userId,
TenantId = tenantId,
Data = data
};
webstudioDbContext.AddOrUpdate(webstudioDbContext.WebstudioSettings, s);
webstudioDbContext.SaveChanges();
} }
public T LoadForCurrentUser<T>() where T : class, ISettings<T> _dbSettingsManagerCache.Remove(key);
{
return LoadForUser<T>(CurrentUserID);
}
public T LoadForUser<T>(Guid userId) where T : class, ISettings<T> _cache.Insert(key, settings, _expirationTimeout);
{
return LoadSettingsFor<T>(TenantID, userId);
}
public T LoadForUser<T>(UserInfo user) where T : class, ISettings<T> return true;
{
return LoadSettingsFor<T>(TenantID, user.Id);
} }
catch (Exception ex)
public T LoadForDefaultTenant<T>() where T : class, ISettings<T>
{ {
return LoadForTenant<T>(Tenant.DefaultTenant); _logger.ErrorSaveSettingsFor(ex);
return false;
} }
public T LoadForTenant<T>(int tenantId) where T : class, ISettings<T>
{
return LoadSettings<T>(tenantId);
}
public virtual bool Save<T>(T data) where T : class, ISettings<T>
{
return SaveSettings(data, TenantID);
}
public bool SaveForCurrentUser<T>(T data) where T : class, ISettings<T>
{
return SaveForUser(data, CurrentUserID);
}
public bool SaveForUser<T>(T data, Guid userId) where T : class, ISettings<T>
{
return SaveSettingsFor(data, TenantID, userId);
}
public bool SaveForUser<T>(T data, UserInfo user) where T : class, ISettings<T>
{
return SaveSettingsFor(data, TenantID, user.Id);
}
public bool SaveForDefaultTenant<T>(T data) where T : class, ISettings<T>
{
return SaveForTenant(data, Tenant.DefaultTenant);
}
public bool SaveForTenant<T>(T data, int tenantId) where T : class, ISettings<T>
{
return SaveSettings(data, tenantId);
}
public void ClearCache<T>() where T : class, ISettings<T>
{
ClearCache<T>(TenantID);
} }
private T Deserialize<T>(string data) private T Deserialize<T>(string data)

View File

@ -227,7 +227,8 @@ public class EFUserService : IUserService
if (sortBy == "type") if (sortBy == "type")
{ {
var q1 = from user in q var q1 = from user in q
join userGroup in userDbContext.UserGroups.Where(g => !g.Removed && (g.UserGroupId == Users.Constants.GroupAdmin.ID || g.UserGroupId == Users.Constants.GroupUser.ID)) join userGroup in userDbContext.UserGroups.Where(g => !g.Removed && (g.UserGroupId == Users.Constants.GroupAdmin.ID || g.UserGroupId == Users.Constants.GroupUser.ID
|| g.UserGroupId == Users.Constants.GroupCollaborator.ID))
on user.Id equals userGroup.Userid into joinedGroup on user.Id equals userGroup.Userid into joinedGroup
from @group in joinedGroup.DefaultIfEmpty() from @group in joinedGroup.DefaultIfEmpty()
select new { user, @group }; select new { user, @group };
@ -235,12 +236,18 @@ public class EFUserService : IUserService
if (sortOrderAsc) if (sortOrderAsc)
{ {
q = q1.OrderBy(r => r.user.ActivationStatus == EmployeeActivationStatus.Pending) q = q1.OrderBy(r => r.user.ActivationStatus == EmployeeActivationStatus.Pending)
.ThenBy(r => r.group != null && r.group.UserGroupId == Users.Constants.GroupAdmin.ID ? 1 : r.group == null ? 2 : 3).Select(r => r.user); .ThenBy(r => r.group == null ? 2 :
r.group.UserGroupId == Users.Constants.GroupAdmin.ID ? 1 :
r.group.UserGroupId == Users.Constants.GroupCollaborator.ID ? 3 : 4)
.Select(r => r.user);
} }
else else
{ {
q = q1.OrderBy(r => r.user.ActivationStatus == EmployeeActivationStatus.Pending) q = q1.OrderBy(r => r.user.ActivationStatus == EmployeeActivationStatus.Pending)
.ThenByDescending(u => u.group != null && u.group.UserGroupId == Users.Constants.GroupAdmin.ID ? 1 : u.group == null ? 2 : 3).Select(r => r.user); .ThenByDescending(u => u.group == null ? 2 :
u.group.UserGroupId == Users.Constants.GroupAdmin.ID ? 1 :
u.group.UserGroupId == Users.Constants.GroupCollaborator.ID ? 3 : 4)
.Select(r => r.user);
} }
} }
else else
@ -300,33 +307,29 @@ public class EFUserService : IUserService
using var userDbContext = _dbContextFactory.CreateDbContext(); using var userDbContext = _dbContextFactory.CreateDbContext();
using var tr = userDbContext.Database.BeginTransaction(); using var tr = userDbContext.Database.BeginTransaction();
userDbContext.Acl.RemoveRange(userDbContext.Acl.Where(r => r.Tenant == tenant && ids.Any(i => i == r.Subject))); userDbContext.Acl.Where(r => r.Tenant == tenant && ids.Any(i => i == r.Subject)).ExecuteDelete();
userDbContext.Subscriptions.RemoveRange(userDbContext.Subscriptions.Where(r => r.Tenant == tenant && stringIds.Any(i => i == r.Recipient))); userDbContext.Subscriptions.Where(r => r.Tenant == tenant && stringIds.Any(i => i == r.Recipient)).ExecuteDelete();
userDbContext.SubscriptionMethods.RemoveRange(userDbContext.SubscriptionMethods.Where(r => r.Tenant == tenant && stringIds.Any(i => i == r.Recipient))); userDbContext.SubscriptionMethods.Where(r => r.Tenant == tenant && stringIds.Any(i => i == r.Recipient)).ExecuteDelete();
var userGroups = userDbContext.UserGroups.Where(r => r.Tenant == tenant && ids.Any(i => i == r.UserGroupId)); var userGroups = userDbContext.UserGroups.Where(r => r.Tenant == tenant && ids.Any(i => i == r.UserGroupId));
var groups = userDbContext.Groups.Where(r => r.Tenant == tenant && ids.Any(i => i == r.Id)); var groups = userDbContext.Groups.Where(r => r.Tenant == tenant && ids.Any(i => i == r.Id));
if (immediate) if (immediate)
{ {
userDbContext.UserGroups.RemoveRange(userGroups); userGroups.ExecuteDelete();
userDbContext.Groups.RemoveRange(groups); groups.ExecuteDelete();
} }
else else
{ {
foreach (var ug in userGroups) userGroups.ExecuteUpdate(ug => ug
{ .SetProperty(p => p.Removed, true)
ug.Removed = true; .SetProperty(p => p.LastModified, DateTime.UtcNow));
ug.LastModified = DateTime.UtcNow;
} groups.ExecuteUpdate(g => g
foreach (var g in groups) .SetProperty(p => p.Removed, true)
{ .SetProperty(p => p.LastModified, DateTime.UtcNow));
g.Removed = true;
g.LastModified = DateTime.UtcNow;
}
} }
userDbContext.SaveChanges();
tr.Commit(); tr.Commit();
}); });
} }
@ -346,10 +349,10 @@ public class EFUserService : IUserService
using var userDbContext = _dbContextFactory.CreateDbContext(); using var userDbContext = _dbContextFactory.CreateDbContext();
using var tr = userDbContext.Database.BeginTransaction(); using var tr = userDbContext.Database.BeginTransaction();
userDbContext.Acl.RemoveRange(userDbContext.Acl.Where(r => r.Tenant == tenant && r.Subject == id)); userDbContext.Acl.Where(r => r.Tenant == tenant && r.Subject == id).ExecuteDelete();
userDbContext.Subscriptions.RemoveRange(userDbContext.Subscriptions.Where(r => r.Tenant == tenant && r.Recipient == id.ToString())); userDbContext.Subscriptions.Where(r => r.Tenant == tenant && r.Recipient == id.ToString()).ExecuteDelete();
userDbContext.SubscriptionMethods.RemoveRange(userDbContext.SubscriptionMethods.Where(r => r.Tenant == tenant && r.Recipient == id.ToString())); userDbContext.SubscriptionMethods.Where(r => r.Tenant == tenant && r.Recipient == id.ToString()).ExecuteDelete();
userDbContext.Photos.RemoveRange(userDbContext.Photos.Where(r => r.Tenant == tenant && r.UserId == id)); userDbContext.Photos.Where(r => r.Tenant == tenant && r.UserId == id).ExecuteDelete();
var userGroups = userDbContext.UserGroups.Where(r => r.Tenant == tenant && r.Userid == id); var userGroups = userDbContext.UserGroups.Where(r => r.Tenant == tenant && r.Userid == id);
var users = userDbContext.Users.Where(r => r.Tenant == tenant && r.Id == id); var users = userDbContext.Users.Where(r => r.Tenant == tenant && r.Id == id);
@ -357,28 +360,23 @@ public class EFUserService : IUserService
if (immediate) if (immediate)
{ {
userDbContext.UserGroups.RemoveRange(userGroups); userGroups.ExecuteDelete();
userDbContext.Users.RemoveRange(users); users.ExecuteDelete();
userDbContext.UserSecurity.RemoveRange(userSecurity); userSecurity.ExecuteDelete();
} }
else else
{ {
foreach (var ug in userGroups) userGroups.ExecuteUpdate(ug => ug
{ .SetProperty(p => p.Removed, true)
ug.Removed = true; .SetProperty(p => p.LastModified, DateTime.UtcNow));
ug.LastModified = DateTime.UtcNow;
}
foreach (var u in users) users.ExecuteUpdate(ug => ug
{ .SetProperty(p => p.Removed, true)
u.Removed = true; .SetProperty(p => p.LastModified, DateTime.UtcNow)
u.Status = EmployeeStatus.Terminated; .SetProperty(p => p.TerminatedDate, DateTime.UtcNow)
u.TerminatedDate = DateTime.UtcNow; .SetProperty(p => p.Status, EmployeeStatus.Terminated)
u.LastModified = DateTime.UtcNow; );
} }
}
userDbContext.SaveChanges();
tr.Commit(); tr.Commit();
}); });
@ -402,16 +400,15 @@ public class EFUserService : IUserService
var userGroups = userDbContext.UserGroups.Where(r => r.Tenant == tenant && r.Userid == userId && r.UserGroupId == groupId && r.RefType == refType); var userGroups = userDbContext.UserGroups.Where(r => r.Tenant == tenant && r.Userid == userId && r.UserGroupId == groupId && r.RefType == refType);
if (immediate) if (immediate)
{ {
userDbContext.UserGroups.RemoveRange(userGroups); userGroups.ExecuteDelete();
} }
else else
{ {
foreach (var u in userGroups) userGroups.ExecuteUpdate(ug => ug
{ .SetProperty(p => p.Removed, true)
u.LastModified = DateTime.UtcNow; .SetProperty(p => p.LastModified, DateTime.UtcNow));
u.Removed = true;
}
} }
var user = userDbContext.Users.First(r => r.Tenant == tenant && r.Id == userId); var user = userDbContext.Users.First(r => r.Tenant == tenant && r.Id == userId);
user.LastModified = DateTime.UtcNow; user.LastModified = DateTime.UtcNow;
userDbContext.SaveChanges(); userDbContext.SaveChanges();

View File

@ -39,7 +39,7 @@ public class HostedSolution
internal UserFormatter UserFormatter { get; set; } internal UserFormatter UserFormatter { get; set; }
internal TenantManager ClientTenantManager { get; set; } internal TenantManager ClientTenantManager { get; set; }
internal TenantUtil TenantUtil { get; set; } internal TenantUtil TenantUtil { get; set; }
internal DbSettingsManager SettingsManager { get; set; } internal SettingsManager SettingsManager { get; set; }
internal CoreSettings CoreSettings { get; set; } internal CoreSettings CoreSettings { get; set; }
public string Region { get; private set; } public string Region { get; private set; }
@ -51,7 +51,7 @@ public class HostedSolution
UserFormatter userFormatter, UserFormatter userFormatter,
TenantManager clientTenantManager, TenantManager clientTenantManager,
TenantUtil tenantUtil, TenantUtil tenantUtil,
DbSettingsManager settingsManager, SettingsManager settingsManager,
CoreSettings coreSettings) CoreSettings coreSettings)
{ {
TenantService = tenantService; TenantService = tenantService;
@ -196,9 +196,9 @@ public class HostedSolution
return null; return null;
} }
var tenantSettings = SettingsManager.LoadSettingsFor<TenantCookieSettings>(tenantId, Guid.Empty); var tenantSettings = SettingsManager.Load<TenantCookieSettings>(tenantId, Guid.Empty);
var expires = tenantSettings.IsDefault() ? DateTime.UtcNow.AddYears(1) : DateTime.UtcNow.AddMinutes(tenantSettings.LifeTime); var expires = tenantSettings.IsDefault() ? DateTime.UtcNow.AddYears(1) : DateTime.UtcNow.AddMinutes(tenantSettings.LifeTime);
var userSettings = SettingsManager.LoadSettingsFor<TenantCookieSettings>(tenantId, user.Id); var userSettings = SettingsManager.Load<TenantCookieSettings>(tenantId, user.Id);
return cookieStorage.EncryptCookie(tenantId, user.Id, tenantSettings.Index, expires, userSettings.Index, 0); return cookieStorage.EncryptCookie(tenantId, user.Id, tenantSettings.Index, expires, userSettings.Index, 0);
} }

View File

@ -28,8 +28,8 @@ namespace ASC.Core.Common.Log;
internal static partial class DbSettingsManagerLogger internal static partial class DbSettingsManagerLogger
{ {
[LoggerMessage(Level = LogLevel.Error, Message = "SaveSettingsFor")] [LoggerMessage(Level = LogLevel.Error, Message = "SaveSettingsFor")]
public static partial void ErrorSaveSettingsFor(this ILogger<DbSettingsManager> logger, Exception exception); public static partial void ErrorSaveSettingsFor(this ILogger<SettingsManager> logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "LoadSettingsFor")] [LoggerMessage(Level = LogLevel.Error, Message = "LoadSettingsFor")]
public static partial void ErrorLoadSettingsFor(this ILogger<DbSettingsManager> logger, Exception exception); public static partial void ErrorLoadSettingsFor(this ILogger<SettingsManager> logger, Exception exception);
} }

View File

@ -445,7 +445,8 @@ public enum MessageAction
DocumentsForcesave = 5049, DocumentsForcesave = 5049,
DocumentsStoreForcesave = 5048, DocumentsStoreForcesave = 5048,
DocumentsUploadingFormatsSettingsUpdated = 5033, DocumentsUploadingFormatsSettingsUpdated = 5033,
DocumentsExternalShareSettingsUpdated = 5069, // last DocumentsExternalShareSettingsUpdated = 5069,
DocumentsKeepNewFileNameSettingsUpdated = 5083, // last
FileConverted = 5035, FileConverted = 5035,

View File

@ -71,24 +71,18 @@ public class TelegramDao
{ {
using var dbContext = _dbContextFactory.CreateDbContext(); using var dbContext = _dbContextFactory.CreateDbContext();
var toRemove = dbContext.Users dbContext.Users
.Where(r => r.PortalUserId == userId) .Where(r => r.PortalUserId == userId)
.Where(r => r.TenantId == tenantId) .Where(r => r.TenantId == tenantId)
.ToList(); .ExecuteDelete();
dbContext.Users.RemoveRange(toRemove);
dbContext.SaveChanges();
} }
public void Delete(long telegramId) public void Delete(long telegramId)
{ {
using var dbContext = _dbContextFactory.CreateDbContext(); using var dbContext = _dbContextFactory.CreateDbContext();
var toRemove = dbContext.Users dbContext.Users
.Where(r => r.TelegramUserId == telegramId) .Where(r => r.TelegramUserId == telegramId)
.ToList(); .ExecuteDelete();
dbContext.Users.RemoveRange(toRemove);
dbContext.SaveChanges();
} }
} }

View File

@ -26,11 +26,11 @@
namespace ASC.Core.Common.Quota.Features; namespace ASC.Core.Common.Quota.Features;
public class CountRoomAdminFeature : TenantQuotaFeatureCount public class CountPaidUserFeature : TenantQuotaFeatureCount
{ {
public override bool Paid { get => true; } public override bool Paid { get => true; }
public override string Name { get => "manager"; } public override string Name { get => "manager"; }
public CountRoomAdminFeature(TenantQuota tenantQuota) : base(tenantQuota) public CountPaidUserFeature(TenantQuota tenantQuota) : base(tenantQuota)
{ {
} }
} }

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Constants = ASC.Common.Security.Authorizing.Constants;
namespace ASC.Core.Security.Authorizing; namespace ASC.Core.Security.Authorizing;
[Scope] [Scope]
@ -51,6 +53,11 @@ class RoleProvider : IRoleProvider
} }
} }
if (roles.Any(r => r.ID == Constants.Collaborator.ID || r.ID == Constants.User.ID))
{
roles = roles.Where(r => r.ID != Constants.RoomAdmin.ID).ToList();
}
return roles; return roles;
} }

View File

@ -31,23 +31,30 @@ namespace ASC.Core.Common.Security;
public static class Security public static class Security
{ {
public static readonly Dictionary<Guid, Dictionary<Guid, HashSet<Rule>>> Rules = new Dictionary<Guid, Dictionary<Guid, HashSet<Rule>>> public static readonly Dictionary<Guid, Dictionary<Guid, HashSet<Rule>>> Rules = new()
{ {
{ {
Constants.RoomAdmin.ID, new Dictionary<Guid, HashSet<Rule>>() Constants.RoomAdmin.ID, new Dictionary<Guid, HashSet<Rule>>
{ {
{ {
Constants.User.ID, new HashSet<Rule>() Constants.User.ID, new HashSet<Rule>
{ {
new Rule(UserConstants.Action_EditGroups.ID, Constants.User), new(UserConstants.Action_EditGroups.ID, Constants.User),
new Rule(UserConstants.Action_AddRemoveUser.ID), new(UserConstants.Action_AddRemoveUser.ID),
} }
}, },
{ {
Constants.RoomAdmin.ID, new HashSet<Rule>() Constants.RoomAdmin.ID, new HashSet<Rule>
{ {
new Rule(UserConstants.Action_EditGroups.ID, Constants.User), new(UserConstants.Action_EditGroups.ID, Constants.User),
new Rule(UserConstants.Action_AddRemoveUser.ID), new(UserConstants.Action_AddRemoveUser.ID),
}
},
{
Constants.Collaborator.ID, new HashSet<Rule>
{
new(UserConstants.Action_EditGroups.ID, Constants.Collaborator),
new(UserConstants.Action_AddRemoveUser.ID),
} }
} }
} }

View File

@ -24,29 +24,44 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using AuthConstants = ASC.Common.Security.Authorizing.Constants;
namespace ASC.Core.Common.Security; namespace ASC.Core.Common.Security;
public class UserGroupObject : SecurityObject public class UserGroupObject : SecurityObject
{ {
private ISubject User { get; set; } private readonly Guid _groupId;
private Guid GroupId { get; set; }
public UserGroupObject(ISubject user, Guid groupId) public UserGroupObject(ISubject user, Guid groupId)
{ {
SecurityId = user.ID; SecurityId = user.ID;
User = user; _groupId = groupId;
GroupId = groupId;
ObjectType = typeof(UserGroupObject); ObjectType = typeof(UserGroupObject);
FullId = $"{ObjectType.FullName}|{User.ID}|{GroupId}"; FullId = $"{ObjectType.FullName}|{user.ID}|{_groupId}";
} }
protected override IEnumerable<IRole> GetTargetRoles(IRoleProvider roleProvider) protected override IEnumerable<IRole> GetTargetRoles(IRoleProvider roleProvider)
{ {
return roleProvider.GetRoles(User); if (_groupId == Users.Constants.GroupAdmin.ID)
{
return new[] { AuthConstants.DocSpaceAdmin };
}
if (_groupId == Users.Constants.GroupUser.ID)
{
return new[] { AuthConstants.User };
}
if (_groupId == Users.Constants.GroupCollaborator.ID)
{
return new[] { AuthConstants.Collaborator };
}
return Array.Empty<IRole>();
} }
protected override IRuleData GetRuleData() protected override IRuleData GetRuleData()
{ {
return new Role(GroupId, "ruleData"); return new Role(_groupId, "ruleData");
} }
} }

View File

@ -67,6 +67,7 @@ public class UserSecurityProvider : SecurityObject
{ {
EmployeeType.DocSpaceAdmin => new[] { AuthConstants.DocSpaceAdmin }, EmployeeType.DocSpaceAdmin => new[] { AuthConstants.DocSpaceAdmin },
EmployeeType.RoomAdmin => new[] { AuthConstants.RoomAdmin }, EmployeeType.RoomAdmin => new[] { AuthConstants.RoomAdmin },
EmployeeType.Collaborator => new[] { AuthConstants.Collaborator },
EmployeeType.User => new[] { AuthConstants.User }, EmployeeType.User => new[] { AuthConstants.User },
_ => Array.Empty<IRole>(), _ => Array.Empty<IRole>(),
}; };

View File

@ -72,7 +72,7 @@ public class TenantCookieSettingsHelper
public TenantCookieSettings GetForTenant(int tenantId) public TenantCookieSettings GetForTenant(int tenantId)
{ {
return IsVisibleSettings return IsVisibleSettings
? _settingsManager.LoadForTenant<TenantCookieSettings>(tenantId) ? _settingsManager.Load<TenantCookieSettings>(tenantId)
: TenantCookieSettings.GetInstance(); : TenantCookieSettings.GetInstance();
} }
@ -83,20 +83,20 @@ public class TenantCookieSettingsHelper
return; return;
} }
_settingsManager.SaveForTenant(settings ?? TenantCookieSettings.GetInstance(), tenantId); _settingsManager.Save(settings ?? TenantCookieSettings.GetInstance(), tenantId);
} }
public TenantCookieSettings GetForUser(Guid userId) public TenantCookieSettings GetForUser(Guid userId)
{ {
return IsVisibleSettings return IsVisibleSettings
? _settingsManager.LoadForUser<TenantCookieSettings>(userId) ? _settingsManager.Load<TenantCookieSettings>(userId)
: TenantCookieSettings.GetInstance(); : TenantCookieSettings.GetInstance();
} }
public TenantCookieSettings GetForUser(int tenantId, Guid userId) public TenantCookieSettings GetForUser(int tenantId, Guid userId)
{ {
return IsVisibleSettings return IsVisibleSettings
? _settingsManager.LoadSettingsFor<TenantCookieSettings>(tenantId, userId) ? _settingsManager.Load<TenantCookieSettings>(tenantId, userId)
: TenantCookieSettings.GetInstance(); : TenantCookieSettings.GetInstance();
} }
@ -107,7 +107,7 @@ public class TenantCookieSettingsHelper
return; return;
} }
_settingsManager.SaveForUser(settings ?? TenantCookieSettings.GetInstance(), userId); _settingsManager.Save(settings ?? TenantCookieSettings.GetInstance(), userId);
} }
public DateTime GetExpiresTime(int tenantId) public DateTime GetExpiresTime(int tenantId)

View File

@ -90,11 +90,11 @@ public class TenantQuota : IMapFrom<DbQuota>
set => _countUserFeature.Value = value; set => _countUserFeature.Value = value;
} }
private readonly CountRoomAdminFeature _countRoomAdminFeature; private readonly CountPaidUserFeature _countPaidUserFeature;
public int CountRoomAdmin public int CountRoomAdmin
{ {
get => _countRoomAdminFeature.Value; get => _countPaidUserFeature.Value;
set => _countRoomAdminFeature.Value = value; set => _countPaidUserFeature.Value = value;
} }
private readonly UsersInRoomFeature _usersInRoomFeature; private readonly UsersInRoomFeature _usersInRoomFeature;
@ -221,7 +221,7 @@ public class TenantQuota : IMapFrom<DbQuota>
_featuresList = new List<string>(); _featuresList = new List<string>();
_countUserFeature = new CountUserFeature(this) { Order = 1 }; _countUserFeature = new CountUserFeature(this) { Order = 1 };
_countRoomAdminFeature = new CountRoomAdminFeature(this); _countPaidUserFeature = new CountPaidUserFeature(this);
_usersInRoomFeature = new UsersInRoomFeature(this) { Order = 8 }; _usersInRoomFeature = new UsersInRoomFeature(this) { Order = 8 };
_countRoomFeature = new CountRoomFeature(this) { Order = 2 }; _countRoomFeature = new CountRoomFeature(this) { Order = 2 };
_maxTotalSizeFeature = new MaxTotalSizeFeature(this); _maxTotalSizeFeature = new MaxTotalSizeFeature(this);
@ -245,7 +245,7 @@ public class TenantQuota : IMapFrom<DbQuota>
TenantQuotaFeatures = new List<TenantQuotaFeature> TenantQuotaFeatures = new List<TenantQuotaFeature>
{ {
_countUserFeature, _countUserFeature,
_countRoomAdminFeature, _countPaidUserFeature,
_usersInRoomFeature, _usersInRoomFeature,
_countRoomFeature, _countRoomFeature,
_maxTotalSizeFeature, _maxTotalSizeFeature,

View File

@ -87,12 +87,19 @@ public sealed class Constants
Name = AuthConst.DocSpaceAdmin.Name, Name = AuthConst.DocSpaceAdmin.Name,
}; };
public static readonly GroupInfo GroupCollaborator = new(SysGroupCategoryId)
{
ID = AuthConst.Collaborator.ID,
Name = AuthConst.Collaborator.Name,
};
public static readonly GroupInfo[] BuildinGroups = new[] public static readonly GroupInfo[] BuildinGroups = new[]
{ {
GroupEveryone, GroupEveryone,
GroupUser, GroupUser,
GroupManager, GroupManager,
GroupAdmin, GroupAdmin,
GroupCollaborator,
}; };
public static readonly UserInfo LostUser = new UserInfo public static readonly UserInfo LostUser = new UserInfo

View File

@ -40,7 +40,12 @@ public static class UserExtensions
public static bool IsMe(this UserInfo ui, AuthContext authContext) public static bool IsMe(this UserInfo ui, AuthContext authContext)
{ {
return ui != null && ui.Id == authContext.CurrentAccount.ID; return IsMe(ui, authContext.CurrentAccount.ID);
}
public static bool IsMe(this UserInfo user, Guid id)
{
return user != null && user.Id == id;
} }
public static bool IsDocSpaceAdmin(this UserManager userManager, Guid id) public static bool IsDocSpaceAdmin(this UserManager userManager, Guid id)
@ -65,6 +70,17 @@ public static class UserExtensions
return ui != null && userManager.IsUserInGroup(ui.Id, Constants.GroupUser.ID); return ui != null && userManager.IsUserInGroup(ui.Id, Constants.GroupUser.ID);
} }
public static bool IsCollaborator(this UserManager userManager, UserInfo userInfo)
{
return userInfo != null && userManager.IsUserInGroup(userInfo.Id, Constants.GroupCollaborator.ID);
}
public static bool IsCollaborator(this UserManager userManager, Guid id)
{
var userInfo = userManager.GetUsers(id);
return userManager.IsCollaborator(userInfo);
}
public static bool IsOutsider(this UserManager userManager, Guid id) public static bool IsOutsider(this UserManager userManager, Guid id)
{ {
return userManager.IsUser(id) && id == Constants.OutsideUser.Id; return userManager.IsUser(id) && id == Constants.OutsideUser.Id;
@ -98,7 +114,15 @@ public static class UserExtensions
public static EmployeeType GetUserType(this UserManager userManager, Guid id) public static EmployeeType GetUserType(this UserManager userManager, Guid id)
{ {
return userManager.IsDocSpaceAdmin(id) ? EmployeeType.DocSpaceAdmin : userManager.IsUser(id) ? EmployeeType.User : EmployeeType.RoomAdmin; if (userManager.GetUsers(id).Equals(Constants.LostUser))
{
return EmployeeType.User;
}
return userManager.IsDocSpaceAdmin(id) ? EmployeeType.DocSpaceAdmin :
userManager.IsUser(id) ? EmployeeType.User :
userManager.IsCollaborator(id) ? EmployeeType.Collaborator :
EmployeeType.RoomAdmin;
} }
private const string _extMobPhone = "extmobphone"; private const string _extMobPhone = "extmobphone";

View File

@ -115,10 +115,7 @@ public class BackupRepository : IBackupRepository
public void DeleteBackupSchedule(int tenantId) public void DeleteBackupSchedule(int tenantId)
{ {
using var backupContext = _dbContextFactory.CreateDbContext(); using var backupContext = _dbContextFactory.CreateDbContext();
var shedule = backupContext.Schedules.AsQueryable().Where(s => s.TenantId == tenantId).ToList(); backupContext.Schedules.Where(s => s.TenantId == tenantId).ExecuteDelete();
backupContext.Schedules.RemoveRange(shedule);
backupContext.SaveChanges();
} }
public List<BackupSchedule> GetBackupSchedules() public List<BackupSchedule> GetBackupSchedules()

View File

@ -50,6 +50,7 @@ public class Helpers
UserConstants.GroupEveryone.ID, UserConstants.GroupEveryone.ID,
UserConstants.GroupUser.ID, UserConstants.GroupUser.ID,
UserConstants.GroupManager.ID, UserConstants.GroupManager.ID,
UserConstants.GroupCollaborator.ID,
new Guid("{EA942538-E68E-4907-9394-035336EE0BA8}"), //community product new Guid("{EA942538-E68E-4907-9394-035336EE0BA8}"), //community product
new Guid("{1e044602-43b5-4d79-82f3-fd6208a11960}"), //projects product new Guid("{1e044602-43b5-4d79-82f3-fd6208a11960}"), //projects product
new Guid("{6743007C-6F95-4d20-8C88-A8601CE5E76D}"), //crm product new Guid("{6743007C-6F95-4d20-8C88-A8601CE5E76D}"), //crm product

View File

@ -125,6 +125,9 @@ public class CommonChunkedUploadSession : ICloneable
case JsonValueKind.Array: case JsonValueKind.Array:
newItems.Add(item.Key, value.EnumerateArray().Select(o => o.ToString()).ToList()); newItems.Add(item.Key, value.EnumerateArray().Select(o => o.ToString()).ToList());
break; break;
case JsonValueKind.Object:
newItems.Add(item.Key, JsonSerializer.Deserialize<Dictionary<int, string>>(item.Value.ToString()));
break;
default: default:
newItems.Add(item.Key, item.Value); newItems.Add(item.Key, item.Value);
break; break;

View File

@ -63,13 +63,13 @@ public class BaseStorageSettingsListener
var scopeClass = scope.ServiceProvider.GetService<BaseStorageSettingsListenerScope>(); var scopeClass = scope.ServiceProvider.GetService<BaseStorageSettingsListenerScope>();
var (storageSettingsHelper, settingsManager) = scopeClass; var (storageSettingsHelper, settingsManager) = scopeClass;
var settings = settingsManager.LoadForTenant<StorageSettings>(i.TenantId); var settings = settingsManager.Load<StorageSettings>(i.TenantId);
if (i.Name == settings.Module) if (i.Name == settings.Module)
{ {
storageSettingsHelper.Clear(settings); storageSettingsHelper.Clear(settings);
} }
var cdnSettings = settingsManager.LoadForTenant<CdnStorageSettings>(i.TenantId); var cdnSettings = settingsManager.Load<CdnStorageSettings>(i.TenantId);
if (i.Name == cdnSettings.Module) if (i.Name == cdnSettings.Module)
{ {
storageSettingsHelper.Clear(cdnSettings); storageSettingsHelper.Clear(cdnSettings);

View File

@ -155,7 +155,7 @@ public class StorageFactory
throw new InvalidOperationException("config section not found"); throw new InvalidOperationException("config section not found");
} }
var settings = _settingsManager.LoadForTenant<StorageSettings>(tenant.Value); var settings = _settingsManager.Load<StorageSettings>(tenant.Value);
//TODO:GetStoreAndCache //TODO:GetStoreAndCache
return GetDataStore(tenantPath, module, _storageSettingsHelper.DataStoreConsumer(settings), controller, region); return GetDataStore(tenantPath, module, _storageSettingsHelper.DataStoreConsumer(settings), controller, region);
} }

View File

@ -89,8 +89,8 @@ public class S3ZipWriteOperator : IDataWriteOperator
{ {
var fs = _fileStream; var fs = _fileStream;
_fileStream = null; _fileStream = null;
Upload(fs);
Computehash(fs, false); Computehash(fs, false);
Upload(fs);
} }
} }
@ -99,7 +99,7 @@ public class S3ZipWriteOperator : IDataWriteOperator
stream.Position = 0; stream.Position = 0;
var buffer = new byte[_sessionHolder.MaxChunkUploadSize]; var buffer = new byte[_sessionHolder.MaxChunkUploadSize];
int bytesRead; int bytesRead;
while ((bytesRead = _fileStream.Read(buffer, 0, (int)_sessionHolder.MaxChunkUploadSize)) > 0) while ((bytesRead = stream.Read(buffer, 0, (int)_sessionHolder.MaxChunkUploadSize)) > 0)
{ {
_sha.TransformBlock(buffer, 0, bytesRead, buffer, 0); _sha.TransformBlock(buffer, 0, bytesRead, buffer, 0);
} }
@ -134,12 +134,13 @@ public class S3ZipWriteOperator : IDataWriteOperator
_tarOutputStream.Close(); _tarOutputStream.Close();
_tarOutputStream.Dispose(); _tarOutputStream.Dispose();
Computehash(_fileStream, true);
Upload(_fileStream); Upload(_fileStream);
Task.WaitAll(_tasks.ToArray()); Task.WaitAll(_tasks.ToArray());
StoragePath = await _sessionHolder.FinalizeAsync(_chunkedUploadSession); StoragePath = await _sessionHolder.FinalizeAsync(_chunkedUploadSession);
Computehash(_fileStream, true);
Hash = BitConverter.ToString(_sha.Hash).Replace("-", string.Empty); Hash = BitConverter.ToString(_sha.Hash).Replace("-", string.Empty);
_sha.Dispose(); _sha.Dispose();

View File

@ -154,11 +154,8 @@ public class FeedAggregateDataProvider
using var feedDbContext = _dbContextFactory.CreateDbContext(); using var feedDbContext = _dbContextFactory.CreateDbContext();
using var tx = feedDbContext.Database.BeginTransaction(IsolationLevel.ReadUncommitted); using var tx = feedDbContext.Database.BeginTransaction(IsolationLevel.ReadUncommitted);
var aggregates = feedDbContext.FeedAggregates.Where(r => r.AggregateDate <= fromTime); feedDbContext.FeedAggregates.Where(r => r.AggregateDate <= fromTime).ExecuteDelete();
feedDbContext.FeedAggregates.RemoveRange(aggregates); feedDbContext.FeedUsers.Where(r => feedDbContext.FeedAggregates.Where(r => r.AggregateDate <= fromTime).Any(a => a.Id == r.FeedId)).ExecuteDelete();
var users = feedDbContext.FeedUsers.Where(r => feedDbContext.FeedAggregates.Where(r => r.AggregateDate <= fromTime).Any(a => a.Id == r.FeedId));
feedDbContext.FeedUsers.RemoveRange(users);
tx.Commit(); tx.Commit();
}); });
@ -310,13 +307,8 @@ public class FeedAggregateDataProvider
using var feedDbContext = _dbContextFactory.CreateDbContext(); using var feedDbContext = _dbContextFactory.CreateDbContext();
using var tx = feedDbContext.Database.BeginTransaction(IsolationLevel.ReadUncommitted); using var tx = feedDbContext.Database.BeginTransaction(IsolationLevel.ReadUncommitted);
var aggregates = feedDbContext.FeedAggregates.Where(r => r.Id == id); feedDbContext.FeedAggregates.Where(r => r.Id == id).ExecuteDelete();
feedDbContext.FeedAggregates.RemoveRange(aggregates); feedDbContext.FeedUsers.Where(r => r.FeedId == id).ExecuteDelete();
var users = feedDbContext.FeedUsers.Where(r => r.FeedId == id);
feedDbContext.FeedUsers.RemoveRange(users);
feedDbContext.SaveChanges();
tx.Commit(); tx.Commit();
}); });

View File

@ -57,9 +57,7 @@ public class IPRestrictionsRepository
using var tenantDbContext = _dbContextManager.CreateDbContext(); using var tenantDbContext = _dbContextManager.CreateDbContext();
using var tx = tenantDbContext.Database.BeginTransaction(); using var tx = tenantDbContext.Database.BeginTransaction();
var restrictions = tenantDbContext.TenantIpRestrictions.Where(r => r.Tenant == tenant).ToList(); tenantDbContext.TenantIpRestrictions.Where(r => r.Tenant == tenant).ExecuteDelete();
tenantDbContext.TenantIpRestrictions.RemoveRange(restrictions);
tenantDbContext.SaveChanges();
var ipsList = ips.Select(r => new TenantIpRestrictions var ipsList = ips.Select(r => new TenantIpRestrictions
{ {

View File

@ -69,7 +69,13 @@ public class DbWorker
return objForCreate; return objForCreate;
} }
var toAdd = new WebhooksConfig { TenantId = Tenant, Uri = uri, SecretKey = secretKey }; var toAdd = new WebhooksConfig
{
TenantId = Tenant,
Uri = uri,
SecretKey = secretKey
};
toAdd = await webhooksDbContext.AddOrUpdateAsync(r => r.WebhooksConfigs, toAdd); toAdd = await webhooksDbContext.AddOrUpdateAsync(r => r.WebhooksConfigs, toAdd);
await webhooksDbContext.SaveChangesAsync(); await webhooksDbContext.SaveChangesAsync();

View File

@ -123,9 +123,9 @@ public class Startup
app.UseAuthorization(); app.UseAuthorization();
app.UseEndpoints(endpoints => app.UseEndpoints(async endpoints =>
{ {
endpoints.MapCustom(); await endpoints.MapCustomAsync();
endpoints.MapHealthChecks("/health", new HealthCheckOptions() endpoints.MapHealthChecks("/health", new HealthCheckOptions()
{ {

View File

@ -144,15 +144,16 @@ public class Startup
app.UseAuthorization(); app.UseAuthorization();
app.UseEndpoints(endpoints => app.UseEndpoints( async endpoints =>
{ {
endpoints.MapCustom(); await endpoints.MapCustomAsync();
endpoints.MapHealthChecks("/health", new HealthCheckOptions() endpoints.MapHealthChecks("/health", new HealthCheckOptions()
{ {
Predicate = _ => true, Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
}); });
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
{ {
Predicate = r => r.Name.Contains("self") Predicate = r => r.Name.Contains("self")

View File

@ -57,10 +57,7 @@ try
startup.ConfigureServices(builder.Services); startup.ConfigureServices(builder.Services);
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => builder.Host.ConfigureContainer<ContainerBuilder>(startup.ConfigureContainer);
{
startup.ConfigureContainer(containerBuilder);
});
var app = builder.Build(); var app = builder.Build();

View File

@ -57,10 +57,7 @@ try
startup.ConfigureServices(builder.Services); startup.ConfigureServices(builder.Services);
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => builder.Host.ConfigureContainer<ContainerBuilder>(startup.ConfigureContainer);
{
startup.ConfigureContainer(containerBuilder);
});
var app = builder.Build(); var app = builder.Build();

View File

@ -158,7 +158,7 @@ public class SearchSettingsHelper
return true; return true;
} }
var settings = _settingsManager.LoadForTenant<SearchSettings>(tenantId); var settings = _settingsManager.Load<SearchSettings>(tenantId);
return settings.IsEnabled(((ISearchItemDocument)_serviceProvider.GetService(t)).IndexName); return settings.IsEnabled(((ISearchItemDocument)_serviceProvider.GetService(t)).IndexName);
} }

View File

@ -85,15 +85,11 @@ public class NotifyCleanerService : BackgroundService
{ {
using var tx = dbContext.Database.BeginTransaction(); using var tx = dbContext.Database.BeginTransaction();
var info = dbContext.NotifyInfo.Where(r => r.ModifyDate < date && r.State == 4).ToList(); var infoCount = dbContext.NotifyInfo.Where(r => r.ModifyDate < date && r.State == 4).ExecuteDelete();
var queue = dbContext.NotifyQueue.Where(r => r.CreationDate < date).ToList(); var queueCount = dbContext.NotifyQueue.Where(r => r.CreationDate < date).ExecuteDelete();
dbContext.NotifyInfo.RemoveRange(info);
dbContext.NotifyQueue.RemoveRange(queue);
dbContext.SaveChanges();
tx.Commit(); tx.Commit();
_logger.InformationClearNotifyMessages(info.Count, queue.Count); _logger.InformationClearNotifyMessages(infoCount, queueCount);
}); });
} }
catch (ThreadAbortException) catch (ThreadAbortException)

View File

@ -69,6 +69,7 @@
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
<script src="/static/scripts/browserDetector.js"></script>
<script> <script>
console.log("It's WEB CLIENT INIT"); console.log("It's WEB CLIENT INIT");
fetch("/static/scripts/config.json") fetch("/static/scripts/config.json")

View File

@ -1,4 +1,5 @@
{ {
"ResetApplicationDescription": "Authenticator application configuration will be reset.", "ResetApplicationDescription": "Authenticator application configuration will be reset.",
"ResetApplicationTitle": "Reset application configuration" "ResetApplicationTitle": "Reset application configuration",
"SuccessResetApplication": "Application settings for authentication have been successfully reset"
} }

View File

@ -44,6 +44,7 @@
"Remove": "Remove", "Remove": "Remove",
"RoleCommentator": "Commentator", "RoleCommentator": "Commentator",
"RoleCommentatorDescription": "Operations with existing files: viewing, commenting.", "RoleCommentatorDescription": "Operations with existing files: viewing, commenting.",
"RoleCollaboratorDescription": "Collaborators can create and edit files in the room, but can't create rooms, manage users, or access settings.",
"RoleDocSpaceAdminDescription": "DocSpace admins can access DocSpace settings, manage and archive rooms, invite new users and assign roles below their level. All admins have access to the Personal section.", "RoleDocSpaceAdminDescription": "DocSpace admins can access DocSpace settings, manage and archive rooms, invite new users and assign roles below their level. All admins have access to the Personal section.",
"RoleEditor": "Editor", "RoleEditor": "Editor",
"RoleEditorDescription": "Operations with existing files: viewing, editing, form filling, reviewing, commenting.", "RoleEditorDescription": "Operations with existing files: viewing, editing, form filling, reviewing, commenting.",

View File

@ -13,6 +13,7 @@
"BackToParentFolderButton": "Вернуться в папку на уровень выше", "BackToParentFolderButton": "Вернуться в папку на уровень выше",
"ByAuthor": "Автор", "ByAuthor": "Автор",
"ByCreation": "Создан", "ByCreation": "Создан",
"ByErasure": "Стирание",
"ByLastModified": "Изменен", "ByLastModified": "Изменен",
"ByOwner": "владелец", "ByOwner": "владелец",
"CollaborationRooms": "Совместное редактирование", "CollaborationRooms": "Совместное редактирование",
@ -22,6 +23,7 @@
"CopyItems": "Скопировано элементов: <strong>{{qty}}</strong>", "CopyItems": "Скопировано элементов: <strong>{{qty}}</strong>",
"CreateRoom": "Создание комнаты", "CreateRoom": "Создание комнаты",
"CustomRooms": "Пользовательская", "CustomRooms": "Пользовательская",
"DaysRemaining": "Дней осталось: {{daysRemaining}}",
"Document": "Документ", "Document": "Документ",
"EditRoom": "Изменить комнату", "EditRoom": "Изменить комнату",
"EmptyFile": "Пустой файл", "EmptyFile": "Пустой файл",

View File

@ -1,4 +1,5 @@
{ {
"ResetApplicationDescription": "Настройки приложения для аутентификации будут сброшены.", "ResetApplicationDescription": "Настройки приложения для аутентификации будут сброшены.",
"ResetApplicationTitle": "Сбросить настройки приложения" "ResetApplicationTitle": "Сбросить настройки приложения",
"SuccessResetApplication": "Настройки приложения для аутентификации успешно сброшены"
} }

View File

@ -44,6 +44,7 @@
"Remove": "Удалить", "Remove": "Удалить",
"RoleCommentator": "Комментатор", "RoleCommentator": "Комментатор",
"RoleCommentatorDescription": "Операции с существующими файлами: просмотр, комментирование.", "RoleCommentatorDescription": "Операции с существующими файлами: просмотр, комментирование.",
"RoleCollaboratorDescription": "Сотрудники могут создавать и редактировать файлы в комнате, но не могут создавать комнаты, управлять пользователями или получать доступ к настройкам.",
"RoleDocSpaceAdminDescription": "Администраторы DocSpace могут получить доступ к настройкам DocSpace, управлять и архивировать комнаты, приглашать новых пользователей и назначать роли ниже своего уровня. Все администраторы имеют доступ к Личному разделу.", "RoleDocSpaceAdminDescription": "Администраторы DocSpace могут получить доступ к настройкам DocSpace, управлять и архивировать комнаты, приглашать новых пользователей и назначать роли ниже своего уровня. Все администраторы имеют доступ к Личному разделу.",
"RoleEditor": "Редактор", "RoleEditor": "Редактор",
"RoleEditorDescription": "Операции с существующими файлами: просмотр, редактирование, заполнение форм, рецензирование, комментирование.", "RoleEditorDescription": "Операции с существующими файлами: просмотр, редактирование, заполнение форм, рецензирование, комментирование.",

View File

@ -55,7 +55,7 @@ const withLoader = (WrappedComponent) => (Loader) => {
return (!isEditor && firstLoad && !isGallery) || return (!isEditor && firstLoad && !isGallery) ||
!isLoaded || !isLoaded ||
(isMobile && inLoad) || (isMobile && inLoad && !firstLoad) ||
(isLoadingFilesFind && !Loader) || (isLoadingFilesFind && !Loader) ||
!tReady || !tReady ||
!isInit ? ( !isInit ? (

View File

@ -517,7 +517,6 @@ const ShellWrapper = inject(({ auth, backup }) => {
setSnackbarExist, setSnackbarExist,
socketHelper, socketHelper,
setTheme, setTheme,
getWhiteLabelLogoUrls,
whiteLabelLogoUrls, whiteLabelLogoUrls,
} = settingsStore; } = settingsStore;
const isBase = settingsStore.theme.isBase; const isBase = settingsStore.theme.isBase;

View File

@ -2,7 +2,8 @@ import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import App from "./App"; import App from "./App";
//import { registerSW } from "@docspace/common/sw/helper"; //import { registerSW } from "@docspace/common/sw/helper";
const root = document.getElementById("root");
ReactDOM.render(<App />, document.getElementById("root")); if (root) ReactDOM.render(<App />, root);
//registerSW(); //registerSW();

View File

@ -154,6 +154,7 @@ const Items = ({
startUpload, startUpload,
uploadEmptyFolders, uploadEmptyFolders,
isVisitor, isVisitor,
isCollaborator,
isAdmin, isAdmin,
myId, myId,
commonId, commonId,
@ -169,6 +170,8 @@ const Items = ({
onHide, onHide,
firstLoad, firstLoad,
deleteAction,
startDrag,
}) => { }) => {
useEffect(() => { useEffect(() => {
data.forEach((elem) => { data.forEach((elem) => {
@ -263,7 +266,12 @@ const Items = ({
return false; return false;
} }
if (!draggableItems || draggableItems.find((x) => x.id === item.id)) if (
!draggableItems ||
draggableItems.find(
(x) => x.id === item.id && x.isFolder === item.isFolder
)
)
return false; return false;
if ( if (
@ -278,7 +286,8 @@ const Items = ({
(item.pathParts && (item.pathParts &&
(item.pathParts[0] === myId || item.pathParts[0] === commonId)) || (item.pathParts[0] === myId || item.pathParts[0] === commonId)) ||
item.rootFolderType === FolderType.USER || item.rootFolderType === FolderType.USER ||
item.rootFolderType === FolderType.COMMON item.rootFolderType === FolderType.COMMON ||
(item.rootFolderType === FolderType.TRASH && startDrag)
) { ) {
return true; return true;
} }
@ -306,6 +315,18 @@ const Items = ({
[moveDragItems, t] [moveDragItems, t]
); );
const onRemove = React.useCallback(() => {
const translations = {
deleteOperation: t("Translations:DeleteOperation"),
deleteFromTrash: t("Translations:DeleteFromTrash"),
deleteSelectedElem: t("Translations:DeleteSelectedElem"),
FileRemoved: t("Files:FileRemoved"),
FolderRemoved: t("Files:FolderRemoved"),
};
deleteAction(translations);
}, [deleteAction]);
const onEmptyTrashAction = () => { const onEmptyTrashAction = () => {
isMobile && onHide(); isMobile && onHide();
setEmptyTrashDialogVisible(true); setEmptyTrashDialogVisible(true);
@ -335,7 +356,7 @@ const Items = ({
getEndOfBlock={getEndOfBlock} getEndOfBlock={getEndOfBlock}
showText={showText} showText={showText}
onClick={onClick} onClick={onClick}
onMoveTo={onMoveTo} onMoveTo={isTrash ? onRemove : onMoveTo}
onBadgeClick={isTrash ? onEmptyTrashAction : onBadgeClick} onBadgeClick={isTrash ? onEmptyTrashAction : onBadgeClick}
showDragItems={showDragItems} showDragItems={showDragItems}
showBadge={showBadge} showBadge={showBadge}
@ -347,7 +368,8 @@ const Items = ({
}); });
if (!firstLoad) items.splice(3, 0, <SettingsItem key="settings-item" />); if (!firstLoad) items.splice(3, 0, <SettingsItem key="settings-item" />);
if (!isVisitor) items.splice(3, 0, <AccountsItem key="accounts-item" />); if (!isVisitor && !isCollaborator)
items.splice(3, 0, <AccountsItem key="accounts-item" />);
if (!isVisitor) items.splice(3, 0, <CatalogDivider key="other-header" />); if (!isVisitor) items.splice(3, 0, <CatalogDivider key="other-header" />);
else items.splice(2, 0, <CatalogDivider key="other-header" />); else items.splice(2, 0, <CatalogDivider key="other-header" />);
@ -401,9 +423,9 @@ export default inject(
selection, selection,
dragging, dragging,
setDragging, setDragging,
setStartDrag,
trashIsEmpty, trashIsEmpty,
firstLoad, firstLoad,
startDrag,
} = filesStore; } = filesStore;
const { startUpload } = uploadDataStore; const { startUpload } = uploadDataStore;
@ -417,12 +439,17 @@ export default inject(
} = treeFoldersStore; } = treeFoldersStore;
const { id, pathParts, rootFolderType } = selectedFolderStore; const { id, pathParts, rootFolderType } = selectedFolderStore;
const { moveDragItems, uploadEmptyFolders } = filesActionsStore; const {
moveDragItems,
uploadEmptyFolders,
deleteAction,
} = filesActionsStore;
const { setEmptyTrashDialogVisible } = dialogsStore; const { setEmptyTrashDialogVisible } = dialogsStore;
return { return {
isAdmin: auth.isAdmin, isAdmin: auth.isAdmin,
isVisitor: auth.userStore.user.isVisitor, isVisitor: auth.userStore.user.isVisitor,
isCollaborator: auth.userStore.user.isCollaborator,
myId: myFolderId, myId: myFolderId,
commonId: commonFolderId, commonId: commonFolderId,
isPrivacy: isPrivacyFolder, isPrivacy: isPrivacyFolder,
@ -435,14 +462,15 @@ export default inject(
draggableItems: dragging ? selection : null, draggableItems: dragging ? selection : null,
dragging, dragging,
setDragging, setDragging,
setStartDrag,
moveDragItems, moveDragItems,
deleteAction,
startUpload, startUpload,
uploadEmptyFolders, uploadEmptyFolders,
setEmptyTrashDialogVisible, setEmptyTrashDialogVisible,
trashIsEmpty, trashIsEmpty,
rootFolderType, rootFolderType,
firstLoad, firstLoad,
startDrag,
}; };
} }
)(withTranslation(["Files", "Common", "Translations"])(observer(Items))); )(withTranslation(["Files", "Common", "Translations"])(observer(Items)));

View File

@ -50,10 +50,17 @@ const ArticleBodyContent = (props) => {
const [disableBadgeClick, setDisableBadgeClick] = React.useState(false); const [disableBadgeClick, setDisableBadgeClick] = React.useState(false);
let loadTimeout = null;
const campaigns = (localStorage.getItem("campaigns") || "") const campaigns = (localStorage.getItem("campaigns") || "")
.split(",") .split(",")
.filter((campaign) => campaign.length > 0); .filter((campaign) => campaign.length > 0);
const cleanTimer = () => {
loadTimeout && clearTimeout(loadTimeout);
loadTimeout = null;
};
const onClick = React.useCallback( const onClick = React.useCallback(
(folderId) => { (folderId) => {
const { const {
@ -72,7 +79,9 @@ const ArticleBodyContent = (props) => {
const filesSection = window.location.pathname.indexOf("/filter") > 0; const filesSection = window.location.pathname.indexOf("/filter") > 0;
if (filesSection) { if (filesSection) {
loadTimeout = setTimeout(() => {
setIsLoading(true); setIsLoading(true);
}, 200);
} else { } else {
showLoader(); showLoader();
} }
@ -86,26 +95,9 @@ const ArticleBodyContent = (props) => {
? RoomSearchArea.Archive ? RoomSearchArea.Archive
: RoomSearchArea.Active; : RoomSearchArea.Active;
fetchRooms(folderId, filter) fetchRooms(folderId, filter).finally(() => {
.then(() => {
const url = getCategoryUrl(
folderId === archiveFolderId
? CategoryType.Archive
: CategoryType.Shared
);
const filterParamsStr = filter.toUrlParams();
history.push(
combineUrl(
window.DocSpaceConfig?.proxy?.url,
homepage,
`${url}?${filterParamsStr}`
)
);
})
.finally(() => {
if (filesSection) { if (filesSection) {
cleanTimer();
setIsLoading(false); setIsLoading(false);
} else { } else {
hideLoader(); hideLoader();
@ -113,27 +105,6 @@ const ArticleBodyContent = (props) => {
}); });
} else { } else {
fetchFiles(folderId, null, true, false) fetchFiles(folderId, null, true, false)
.then(() => {
if (!filesSection) {
const filter = FilesFilter.getDefault();
filter.folder = folderId;
const filterParamsStr = filter.toUrlParams();
const url = getCategoryUrl(categoryType, filter.folder);
const pathname = `${url}?${filterParamsStr}`;
history.push(
combineUrl(
window.DocSpaceConfig?.proxy?.url,
config.homepage,
pathname
)
);
}
})
.catch((err) => toastr.error(err)) .catch((err) => toastr.error(err))
.finally(() => { .finally(() => {
if (filesSection) { if (filesSection) {

View File

@ -9,6 +9,7 @@ import ActionsPresentationReactSvgUrl from "PUBLIC_DIR/images/actions.presentati
import CatalogFolderReactSvgUrl from "PUBLIC_DIR/images/catalog.folder.react.svg?url"; import CatalogFolderReactSvgUrl from "PUBLIC_DIR/images/catalog.folder.react.svg?url";
import PersonAdminReactSvgUrl from "PUBLIC_DIR/images/person.admin.react.svg?url"; import PersonAdminReactSvgUrl from "PUBLIC_DIR/images/person.admin.react.svg?url";
import PersonManagerReactSvgUrl from "PUBLIC_DIR/images/person.manager.react.svg?url"; import PersonManagerReactSvgUrl from "PUBLIC_DIR/images/person.manager.react.svg?url";
import PersonReactSvgUrl from "PUBLIC_DIR/images/person.react.svg?url";
import PersonUserReactSvgUrl from "PUBLIC_DIR/images/person.user.react.svg?url"; import PersonUserReactSvgUrl from "PUBLIC_DIR/images/person.user.react.svg?url";
import InviteAgainReactSvgUrl from "PUBLIC_DIR/images/invite.again.react.svg?url"; import InviteAgainReactSvgUrl from "PUBLIC_DIR/images/invite.again.react.svg?url";
import React from "react"; import React from "react";
@ -29,7 +30,7 @@ import { Events, EmployeeType } from "@docspace/common/constants";
import { getMainButtonItems } from "SRC_DIR/helpers/plugins"; import { getMainButtonItems } from "SRC_DIR/helpers/plugins";
import toastr from "@docspace/components/toast/toastr"; import toastr from "@docspace/components/toast/toastr";
import styled from "styled-components"; import styled, { css } from "styled-components";
import Button from "@docspace/components/button"; import Button from "@docspace/components/button";
import { resendInvitesAgain } from "@docspace/common/api/people"; import { resendInvitesAgain } from "@docspace/common/api/people";
@ -38,30 +39,36 @@ const StyledButton = styled(Button)`
font-weight: 700; font-weight: 700;
font-size: 16px; font-size: 16px;
padding: 0; padding: 0;
opacity: 1; opacity: ${(props) => (props.isDisabled ? 0.6 : 1)};
background-color: ${({ currentColorScheme }) => background-color: ${({ currentColorScheme }) =>
currentColorScheme.main.accent} !important; currentColorScheme.main.accent} !important;
background: ${({ currentColorScheme }) => currentColorScheme.main.accent}; background: ${({ currentColorScheme }) => currentColorScheme.main.accent};
border: ${({ currentColorScheme }) => currentColorScheme.main.accent}; border: ${({ currentColorScheme }) => currentColorScheme.main.accent};
${(props) =>
!props.isDisabled &&
css`
:hover { :hover {
background-color: ${({ currentColorScheme }) => background-color: ${({ currentColorScheme }) =>
currentColorScheme.main.accent}; currentColorScheme.main.accent};
opacity: 0.85; opacity: 0.85;
background: ${({ currentColorScheme }) => currentColorScheme.main.accent}; background: ${({ currentColorScheme }) =>
currentColorScheme.main.accent};
border: ${({ currentColorScheme }) => currentColorScheme.main.accent}; border: ${({ currentColorScheme }) => currentColorScheme.main.accent};
} }
:active { :active {
background-color: ${({ currentColorScheme }) => background-color: ${({ currentColorScheme }) =>
currentColorScheme.main.accent}; currentColorScheme.main.accent};
background: ${({ currentColorScheme }) => currentColorScheme.main.accent}; background: ${({ currentColorScheme }) =>
currentColorScheme.main.accent};
border: ${({ currentColorScheme }) => currentColorScheme.main.accent}; border: ${({ currentColorScheme }) => currentColorScheme.main.accent};
opacity: 1; opacity: 1;
filter: brightness(90%); filter: brightness(90%);
cursor: pointer; cursor: pointer;
} }
`}
.button-content { .button-content {
color: ${({ currentColorScheme }) => currentColorScheme.text.accent}; color: ${({ currentColorScheme }) => currentColorScheme.text.accent};
@ -113,6 +120,8 @@ const ArticleMainButtonContent = (props) => {
setInvitePanelOptions, setInvitePanelOptions,
mainButtonMobileVisible, mainButtonMobileVisible,
security,
} = props; } = props;
const isAccountsPage = selectedTreeNode[0] === "accounts"; const isAccountsPage = selectedTreeNode[0] === "accounts";
@ -297,6 +306,15 @@ const ArticleMainButtonContent = (props) => {
action: EmployeeType.User, action: EmployeeType.User,
key: "manager", key: "manager",
}, },
{
id: "invite_room-collaborator",
className: "main-button_drop-down",
icon: PersonReactSvgUrl,
label: t("Common:Collaborator"),
onClick: onInvite,
action: EmployeeType.Collaborator,
key: "collaborator",
},
{ {
id: "invite_user", id: "invite_user",
className: "main-button_drop-down", className: "main-button_drop-down",
@ -429,16 +447,17 @@ const ArticleMainButtonContent = (props) => {
? t("Common:Invite") ? t("Common:Invite")
: t("Common:Actions"); : t("Common:Actions");
const isDisabled = const isDisabled = isAccountsPage ? !canInvite : !security?.Create;
((!canCreate || (!canCreateFiles && !isRoomsFolder)) && !canInvite) ||
isArchiveFolder;
const isProfile = history.location.pathname === "/accounts/view/@self"; const isProfile = history.location.pathname === "/accounts/view/@self";
return ( return (
<> <>
{isMobileArticle ? ( {isMobileArticle ? (
<> <>
{!isArticleLoading && !isProfile && (canCreateFiles || canInvite) && ( {!isArticleLoading &&
!isProfile &&
(security?.Create || canInvite) && (
<MobileView <MobileView
t={t} t={t}
titleProp={t("Upload")} titleProp={t("Upload")}
@ -534,9 +553,11 @@ export default inject(
const { enablePlugins, currentColorScheme } = auth.settingsStore; const { enablePlugins, currentColorScheme } = auth.settingsStore;
const security = selectedFolderStore.security;
const currentFolderId = selectedFolderStore.id; const currentFolderId = selectedFolderStore.id;
const { isAdmin, isOwner, isVisitor } = auth.userStore.user; const { isAdmin, isOwner } = auth.userStore.user;
const { canCreateFiles } = accessRightsStore; const { canCreateFiles } = accessRightsStore;
@ -572,9 +593,9 @@ export default inject(
isAdmin, isAdmin,
isOwner, isOwner,
isVisitor,
mainButtonMobileVisible, mainButtonMobileVisible,
security,
}; };
} }
)( )(

View File

@ -65,8 +65,10 @@ const RootFolderContainer = (props) => {
isEmptyPage, isEmptyPage,
setIsEmptyPage, setIsEmptyPage,
isVisitor, isVisitor,
isCollaborator,
sectionWidth, sectionWidth,
setIsLoadedEmptyPage, setIsLoadedEmptyPage,
security,
} = props; } = props;
const personalDescription = t("EmptyFolderDecription"); const personalDescription = t("EmptyFolderDecription");
@ -77,10 +79,12 @@ const RootFolderContainer = (props) => {
const favoritesDescription = t("FavoritesEmptyContainerDescription"); const favoritesDescription = t("FavoritesEmptyContainerDescription");
const recentDescription = t("RecentEmptyContainerDescription"); const recentDescription = t("RecentEmptyContainerDescription");
const roomsDescription = isVisitor const roomsDescription =
isVisitor || isCollaborator
? t("RoomEmptyContainerDescriptionUser") ? t("RoomEmptyContainerDescriptionUser")
: t("RoomEmptyContainerDescription"); : t("RoomEmptyContainerDescription");
const archiveRoomsDescription = isVisitor const archiveRoomsDescription =
isVisitor || isCollaborator
? t("ArchiveEmptyScreenUser") ? t("ArchiveEmptyScreenUser")
: t("ArchiveEmptyScreen"); : t("ArchiveEmptyScreen");
@ -201,7 +205,7 @@ const RootFolderContainer = (props) => {
imageSrc: theme.isBase imageSrc: theme.isBase
? EmptyScreenCorporateSvgUrl ? EmptyScreenCorporateSvgUrl
: EmptyScreenCorporateDarkSvgUrl, : EmptyScreenCorporateDarkSvgUrl,
buttons: isVisitor ? null : roomsButtons, buttons: !security?.Create ? null : roomsButtons,
}; };
case FolderType.Archive: case FolderType.Archive:
return { return {
@ -386,7 +390,7 @@ export default inject(
setIsEmptyPage, setIsEmptyPage,
setIsLoadedEmptyPage, setIsLoadedEmptyPage,
} = filesStore; } = filesStore;
const { title, rootFolderType } = selectedFolderStore; const { title, rootFolderType, security } = selectedFolderStore;
const { isPrivacyFolder, myFolderId } = treeFoldersStore; const { isPrivacyFolder, myFolderId } = treeFoldersStore;
return { return {
@ -394,6 +398,7 @@ export default inject(
isPrivacyFolder, isPrivacyFolder,
isDesktop: isDesktopClient, isDesktop: isDesktopClient,
isVisitor: auth.userStore.user.isVisitor, isVisitor: auth.userStore.user.isVisitor,
isCollaborator: auth.userStore.user.isCollaborator,
isEncryptionSupport, isEncryptionSupport,
organizationName, organizationName,
privacyInstructions, privacyInstructions,
@ -411,6 +416,7 @@ export default inject(
isEmptyPage, isEmptyPage,
setIsEmptyPage, setIsEmptyPage,
setIsLoadedEmptyPage, setIsLoadedEmptyPage,
security,
}; };
} }
)(withTranslation(["Files"])(observer(RootFolderContainer))); )(withTranslation(["Files"])(observer(RootFolderContainer)));

View File

@ -419,7 +419,7 @@ class TreeFolders extends React.Component {
data: incomingDate, data: incomingDate,
certainFolders, certainFolders,
roomsFolderId, roomsFolderId,
expandedPanelKeys, expandedPanelKeys = [],
} = this.props; } = this.props;
isExpand && this.setState({ isExpand: true }); isExpand && this.setState({ isExpand: true });
//console.log("load data...", treeNode); //console.log("load data...", treeNode);

View File

@ -15,6 +15,7 @@ const ChangeUserTypeEvent = ({
peopleFilter, peopleFilter,
updateUserType, updateUserType,
getUsersList, getUsersList,
onClose,
}) => { }) => {
const { const {
toType, toType,
@ -58,7 +59,7 @@ const ChangeUserTypeEvent = ({
}; };
const onChangeUserType = () => { const onChangeUserType = () => {
onClose(); onClosePanel();
updateUserType(toType, userIDs, peopleFilter, fromType) updateUserType(toType, userIDs, peopleFilter, fromType)
.then(() => { .then(() => {
toastr.success(t("SuccessChangeUserType")); toastr.success(t("SuccessChangeUserType"));
@ -83,14 +84,15 @@ const ChangeUserTypeEvent = ({
}); });
}; };
const onClose = () => { const onClosePanel = () => {
setVisible(false); setVisible(false);
onClose();
}; };
const onCloseAction = async () => { const onCloseAction = async () => {
await getUsersList(peopleFilter); await getUsersList(peopleFilter);
abortCallback && abortCallback(); abortCallback && abortCallback();
onClose(); onClosePanel();
}; };
const getType = (type) => { const getType = (type) => {
@ -99,6 +101,8 @@ const ChangeUserTypeEvent = ({
return t("Common:DocSpaceAdmin"); return t("Common:DocSpaceAdmin");
case "manager": case "manager":
return t("Common:RoomAdmin"); return t("Common:RoomAdmin");
case "collaborator":
return t("Common:Collaborator");
case "user": case "user":
default: default:
return t("Common:User"); return t("Common:User");

View File

@ -48,7 +48,7 @@ const CreateEvent = ({
setEventDialogVisible, setEventDialogVisible,
eventDialogVisible, eventDialogVisible,
createWithoutDialog, keepNewFileName,
}) => { }) => {
const [headerTitle, setHeaderTitle] = React.useState(null); const [headerTitle, setHeaderTitle] = React.useState(null);
const [startValue, setStartValue] = React.useState(""); const [startValue, setStartValue] = React.useState("");
@ -77,7 +77,7 @@ const CreateEvent = ({
if (!extension) return setEventDialogVisible(true); if (!extension) return setEventDialogVisible(true);
if (!createWithoutDialog) { if (!keepNewFileName) {
setEventDialogVisible(true); setEventDialogVisible(true);
} else { } else {
onSave(null, title || defaultName); onSave(null, title || defaultName);
@ -289,6 +289,7 @@ export default inject(
uploadDataStore, uploadDataStore,
dialogsStore, dialogsStore,
oformsStore, oformsStore,
settingsStore,
}) => { }) => {
const { const {
setIsLoading, setIsLoading,
@ -321,7 +322,7 @@ export default inject(
eventDialogVisible, eventDialogVisible,
} = dialogsStore; } = dialogsStore;
const { createWithoutDialog } = filesStore; const { keepNewFileName } = settingsStore;
return { return {
setEventDialogVisible, setEventDialogVisible,
@ -352,7 +353,7 @@ export default inject(
replaceFileStream, replaceFileStream,
setEncryptionAccess, setEncryptionAccess,
createWithoutDialog, keepNewFileName,
}; };
} }
)(observer(CreateEvent)); )(observer(CreateEvent));

View File

@ -22,9 +22,9 @@ const Dialog = ({
onCancel, onCancel,
onClose, onClose,
isCreateDialog, isCreateDialog,
createWithoutDialog,
setCreateWithoutDialog,
extension, extension,
keepNewFileName,
setKeepNewFileName,
}) => { }) => {
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const [isDisabled, setIsDisabled] = useState(false); const [isDisabled, setIsDisabled] = useState(false);
@ -32,8 +32,8 @@ const Dialog = ({
const [isChanged, setIsChanged] = useState(false); const [isChanged, setIsChanged] = useState(false);
useEffect(() => { useEffect(() => {
createWithoutDialog && isCreateDialog && setIsChecked(createWithoutDialog); keepNewFileName && isCreateDialog && setIsChecked(keepNewFileName);
}, [isCreateDialog, createWithoutDialog]); }, [isCreateDialog, keepNewFileName]);
useEffect(() => { useEffect(() => {
let input = document?.getElementById("create-text-input"); let input = document?.getElementById("create-text-input");
@ -80,7 +80,7 @@ const Dialog = ({
const onSaveAction = useCallback( const onSaveAction = useCallback(
(e) => { (e) => {
setIsDisabled(true); setIsDisabled(true);
isCreateDialog && setCreateWithoutDialog(isChecked); isCreateDialog && setKeepNewFileName(isChecked);
onSave && onSave(e, value); onSave && onSave(e, value);
}, },
[onSave, isCreateDialog, value, isChecked] [onSave, isCreateDialog, value, isChecked]
@ -88,7 +88,7 @@ const Dialog = ({
const onCancelAction = useCallback((e) => { const onCancelAction = useCallback((e) => {
if (isChecked) { if (isChecked) {
setCreateWithoutDialog(false); setKeepNewFileName(false);
} }
onCancel && onCancel(e); onCancel && onCancel(e);
}, []); }, []);
@ -96,7 +96,7 @@ const Dialog = ({
const onCloseAction = useCallback( const onCloseAction = useCallback(
(e) => { (e) => {
if (!isDisabled && isChecked) { if (!isDisabled && isChecked) {
setCreateWithoutDialog(false); setKeepNewFileName(false);
} }
onClose && onClose(e); onClose && onClose(e);
}, },
@ -171,9 +171,9 @@ const Dialog = ({
); );
}; };
export default inject(({ auth, filesStore }) => { export default inject(({ auth, settingsStore }) => {
const { folderFormValidation } = auth.settingsStore; const { folderFormValidation } = auth.settingsStore;
const { createWithoutDialog, setCreateWithoutDialog } = filesStore; const { keepNewFileName, setKeepNewFileName } = settingsStore;
return { folderFormValidation, createWithoutDialog, setCreateWithoutDialog }; return { folderFormValidation, keepNewFileName, setKeepNewFileName };
})(observer(Dialog)); })(observer(Dialog));

View File

@ -39,6 +39,8 @@ const Bar = (props) => {
showRoomQuotaBar, showRoomQuotaBar,
showStorageQuotaBar, showStorageQuotaBar,
currentColorScheme,
} = props; } = props;
const [barVisible, setBarVisible] = useState({ const [barVisible, setBarVisible] = useState({
@ -112,13 +114,7 @@ const Bar = (props) => {
}, []); }, []);
const sendActivationLinkAction = () => { const sendActivationLinkAction = () => {
if (sendActivationLink) { sendActivationLink && sendActivationLink(t);
sendActivationLink(t).finally(() => {
return onCloseActivationBar();
});
} else {
onCloseActivationBar();
}
}; };
const onCloseActivationBar = () => { const onCloseActivationBar = () => {
@ -183,6 +179,7 @@ const Bar = (props) => {
return (isRoomQuota || isStorageQuota) && tReady ? ( return (isRoomQuota || isStorageQuota) && tReady ? (
<QuotasBar <QuotasBar
currentColorScheme={currentColorScheme}
isRoomQuota={isRoomQuota} isRoomQuota={isRoomQuota}
{...quotasValue} {...quotasValue}
onClick={onClickQuota} onClick={onClickQuota}
@ -191,6 +188,7 @@ const Bar = (props) => {
/> />
) : withActivationBar && barVisible.confirmEmail && tReady ? ( ) : withActivationBar && barVisible.confirmEmail && tReady ? (
<ConfirmEmailBar <ConfirmEmailBar
currentColorScheme={currentColorScheme}
onLoad={onLoad} onLoad={onLoad}
onClick={sendActivationLinkAction} onClick={sendActivationLinkAction}
onClose={onCloseActivationBar} onClose={onCloseActivationBar}
@ -221,6 +219,8 @@ export default inject(({ auth, profileActionsStore }) => {
showStorageQuotaBar, showStorageQuotaBar,
} = auth.currentQuotaStore; } = auth.currentQuotaStore;
const { currentColorScheme } = auth.settingsStore;
return { return {
isAdmin: user?.isAdmin, isAdmin: user?.isAdmin,
withActivationBar, withActivationBar,
@ -236,5 +236,7 @@ export default inject(({ auth, profileActionsStore }) => {
showRoomQuotaBar, showRoomQuotaBar,
showStorageQuotaBar, showStorageQuotaBar,
currentColorScheme,
}; };
})(withTranslation(["Profile", "Common"])(withRouter(observer(Bar)))); })(withTranslation(["Profile", "Common"])(withRouter(observer(Bar))));

View File

@ -11,10 +11,17 @@ const StyledLink = styled(Link)`
line-height: 16px; line-height: 16px;
font-weight: 400; font-weight: 400;
color: #316daa; color: ${(props) => props.currentColorScheme.main.accent};
`; `;
const ConfirmEmailBar = ({ t, tReady, onClick, onClose, onLoad }) => { const ConfirmEmailBar = ({
t,
tReady,
onClick,
onClose,
onLoad,
currentColorScheme,
}) => {
return ( return (
tReady && ( tReady && (
<SnackBar <SnackBar
@ -22,7 +29,12 @@ const ConfirmEmailBar = ({ t, tReady, onClick, onClose, onLoad }) => {
text={ text={
<> <>
{t("ConfirmEmailDescription")}{" "} {t("ConfirmEmailDescription")}{" "}
<StyledLink onClick={onClick}>{t("RequestActivation")}</StyledLink> <StyledLink
currentColorScheme={currentColorScheme}
onClick={onClick}
>
{t("RequestActivation")}
</StyledLink>
</> </>
} }
isCampaigns={false} isCampaigns={false}

View File

@ -11,7 +11,7 @@ const StyledLink = styled(Link)`
line-height: 16px; line-height: 16px;
font-weight: 400; font-weight: 400;
color: #316daa; color: ${(props) => props.currentColorScheme.main.accent};
`; `;
const QuotasBar = ({ const QuotasBar = ({
@ -23,6 +23,7 @@ const QuotasBar = ({
onClick, onClick,
onClose, onClose,
onLoad, onLoad,
currentColorScheme,
}) => { }) => {
const onClickAction = () => { const onClickAction = () => {
onClick && onClick(isRoomQuota); onClick && onClick(isRoomQuota);
@ -37,7 +38,10 @@ const QuotasBar = ({
description: ( description: (
<Trans i18nKey="RoomQuotaDescription" t={t}> <Trans i18nKey="RoomQuotaDescription" t={t}>
You can archived the unnecessary rooms or You can archived the unnecessary rooms or
<StyledLink onClick={onClickAction}> <StyledLink
currentColorScheme={currentColorScheme}
onClick={onClickAction}
>
{{ clickHere: t("ClickHere") }} {{ clickHere: t("ClickHere") }}
</StyledLink>{" "} </StyledLink>{" "}
to find a better pricing plan for your portal. to find a better pricing plan for your portal.
@ -50,7 +54,10 @@ const QuotasBar = ({
description: ( description: (
<Trans i18nKey="StorageQuotaDescription" t={t}> <Trans i18nKey="StorageQuotaDescription" t={t}>
You can remove the unnecessary files or{" "} You can remove the unnecessary files or{" "}
<StyledLink onClick={onClickAction}> <StyledLink
currentColorScheme={currentColorScheme}
onClick={onClickAction}
>
{{ clickHere: t("ClickHere") }} {{ clickHere: t("ClickHere") }}
</StyledLink>{" "} </StyledLink>{" "}
to find a better pricing plan for your portal. to find a better pricing plan for your portal.

View File

@ -85,7 +85,18 @@ const PeopleSelector = ({
}, [isLoading]); }, [isLoading]);
const toListItem = (item) => { const toListItem = (item) => {
const { id, email, avatar, icon, displayName, hasAvatar } = item; const {
id,
email,
avatar,
icon,
displayName,
hasAvatar,
isOwner,
isAdmin,
isVisitor,
isCollaborator,
} = item;
const role = getUserRole(item); const role = getUserRole(item);
@ -98,6 +109,10 @@ const PeopleSelector = ({
icon, icon,
label: displayName || email, label: displayName || email,
role, role,
isOwner,
isAdmin,
isVisitor,
isCollaborator,
}; };
}; };

View File

@ -79,8 +79,6 @@ const AvatarEditorDialog = (props) => {
const avatars = await createThumbnailsAvatar(profile.id, { const avatars = await createThumbnailsAvatar(profile.id, {
x: 0, x: 0,
y: 0, y: 0,
width: 192,
height: 192,
tmpFile: res.data, tmpFile: res.data,
}); });
updateCreatedAvatar(avatars); updateCreatedAvatar(avatars);

View File

@ -14,10 +14,11 @@ class ResetApplicationDialogComponent extends React.Component {
} }
resetApp = async () => { resetApp = async () => {
const { resetTfaApp, id, onClose, history } = this.props; const { t, resetTfaApp, id, onClose, history } = this.props;
onClose && onClose(); onClose && onClose();
try { try {
const res = await resetTfaApp(id); const res = await resetTfaApp(id);
toastr.success(t("SuccessResetApplication"));
if (res) history.push(res.replace(window.location.origin, "")); if (res) history.push(res.replace(window.location.origin, ""));
} catch (e) { } catch (e) {
toastr.error(e); toastr.error(e);

View File

@ -62,12 +62,19 @@ const AddUsersPanel = ({
const currentItem = shareDataItems.find((x) => x.sharedTo.id === item.id); const currentItem = shareDataItems.find((x) => x.sharedTo.id === item.id);
if (!currentItem) { if (!currentItem) {
const currentAccess =
item.isOwner || item.isAdmin
? ShareAccessRights.RoomManager
: access.access;
const newItem = { const newItem = {
access: access.access, access: currentAccess,
email: item.email, email: item.email,
id: item.id, id: item.id,
displayName: item.label, displayName: item.label,
avatar: item.avatar, avatar: item.avatar,
isOwner: item.isOwner,
isAdmin: item.isAdmin,
}; };
items.push(newItem); items.push(newItem);
} }

View File

@ -35,12 +35,15 @@ const InvitePanel = ({
userLink, userLink,
guestLink, guestLink,
adminLink, adminLink,
collaboratorLink,
defaultAccess, defaultAccess,
inviteUsers, inviteUsers,
setInfoPanelIsMobileHidden, setInfoPanelIsMobileHidden,
reloadSelectionParentRoom, reloadSelectionParentRoom,
setUpdateRoomMembers, setUpdateRoomMembers,
roomsView, roomsView,
getUsersList,
filter,
}) => { }) => {
const [selectedRoom, setSelectedRoom] = useState(null); const [selectedRoom, setSelectedRoom] = useState(null);
const [hasErrors, setHasErrors] = useState(false); const [hasErrors, setHasErrors] = useState(false);
@ -85,7 +88,8 @@ const InvitePanel = ({
useEffect(() => { useEffect(() => {
if (roomId === -1) { if (roomId === -1) {
if (!userLink || !guestLink || !adminLink) getPortalInviteLinks(); if (!userLink || !guestLink || !adminLink || !collaboratorLink)
getPortalInviteLinks();
setShareLinks([ setShareLinks([
{ {
@ -106,6 +110,12 @@ const InvitePanel = ({
shareLink: adminLink, shareLink: adminLink,
access: 3, access: 3,
}, },
{
id: "collaborator",
title: "Collaborator",
shareLink: collaboratorLink,
access: 4,
},
]); ]);
return; return;
@ -113,7 +123,7 @@ const InvitePanel = ({
selectRoom(); selectRoom();
getInfo(); getInfo();
}, [roomId, userLink, guestLink, adminLink]); }, [roomId, userLink, guestLink, adminLink, collaboratorLink]);
useEffect(() => { useEffect(() => {
const hasErrors = inviteItems.some((item) => !!item.errors?.length); const hasErrors = inviteItems.some((item) => !!item.errors?.length);
@ -178,6 +188,10 @@ const InvitePanel = ({
} catch (err) { } catch (err) {
toastr.error(err); toastr.error(err);
setIsLoading(false); setIsLoading(false);
} finally {
if (roomId === -1) {
await getUsersList(filter , false);
}
} }
}; };
@ -261,7 +275,8 @@ const InvitePanel = ({
export default inject(({ auth, peopleStore, filesStore, dialogsStore }) => { export default inject(({ auth, peopleStore, filesStore, dialogsStore }) => {
const { theme } = auth.settingsStore; const { theme } = auth.settingsStore;
const { getUsersByQuery, inviteUsers } = peopleStore.usersStore; const { getUsersByQuery, inviteUsers, getUsersList } = peopleStore.usersStore;
const { filter } = peopleStore.filterStore;
const { const {
setIsMobileHidden: setInfoPanelIsMobileHidden, setIsMobileHidden: setInfoPanelIsMobileHidden,
reloadSelectionParentRoom, reloadSelectionParentRoom,
@ -275,6 +290,7 @@ export default inject(({ auth, peopleStore, filesStore, dialogsStore }) => {
userLink, userLink,
guestLink, guestLink,
adminLink, adminLink,
collaboratorLink,
} = peopleStore.inviteLinksStore; } = peopleStore.inviteLinksStore;
const { const {
@ -308,11 +324,14 @@ export default inject(({ auth, peopleStore, filesStore, dialogsStore }) => {
userLink, userLink,
guestLink, guestLink,
adminLink, adminLink,
collaboratorLink,
inviteUsers, inviteUsers,
setInfoPanelIsMobileHidden, setInfoPanelIsMobileHidden,
reloadSelectionParentRoom, reloadSelectionParentRoom,
setUpdateRoomMembers, setUpdateRoomMembers,
roomsView, roomsView,
getUsersList,
filter,
}; };
})( })(
withTranslation([ withTranslation([

View File

@ -16,6 +16,7 @@ import {
StyledDeleteIcon, StyledDeleteIcon,
StyledText, StyledText,
} from "../StyledInvitePanel"; } from "../StyledInvitePanel";
import { filterUserRoleOptions } from "SRC_DIR/helpers/utils";
const Item = ({ const Item = ({
t, t,
@ -38,7 +39,11 @@ const Item = ({
const accesses = getAccessOptions(t, roomType, true, false, isOwner); const accesses = getAccessOptions(t, roomType, true, false, isOwner);
const defaultAccess = accesses.find((option) => option.access === +access); const filteredAccesses = filterUserRoleOptions(accesses, item, true);
const defaultAccess = filteredAccesses.find(
(option) => option.access === +access
);
const errorsInList = () => { const errorsInList = () => {
const hasErrors = inviteItems.some((item) => !!item.errors?.length); const hasErrors = inviteItems.some((item) => !!item.errors?.length);
@ -128,7 +133,7 @@ const Item = ({
<StyledComboBox <StyledComboBox
onSelect={selectItemAccess} onSelect={selectItemAccess}
noBorder noBorder
options={accesses} options={filteredAccesses}
size="content" size="content"
scaled={false} scaled={false}
manualWidth="fit-content" manualWidth="fit-content"

View File

@ -31,6 +31,17 @@ export const getAccessOptions = (
access: access:
roomType === -1 ? EmployeeType.User : ShareAccessRights.RoomManager, roomType === -1 ? EmployeeType.User : ShareAccessRights.RoomManager,
}, },
collaborator: {
key: "collaborator",
label: t("Common:Collaborator"),
description: t("Translations:RoleCollaboratorDescription"),
quota: t("Common:Paid"),
color: "#EDC409",
access:
roomType === -1
? EmployeeType.Collaborator
: ShareAccessRights.Collaborator,
},
user: { user: {
key: "user", key: "user",
label: t("Common:User"), label: t("Common:User"),
@ -73,6 +84,7 @@ export const getAccessOptions = (
case RoomsType.FillingFormsRoom: case RoomsType.FillingFormsRoom:
options = [ options = [
accesses.roomAdmin, accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator }, { key: "s1", isSeparator: withSeparator },
accesses.formFiller, accesses.formFiller,
accesses.viewer, accesses.viewer,
@ -81,6 +93,7 @@ export const getAccessOptions = (
case RoomsType.EditingRoom: case RoomsType.EditingRoom:
options = [ options = [
accesses.roomAdmin, accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator }, { key: "s1", isSeparator: withSeparator },
accesses.editor, accesses.editor,
accesses.viewer, accesses.viewer,
@ -89,6 +102,7 @@ export const getAccessOptions = (
case RoomsType.ReviewRoom: case RoomsType.ReviewRoom:
options = [ options = [
accesses.roomAdmin, accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator }, { key: "s1", isSeparator: withSeparator },
accesses.reviewer, accesses.reviewer,
accesses.commentator, accesses.commentator,
@ -98,6 +112,7 @@ export const getAccessOptions = (
case RoomsType.ReadOnlyRoom: case RoomsType.ReadOnlyRoom:
options = [ options = [
accesses.roomAdmin, accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator }, { key: "s1", isSeparator: withSeparator },
accesses.viewer, accesses.viewer,
]; ];
@ -105,6 +120,7 @@ export const getAccessOptions = (
case RoomsType.CustomRoom: case RoomsType.CustomRoom:
options = [ options = [
accesses.roomAdmin, accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator }, { key: "s1", isSeparator: withSeparator },
accesses.editor, accesses.editor,
accesses.formFiller, accesses.formFiller,
@ -119,6 +135,7 @@ export const getAccessOptions = (
options = [ options = [
...options, ...options,
accesses.roomAdmin, accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator }, { key: "s1", isSeparator: withSeparator },
accesses.user, accesses.user,
]; ];

View File

@ -7,7 +7,6 @@ import toastr from "@docspace/components/toast/toastr";
import SelectFolderDialog from "../SelectFolderDialog"; import SelectFolderDialog from "../SelectFolderDialog";
import SimpleFileInput from "../../SimpleFileInput"; import SimpleFileInput from "../../SimpleFileInput";
import { withTranslation } from "react-i18next"; import { withTranslation } from "react-i18next";
import SelectionPanel from "../SelectionPanel/SelectionPanelBody";
import { FolderType } from "@docspace/common/constants"; import { FolderType } from "@docspace/common/constants";
class SelectFolderInput extends React.PureComponent { class SelectFolderInput extends React.PureComponent {
constructor(props) { constructor(props) {
@ -113,7 +112,7 @@ class SelectFolderInput extends React.PureComponent {
onSelect && onSelect(folderId); onSelect && onSelect(folderId);
}; };
onSetFolderInfo = (folderId) => { onSetFolderInfo = (folderId) => {
const { setExpandedPanelKeys, setParentId } = this.props; const { setExpandedPanelKeys, setParentId, clearLocalStorage } = this.props;
getFolder(folderId) getFolder(folderId)
.then((data) => { .then((data) => {
@ -123,7 +122,10 @@ class SelectFolderInput extends React.PureComponent {
setExpandedPanelKeys(pathParts); setExpandedPanelKeys(pathParts);
setParentId(data.current.parentId); setParentId(data.current.parentId);
}) })
.catch((e) => toastr.error(e)); .catch((e) => {
toastr.error(e);
clearLocalStorage();
});
}; };
render() { render() {
@ -201,7 +203,9 @@ export default inject(
treeFoldersStore, treeFoldersStore,
selectFolderDialogStore, selectFolderDialogStore,
selectedFolderStore, selectedFolderStore,
backup,
}) => { }) => {
const { clearLocalStorage } = backup;
const { setFirstLoad } = filesStore; const { setFirstLoad } = filesStore;
const { setExpandedPanelKeys } = treeFoldersStore; const { setExpandedPanelKeys } = treeFoldersStore;
const { const {
@ -220,6 +224,7 @@ export default inject(
const { setParentId } = selectedFolderStore; const { setParentId } = selectedFolderStore;
return { return {
clearLocalStorage,
setFirstLoad, setFirstLoad,
setExpandedPanelKeys, setExpandedPanelKeys,
setParentId, setParentId,

View File

@ -12,9 +12,6 @@ import ActionsUploadedFile from "./SubComponents/ActionsUploadedFile";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
import NoUserSelect from "@docspace/components/utils/commonStyles"; import NoUserSelect from "@docspace/components/utils/commonStyles";
import Button from "@docspace/components/button"; import Button from "@docspace/components/button";
import globalColors from "@docspace/components/utils/globalColors";
const buttonColor = globalColors.blueDisabled;
const StyledFileRow = styled(Row)` const StyledFileRow = styled(Row)`
width: calc(100% - 16px); width: calc(100% - 16px);
@ -145,11 +142,11 @@ class FileRow extends Component {
onCancelCurrentUpload = (e) => { onCancelCurrentUpload = (e) => {
//console.log("cancel upload ", e); //console.log("cancel upload ", e);
const { id, action, fileId } = e.currentTarget.dataset; const { id, action, fileId } = e.currentTarget.dataset;
const { cancelCurrentUpload, cancelCurrentFileConversion } = this.props; const { t, cancelCurrentUpload, cancelCurrentFileConversion } = this.props;
return action === "convert" return action === "convert"
? cancelCurrentFileConversion(fileId) ? cancelCurrentFileConversion(fileId)
: cancelCurrentUpload(id); : cancelCurrentUpload(id, t);
}; };
onMediaClick = (id) => { onMediaClick = (id) => {
@ -228,8 +225,6 @@ class FileRow extends Component {
t, t,
item, item,
uploaded, uploaded,
//onMediaClick,
currentFileUploadProgress,
fileIcon, fileIcon,
isMedia, isMedia,
ext, ext,
@ -314,7 +309,7 @@ class FileRow extends Component {
data-id={item.uniqueId} data-id={item.uniqueId}
onClick={this.onCancelCurrentUpload} onClick={this.onCancelCurrentUpload}
> >
<LoadingButton percent={currentFileUploadProgress} /> <LoadingButton item={item} />
</div> </div>
)} )}
{showPasswordInput && ( {showPasswordInput && (
@ -365,7 +360,6 @@ export default inject(
const { canViewedDocs, getIconSrc, isArchive } = settingsStore; const { canViewedDocs, getIconSrc, isArchive } = settingsStore;
const { const {
uploaded, uploaded,
primaryProgressDataStore,
cancelCurrentUpload, cancelCurrentUpload,
cancelCurrentFileConversion, cancelCurrentFileConversion,
setUploadPanelVisible, setUploadPanelVisible,
@ -379,7 +373,6 @@ export default inject(
setMediaViewerData, setMediaViewerData,
setCurrentItem, setCurrentItem,
} = mediaViewerDataStore; } = mediaViewerDataStore;
const { loadingFile: file } = primaryProgressDataStore;
const isMedia = const isMedia =
item.fileInfo?.viewAccessability?.ImageView || item.fileInfo?.viewAccessability?.ImageView ||
@ -390,25 +383,16 @@ export default inject(
const fileIcon = getIconSrc(ext, 24); const fileIcon = getIconSrc(ext, 24);
const loadingFile = !file || !file.uniqueId ? null : file;
const currentFileUploadProgress =
file && loadingFile.uniqueId === item.uniqueId
? loadingFile.percent
: null;
const downloadInCurrentTab = isArchive(ext) || !canViewedDocs(ext); const downloadInCurrentTab = isArchive(ext) || !canViewedDocs(ext);
return { return {
isPersonal: personal, isPersonal: personal,
theme, theme,
currentFileUploadProgress,
uploaded, uploaded,
isMedia, isMedia: !!isMedia,
fileIcon, fileIcon,
ext, ext,
name, name,
loadingFile,
isMediaActive, isMediaActive,
downloadInCurrentTab, downloadInCurrentTab,
removeFileFromList, removeFileFromList,

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import { import {
StyledCircle, StyledCircle,
StyledCircleWrap,
StyledLoadingButton, StyledLoadingButton,
} from "@docspace/common/components/StyledLoadingButton"; } from "@docspace/common/components/StyledLoadingButton";
@ -16,7 +16,6 @@ const LoadingButton = (props) => {
onClick, onClick,
isConversion, isConversion,
inConversion, inConversion,
...rest
} = props; } = props;
const [isAnimation, setIsAnimation] = useState(true); const [isAnimation, setIsAnimation] = useState(true);
@ -61,4 +60,22 @@ const LoadingButton = (props) => {
); );
}; };
export default LoadingButton; export default inject(({ uploadDataStore }, { item }) => {
const { primaryProgressDataStore, isParallel } = uploadDataStore;
const { loadingFile: file } = primaryProgressDataStore;
const loadingFile = !file || !file.uniqueId ? null : file;
const currentFileUploadProgress =
file && loadingFile?.uniqueId === item?.uniqueId
? loadingFile.percent
: null;
return {
percent: isParallel
? item?.percent
? item.percent
: null
: currentFileUploadProgress,
};
})(observer(LoadingButton));

View File

@ -18,7 +18,7 @@ class ConfirmRoute extends React.Component {
} }
componentDidMount() { componentDidMount() {
const { forUnauthorized, history, isAuthenticated } = this.props; const { forUnauthorized, isAuthenticated } = this.props;
if (forUnauthorized && isAuthenticated) { if (forUnauthorized && isAuthenticated) {
this.props.logout(); this.props.logout();
@ -42,18 +42,25 @@ class ConfirmRoute extends React.Component {
.then((validationResult) => { .then((validationResult) => {
switch (validationResult) { switch (validationResult) {
case ValidationResult.Ok: case ValidationResult.Ok:
const confirmHeader = `${confirmLinkData}&${search.slice(1)}`; const confirmHeader = search.slice(1);
const linkData = { const linkData = {
...confirmLinkData, ...confirmLinkData,
confirmHeader, confirmHeader,
}; };
console.log("checkConfirmLink", {
confirmLinkData,
validationResult,
linkData,
});
this.setState({ this.setState({
isLoaded: true, isLoaded: true,
linkData, linkData,
}); });
break; break;
case ValidationResult.Invalid: case ValidationResult.Invalid:
console.error("invlid link"); console.error("invlid link", { confirmLinkData, validationResult });
window.location.href = combineUrl( window.location.href = combineUrl(
window.DocSpaceConfig?.proxy?.url, window.DocSpaceConfig?.proxy?.url,
path, path,
@ -61,7 +68,10 @@ class ConfirmRoute extends React.Component {
); );
break; break;
case ValidationResult.Expired: case ValidationResult.Expired:
console.error("expired link"); console.error("expired link", {
confirmLinkData,
validationResult,
});
window.location.href = combineUrl( window.location.href = combineUrl(
window.DocSpaceConfig?.proxy?.url, window.DocSpaceConfig?.proxy?.url,
path, path,
@ -69,7 +79,10 @@ class ConfirmRoute extends React.Component {
); );
break; break;
default: default:
console.error("unknown link"); console.error("unknown link", {
confirmLinkData,
validationResult,
});
window.location.href = combineUrl( window.location.href = combineUrl(
window.DocSpaceConfig?.proxy?.url, window.DocSpaceConfig?.proxy?.url,
path, path,
@ -79,7 +92,7 @@ class ConfirmRoute extends React.Component {
} }
}) })
.catch((error) => { .catch((error) => {
console.error(error); console.error("FAILED checkConfirmLink", { error, confirmLinkData });
window.location.href = combineUrl( window.location.href = combineUrl(
window.DocSpaceConfig?.proxy?.url, window.DocSpaceConfig?.proxy?.url,
path, path,

View File

@ -51,6 +51,8 @@ export const CategoryType = Object.freeze({
Favorite: 5, Favorite: 5,
Recent: 6, Recent: 6,
Trash: 7, Trash: 7,
Settings: 8,
Accounts: 9,
}); });
/** /**

View File

@ -40,8 +40,9 @@ export const getAccessIcon = (access) => {
}; };
export const getTitleWithoutExst = (item, fromTemplate) => { export const getTitleWithoutExst = (item, fromTemplate) => {
return item.fileExst && !fromTemplate const titleWithoutExst = item.title.split(".").slice(0, -1).join(".");
? item.title.split(".").slice(0, -1).join(".") return titleWithoutExst && item.fileExst && !fromTemplate
? titleWithoutExst
: item.title; : item.title;
}; };

View File

@ -10,26 +10,3 @@ export const thumbnailStatuses = {
}; };
export const ADS_TIMEOUT = 300000; // 5 min export const ADS_TIMEOUT = 300000; // 5 min
export const FilterGroups = Object.freeze({
filterType: "filter-filterType",
filterAuthor: "filter-author",
filterFolders: "filter-folders",
filterContent: "filter-withContent",
roomFilterProviderType: "filter-provider-type",
roomFilterType: "filter-type",
roomFilterSubject: "filter-subject",
roomFilterOwner: "filter-owner",
roomFilterTags: "filter-tags",
roomFilterFolders: "filter-withSubfolders",
roomFilterContent: "filter-content",
});
export const FilterKeys = Object.freeze({
withSubfolders: "withSubfolders",
excludeSubfolders: "excludeSubfolders",
withContent: "withContent",
me: "me",
other: "other",
user: "user",
});

View File

@ -2,7 +2,7 @@ import authStore from "@docspace/common/store/AuthStore";
import { toCommunityHostname, getLanguage } from "@docspace/common/utils"; import { toCommunityHostname, getLanguage } from "@docspace/common/utils";
import history from "@docspace/common/history"; import history from "@docspace/common/history";
import { CategoryType } from "./constants"; import { CategoryType } from "./constants";
import { FolderType } from "@docspace/common/constants"; import { FolderType, ShareAccessRights } from "@docspace/common/constants";
import { translations } from "./autoGeneratedTranslations"; import { translations } from "./autoGeneratedTranslations";
export const setDocumentTitle = (subTitle = null) => { export const setDocumentTitle = (subTitle = null) => {
@ -125,8 +125,12 @@ export const getCategoryType = (location) => {
categoryType = CategoryType.Favorite; categoryType = CategoryType.Favorite;
} else if (pathname.startsWith("/recent")) { } else if (pathname.startsWith("/recent")) {
categoryType = CategoryType.Recent; categoryType = CategoryType.Recent;
} else if (pathname.startsWith("/trash")) { } else if (pathname.startsWith("/files/trash")) {
categoryType = CategoryType.Trash; categoryType = CategoryType.Trash;
} else if (pathname.startsWith("/settings")) {
categoryType = CategoryType.Settings;
} else if (pathname.startsWith("/accounts/filter")) {
categoryType = CategoryType.Accounts;
} }
return categoryType; return categoryType;
@ -186,3 +190,24 @@ export const getCategoryUrl = (categoryType, folderId = null) => {
throw new Error("Unknown category type"); throw new Error("Unknown category type");
} }
}; };
export const filterUserRoleOptions = (
options,
currentUser,
withRemove = false
) => {
let newOptions = [...options];
if (currentUser?.isAdmin || currentUser?.isOwner) {
newOptions = newOptions.filter(
(o) =>
+o.access === ShareAccessRights.RoomManager ||
+o.access === ShareAccessRights.None ||
(withRemove && (o.key === "s2" || o.key === "remove"))
);
return newOptions;
}
return newOptions;
};

View File

@ -20,6 +20,7 @@ const PeopleSection = React.memo(() => {
<PrivateRoute exact path={["/accounts/view/@self"]} component={Profile} /> <PrivateRoute exact path={["/accounts/view/@self"]} component={Profile} />
<PrivateRoute <PrivateRoute
exact exact
withManager
path={["/accounts"]} path={["/accounts"]}
component={HomeRedirectToFilter} component={HomeRedirectToFilter}
/> />

View File

@ -12,6 +12,7 @@ import {
SendInviteDialog, SendInviteDialog,
DeleteUsersDialog, DeleteUsersDialog,
ChangeNameDialog, ChangeNameDialog,
ResetApplicationDialog,
} from "SRC_DIR/components/dialogs"; } from "SRC_DIR/components/dialogs";
const Dialogs = ({ const Dialogs = ({
@ -28,10 +29,12 @@ const Dialogs = ({
disableDialogVisible, disableDialogVisible,
sendInviteDialogVisible, sendInviteDialogVisible,
deleteDialogVisible, deleteDialogVisible,
resetAuthDialogVisible,
changeNameVisible, changeNameVisible,
setChangeNameVisible, setChangeNameVisible,
profile, profile,
resetTfaApp,
}) => { }) => {
return ( return (
<> <>
@ -101,6 +104,15 @@ const Dialogs = ({
fromList fromList
/> />
)} )}
{resetAuthDialogVisible && (
<ResetApplicationDialog
visible={resetAuthDialogVisible}
onClose={closeDialogs}
resetTfaApp={resetTfaApp}
id={data}
/>
)}
</> </>
); );
}; };
@ -121,6 +133,7 @@ export default inject(({ auth, peopleStore }) => {
disableDialogVisible, disableDialogVisible,
sendInviteDialogVisible, sendInviteDialogVisible,
deleteDialogVisible, deleteDialogVisible,
resetAuthDialogVisible,
} = peopleStore.dialogStore; } = peopleStore.dialogStore;
const { user: profile } = auth.userStore; const { user: profile } = auth.userStore;
@ -130,6 +143,10 @@ export default inject(({ auth, peopleStore }) => {
setChangeNameVisible, setChangeNameVisible,
} = peopleStore.targetUserStore; } = peopleStore.targetUserStore;
const { tfaStore } = auth;
const { unlinkApp: resetTfaApp } = tfaStore;
return { return {
changeEmail, changeEmail,
changePassword, changePassword,
@ -145,9 +162,12 @@ export default inject(({ auth, peopleStore }) => {
disableDialogVisible, disableDialogVisible,
sendInviteDialogVisible, sendInviteDialogVisible,
deleteDialogVisible, deleteDialogVisible,
resetAuthDialogVisible,
changeNameVisible, changeNameVisible,
setChangeNameVisible, setChangeNameVisible,
profile, profile,
resetTfaApp,
}; };
})(observer(Dialogs)); })(observer(Dialogs));

View File

@ -40,7 +40,14 @@ const UserContent = ({
t, t,
theme, theme,
}) => { }) => {
const { displayName, email, statusType, role, isVisitor } = item; const {
displayName,
email,
statusType,
role,
isVisitor,
isCollaborator,
} = item;
const nameColor = const nameColor =
statusType === "pending" || statusType === "disabled" statusType === "pending" || statusType === "disabled"
@ -53,6 +60,8 @@ const UserContent = ({
? t("Common:Owner") ? t("Common:Owner")
: role === "admin" : role === "admin"
? t("Common:DocSpaceAdmin") ? t("Common:DocSpaceAdmin")
: isCollaborator
? t("Common:Collaborator")
: isVisitor : isVisitor
? t("Common:User") ? t("Common:User")
: t("Common:RoomAdmin"); : t("Common:RoomAdmin");

View File

@ -142,6 +142,7 @@ const PeopleTableRow = (props) => {
role, role,
isVisitor, isVisitor,
isCollaborator,
} = item; } = item;
const isPending = statusType === "pending" || statusType === "disabled"; const isPending = statusType === "pending" || statusType === "disabled";
@ -168,6 +169,12 @@ const PeopleTableRow = (props) => {
label: t("Common:RoomAdmin"), label: t("Common:RoomAdmin"),
action: "manager", action: "manager",
}; };
const collaboratorOption = {
key: "collaborator",
title: t("Common:Collaborator"),
label: t("Common:Collaborator"),
action: "collaborator",
};
const userOption = { const userOption = {
key: "user", key: "user",
title: t("Common:User"), title: t("Common:User"),
@ -179,10 +186,12 @@ const PeopleTableRow = (props) => {
options.push(managerOption); options.push(managerOption);
if (isCollaborator || isVisitor) options.push(collaboratorOption);
isVisitor && options.push(userOption); isVisitor && options.push(userOption);
return options; return options;
}, [t, isOwner, isVisitor]); }, [t, isOwner, isVisitor, isCollaborator]);
const onAbort = () => { const onAbort = () => {
setIsLoading(false); setIsLoading(false);
@ -227,6 +236,8 @@ const PeopleTableRow = (props) => {
return t("Common:DocSpaceAdmin"); return t("Common:DocSpaceAdmin");
case "manager": case "manager":
return t("Common:RoomAdmin"); return t("Common:RoomAdmin");
case "collaborator":
return t("Common:Collaborator");
case "user": case "user":
return t("Common:User"); return t("Common:User");
} }

View File

@ -11,7 +11,11 @@ import Loaders from "@docspace/common/components/Loaders";
import { withLayoutSize } from "@docspace/common/utils"; import { withLayoutSize } from "@docspace/common/utils";
import withPeopleLoader from "SRC_DIR/HOCs/withPeopleLoader"; import withPeopleLoader from "SRC_DIR/HOCs/withPeopleLoader";
import { EmployeeType, PaymentsType } from "@docspace/common/constants"; import {
EmployeeType,
EmployeeStatus,
PaymentsType,
} from "@docspace/common/constants";
const getStatus = (filterValues) => { const getStatus = (filterValues) => {
const employeeStatus = result( const employeeStatus = result(
@ -69,7 +73,6 @@ const SectionFilterContent = ({
}) => { }) => {
const [selectedFilterValues, setSelectedFilterValues] = React.useState(null); const [selectedFilterValues, setSelectedFilterValues] = React.useState(null);
//TODO: add new options from filter after update backend and fix manager from role
const onFilter = (data) => { const onFilter = (data) => {
const status = getStatus(data); const status = getStatus(data);
@ -80,8 +83,12 @@ const SectionFilterContent = ({
const newFilter = filter.clone(); const newFilter = filter.clone();
if (status === 3) { if (status === 3) {
newFilter.employeeStatus = 2; newFilter.employeeStatus = EmployeeStatus.Disabled;
newFilter.activationStatus = null; newFilter.activationStatus = null;
} else if (status === 2) {
console.log(status);
newFilter.employeeStatus = EmployeeStatus.Active;
newFilter.activationStatus = status;
} else { } else {
newFilter.employeeStatus = null; newFilter.employeeStatus = null;
newFilter.activationStatus = status; newFilter.activationStatus = status;
@ -94,6 +101,7 @@ const SectionFilterContent = ({
newFilter.group = group; newFilter.group = group;
newFilter.payments = payments; newFilter.payments = payments;
//console.log(newFilter);
setIsLoading(true); setIsLoading(true);
fetchPeople(newFilter, true).finally(() => setIsLoading(false)); fetchPeople(newFilter, true).finally(() => setIsLoading(false));
@ -123,7 +131,6 @@ const SectionFilterContent = ({
fetchPeople(newFilter, true).finally(() => setIsLoading(false)); fetchPeople(newFilter, true).finally(() => setIsLoading(false));
}; };
// TODO: change translation keys
const getData = async () => { const getData = async () => {
const statusItems = [ const statusItems = [
{ {
@ -172,6 +179,12 @@ const SectionFilterContent = ({
group: "filter-type", group: "filter-type",
label: t("Common:RoomAdmin"), label: t("Common:RoomAdmin"),
}, },
{
id: "filter_type-room-admin",
key: EmployeeType.Collaborator,
group: "filter-type",
label: t("Common:Collaborator"),
},
{ {
id: "filter_type-user", id: "filter_type-user",
key: EmployeeType.Guest, key: EmployeeType.Guest,
@ -312,6 +325,9 @@ const SectionFilterContent = ({
case EmployeeType.User: case EmployeeType.User:
label = t("Common:RoomAdmin"); label = t("Common:RoomAdmin");
break; break;
case EmployeeType.Collaborator:
label = t("Common:Collaborator");
break;
case EmployeeType.Guest: case EmployeeType.Guest:
label = t("Common:User"); label = t("Common:User");
break; break;

View File

@ -72,6 +72,7 @@ const StyledContainer = styled.div`
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 33px;
display: grid; display: grid;
align-items: center; align-items: center;

View File

@ -42,11 +42,20 @@ const PureHome = ({
setSelectedNode, setSelectedNode,
withPaging, withPaging,
onClickBack,
}) => { }) => {
const { location } = history; const { location } = history;
const { pathname } = location; const { pathname } = location;
//console.log("People Home render"); //console.log("People Home render");
useEffect(() => {
window.addEventListener("popstate", onClickBack);
return () => {
window.removeEventListener("popstate", onClickBack);
};
}, []);
useEffect(() => { useEffect(() => {
if (pathname.indexOf("/accounts/filter") > -1) { if (pathname.indexOf("/accounts/filter") > -1) {
setSelectedNode(["accounts", "filter"]); setSelectedNode(["accounts", "filter"]);
@ -95,6 +104,7 @@ const PureHome = ({
<Section.SectionBody> <Section.SectionBody>
<SectionBodyContent /> <SectionBodyContent />
</Section.SectionBody> </Section.SectionBody>
<Section.InfoPanelHeader> <Section.InfoPanelHeader>
<InfoPanelHeaderContent /> <InfoPanelHeaderContent />
</Section.InfoPanelHeader> </Section.InfoPanelHeader>
@ -120,13 +130,20 @@ PureHome.propTypes = {
const Home = withTranslation("People")(PureHome); const Home = withTranslation("People")(PureHome);
export default inject(({ auth, peopleStore, treeFoldersStore }) => { export default inject(
({ auth, peopleStore, treeFoldersStore, filesActionsStore }) => {
const { settingsStore } = auth; const { settingsStore } = auth;
const { showCatalog, withPaging } = settingsStore; const { showCatalog, withPaging } = settingsStore;
const { usersStore, selectedGroupStore, loadingStore, viewAs } = peopleStore; const {
usersStore,
selectedGroupStore,
loadingStore,
viewAs,
} = peopleStore;
const { getUsersList } = usersStore; const { getUsersList } = usersStore;
const { selectedGroup } = selectedGroupStore; const { selectedGroup } = selectedGroupStore;
const { setSelectedNode } = treeFoldersStore; const { setSelectedNode } = treeFoldersStore;
const { onClickBack } = filesActionsStore;
const { const {
isLoading, isLoading,
setIsLoading, setIsLoading,
@ -151,5 +168,7 @@ export default inject(({ auth, peopleStore, treeFoldersStore }) => {
setMaintenanceExist: auth.settingsStore.setMaintenanceExist, setMaintenanceExist: auth.settingsStore.setMaintenanceExist,
snackbarExist: auth.settingsStore.snackbarExist, snackbarExist: auth.settingsStore.snackbarExist,
withPaging, withPaging,
onClickBack,
}; };
})(observer(withRouter(Home))); }
)(observer(withRouter(Home)));

View File

@ -6,17 +6,18 @@ import Section from "@docspace/common/components/Section";
import { combineUrl } from "@docspace/common/utils"; import { combineUrl } from "@docspace/common/utils";
import tryRedirectTo from "@docspace/common/utils/tryRedirectTo"; import tryRedirectTo from "@docspace/common/utils/tryRedirectTo";
import { inject, observer } from "mobx-react"; import { inject, observer } from "mobx-react";
import { EmployeeActivationStatus } from "@docspace/common/constants";
class ActivateEmail extends React.PureComponent { class ActivateEmail extends React.PureComponent {
componentDidMount() { componentDidMount() {
const { logout, changeEmail, linkData } = this.props; const { logout, updateEmailActivationStatus, linkData } = this.props;
const [email, uid, key] = [ const [email, uid, key] = [
linkData.email, linkData.email,
linkData.uid, linkData.uid,
linkData.confirmHeader, linkData.confirmHeader,
]; ];
logout().then(() => logout().then(() =>
changeEmail(uid, email, key) updateEmailActivationStatus(EmployeeActivationStatus.Activated, uid, key)
.then((res) => { .then((res) => {
tryRedirectTo( tryRedirectTo(
combineUrl( combineUrl(
@ -70,6 +71,6 @@ export default inject(({ auth }) => {
const { logout, userStore } = auth; const { logout, userStore } = auth;
return { return {
logout, logout,
changeEmail: userStore.changeEmail, updateEmailActivationStatus: userStore.updateEmailActivationStatus,
}; };
})(withRouter(observer(ActivateEmailForm))); })(withRouter(observer(ActivateEmailForm)));

View File

@ -91,6 +91,7 @@ const FilesSection = React.memo(() => {
<PrivateRoute <PrivateRoute
restricted restricted
withManager withManager
withCollaborator
path={[ path={[
"/rooms/personal", "/rooms/personal",
"/rooms/personal/filter", "/rooms/personal/filter",
@ -144,7 +145,6 @@ const FilesSection = React.memo(() => {
<PrivateRoute <PrivateRoute
exact exact
restricted restricted
withManager
path={"/settings/admin"} path={"/settings/admin"}
component={Settings} component={Settings}
/> />

View File

@ -100,9 +100,8 @@ class Tile extends React.PureComponent {
}; };
onShowTemplateInfo = () => { onShowTemplateInfo = () => {
if (!this.props.isInfoPanelVisible) { this.onSelectForm();
this.props.setIsInfoPanelVisible(true); if (!this.props.isInfoPanelVisible) this.props.setIsInfoPanelVisible(true);
}
}; };
getOptions = () => ["create", "template-info"]; getOptions = () => ["create", "template-info"];

View File

@ -7,6 +7,7 @@ import getCorrectDate from "@docspace/components/utils/getCorrectDate";
import Link from "@docspace/components/link"; import Link from "@docspace/components/link";
import Text from "@docspace/components/text"; import Text from "@docspace/components/text";
import Tag from "@docspace/components/tag"; import Tag from "@docspace/components/tag";
import { decode } from "he";
import { import {
connectedCloudsTypeTitleTranslation as getProviderTranslation, connectedCloudsTypeTitleTranslation as getProviderTranslation,
@ -227,8 +228,8 @@ class DetailsHelper {
const onOpenUser = () => this.openUser(this.item.createdBy, this.history); const onOpenUser = () => this.openUser(this.item.createdBy, this.history);
return this.personal || this.isVisitor return this.personal || this.isVisitor
? text(decodeString(this.item.createdBy?.displayName)) ? text(decode(this.item.createdBy?.displayName))
: link(decodeString(this.item.createdBy?.displayName), onOpenUser); : link(decode(this.item.createdBy?.displayName), onOpenUser);
}; };
getItemLocation = () => { getItemLocation = () => {
@ -279,8 +280,8 @@ class DetailsHelper {
const onOpenUser = () => this.openUser(this.item.updatedBy, this.history); const onOpenUser = () => this.openUser(this.item.updatedBy, this.history);
return this.personal || this.isVisitor return this.personal || this.isVisitor
? text(decodeString(this.item.updatedBy?.displayName)) ? text(decode(this.item.updatedBy?.displayName))
: link(decodeString(this.item.updatedBy?.displayName), onOpenUser); : link(decode(this.item.updatedBy?.displayName), onOpenUser);
}; };
getItemCreationDate = () => { getItemCreationDate = () => {

View File

@ -21,6 +21,11 @@ class MembersHelper {
label: this.t("Common:RoomAdmin"), label: this.t("Common:RoomAdmin"),
access: ShareAccessRights.RoomManager, access: ShareAccessRights.RoomManager,
}, },
collaborator: {
key: "collaborator",
label: this.t("Common:Collaborator"),
access: ShareAccessRights.Collaborator,
},
viewer: { viewer: {
key: "viewer", key: "viewer",
label: this.t("Translations:RoleViewer"), label: this.t("Translations:RoleViewer"),
@ -69,6 +74,7 @@ class MembersHelper {
case RoomsType.FillingFormsRoom: case RoomsType.FillingFormsRoom:
return [ return [
options.roomAdmin, options.roomAdmin,
options.collaborator,
options.formFiller, options.formFiller,
options.viewer, options.viewer,
...deleteOption, ...deleteOption,
@ -76,6 +82,7 @@ class MembersHelper {
case RoomsType.EditingRoom: case RoomsType.EditingRoom:
return [ return [
options.roomAdmin, options.roomAdmin,
options.collaborator,
options.editor, options.editor,
options.viewer, options.viewer,
...deleteOption, ...deleteOption,
@ -83,16 +90,23 @@ class MembersHelper {
case RoomsType.ReviewRoom: case RoomsType.ReviewRoom:
return [ return [
options.roomAdmin, options.roomAdmin,
options.collaborator,
options.reviewer, options.reviewer,
options.commentator, options.commentator,
options.viewer, options.viewer,
...deleteOption, ...deleteOption,
]; ];
case RoomsType.ReadOnlyRoom: case RoomsType.ReadOnlyRoom:
return [options.roomAdmin, options.viewer, ...deleteOption]; return [
options.roomAdmin,
options.collaborator,
options.viewer,
...deleteOption,
];
case RoomsType.CustomRoom: case RoomsType.CustomRoom:
return [ return [
options.roomAdmin, options.roomAdmin,
options.collaborator,
options.editor, options.editor,
options.formFiller, options.formFiller,
options.reviewer, options.reviewer,

View File

@ -1,9 +1,10 @@
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import { Base } from "@docspace/components/themes"; import { Base } from "@docspace/components/themes";
import { hugeMobile, tablet } from "@docspace/components/utils/device";
const StyledAccountsItemTitle = styled.div` const StyledAccountsItemTitle = styled.div`
min-height: 104px; min-height: 80px;
height: 104px; height: 80px;
max-height: 104px; max-height: 104px;
display: flex; display: flex;
@ -11,8 +12,29 @@ const StyledAccountsItemTitle = styled.div`
justify-content: start; justify-content: start;
gap: 16px; gap: 16px;
position: fixed;
margin-top: -80px;
margin-left: -20px;
width: calc(100% - 40px);
padding: 24px 0 24px 20px;
background: ${(props) => props.theme.infoPanel.backgroundColor};
z-index: 100;
@media ${tablet} {
width: 440px;
padding: 24px 20px 24px 20px;
}
@media (max-width: 549px) {
width: calc(100vw - 69px - 40px);
}
@media ${hugeMobile} {
width: calc(100vw - 32px);
padding: 24px 0 24px 16px;
}
.avatar { .avatar {
padding-top: 24px;
min-width: 80px; min-width: 80px;
} }

View File

@ -7,9 +7,9 @@ const StyledInfoPanelBody = styled.div`
${({ isAccounts }) => ${({ isAccounts }) =>
isAccounts isAccounts
? css` ? css`
padding: 0px 3px 0 20px; padding: 80px 3px 0 20px;
@media ${hugeMobile} { @media ${hugeMobile} {
padding: 0px 8px 0 16px; padding: 80px 8px 0 16px;
} }
` `
: css` : css`

View File

@ -28,7 +28,6 @@ const StyledNoItemContainer = styled.div`
} }
.no-accounts { .no-accounts {
padding-top: 80px;
} }
`; `;

View File

@ -5,11 +5,11 @@ import { ReactSVG } from "react-svg";
import { Text } from "@docspace/components"; import { Text } from "@docspace/components";
import { StyledTitle } from "../../styles/common"; import { StyledTitle } from "../../styles/common";
const GalleryItemTitle = ({ selection, getIcon }) => { const GalleryItemTitle = ({ gallerySelected, getIcon }) => {
return ( return (
<StyledTitle> <StyledTitle>
<ReactSVG className="icon" src={getIcon(32, ".docxf")} /> <ReactSVG className="icon" src={getIcon(32, ".docxf")} />
<Text className="text">{selection?.attributes?.name_form}</Text> <Text className="text">{gallerySelected?.attributes?.name_form}</Text>
</StyledTitle> </StyledTitle>
); );
}; };

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