Merge branch 'develop' into feature/webhooks-ui

This commit is contained in:
Alexey Safronov 2023-03-06 11:21:00 +04:00
commit 913b931544
277 changed files with 6472 additions and 6707 deletions

View File

@ -12,4 +12,4 @@ Write-Host "Run Document server" -ForegroundColor Green
$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 --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 ##
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 --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 ##
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 --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 ##
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"
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"
LOG_FILE = sys.argv[2] if sys.argv[2] else "none"
RUN_FILE = sys.argv[1] if (len(sys.argv) > 1) 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_PORT = os.environ["REDIS_PORT"] if environ.get("REDIS_PORT") else "6379"
@ -84,7 +85,8 @@ class RunServices:
" --log:dir=" + LOG_DIR +\
" --log:name=" + LOG_FILE +\
" core:products:folder=/var/www/products/" +\
" core:products:subfolder=server")
" core:products:subfolder=server" + " " +\
CORE_EVENT_BUS)
else:
os.system("dotnet " + RUN_FILE + " --urls=" + URLS + self.SERVICE_PORT +\
" --\'$STORAGE_ROOT\'=" + APP_STORAGE_ROOT +\
@ -93,7 +95,8 @@ class RunServices:
" --log:name=" + LOG_FILE +\
" --ENVIRONMENT=" + ENV_EXTENSION +\
" core:products:folder=/var/www/products/" +\
" core:products:subfolder=server")
" core:products:subfolder=server" + " " +\
CORE_EVENT_BUS)
def openJsonFile(filePath):
try:

View File

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

View File

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

View File

@ -64,25 +64,26 @@ public abstract class BaseStartup
public virtual void ConfigureServices(IServiceCollection services)
{
services.AddCustomHealthCheck(_configuration);
services.AddCustomHealthCheck(_configuration);
services.AddHttpContextAccessor();
services.AddMemoryCache();
services.AddHttpClient();
services.AddScoped<EFLoggerFactory>();
services.AddBaseDbContextPool<AccountLinkContext>();
services.AddBaseDbContextPool<CoreDbContext>();
services.AddBaseDbContextPool<TenantDbContext>();
services.AddBaseDbContextPool<UserDbContext>();
services.AddBaseDbContextPool<TelegramDbContext>();
services.AddBaseDbContextPool<FirebaseDbContext>();
services.AddBaseDbContextPool<CustomDbContext>();
services.AddBaseDbContextPool<WebstudioDbContext>();
services.AddBaseDbContextPool<InstanceRegistrationContext>();
services.AddBaseDbContextPool<IntegrationEventLogContext>();
services.AddBaseDbContextPool<FeedDbContext>();
services.AddBaseDbContextPool<MessagesContext>();
services.AddBaseDbContextPool<WebhooksDbContext>();
services.AddBaseDbContextPool<AccountLinkContext>()
.AddBaseDbContextPool<CoreDbContext>()
.AddBaseDbContextPool<TenantDbContext>()
.AddBaseDbContextPool<UserDbContext>()
.AddBaseDbContextPool<TelegramDbContext>()
.AddBaseDbContextPool<FirebaseDbContext>()
.AddBaseDbContextPool<CustomDbContext>()
.AddBaseDbContextPool<WebstudioDbContext>()
.AddBaseDbContextPool<InstanceRegistrationContext>()
.AddBaseDbContextPool<IntegrationEventLogContext>()
.AddBaseDbContextPool<FeedDbContext>()
.AddBaseDbContextPool<MessagesContext>()
.AddBaseDbContextPool<WebhooksDbContext>();
if (AddAndUseSession)
{
@ -294,20 +295,35 @@ public abstract class BaseStartup
app.UseLoggerMiddleware();
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()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapHealthChecks("/ready", new HealthCheckOptions
{
Predicate = r => r.Name.Contains("services")
});
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
{
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)

View File

@ -83,7 +83,7 @@ public static class EndpointExtension
"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();

View File

@ -23,67 +23,35 @@
// 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.Api.Core.Core;
public static class CustomHealthCheck
{
{
public static bool Running { get; set;}
static CustomHealthCheck()
{
Running = true;
}
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration)
{
var hcBuilder = services.AddHealthChecks();
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy());
var configurationExtension = new ConfigurationExtension(configuration);
var connectionString = configurationExtension.GetConnectionStrings("default");
var hcBuilder = services.AddHealthChecks();
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"))
{
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" });
}
}
hcBuilder.AddCheck("self", () => Running ? HealthCheckResult.Healthy()
: HealthCheckResult.Unhealthy())
.AddDatabase(configuration)
.AddDistibutedCache(configuration)
.AddMessageQueue(configuration)
.AddSearch(configuration);
return services;
}
public static IHealthChecksBuilder AddDistibutedCache(
this IHealthChecksBuilder hcBuilder, IConfiguration configuration)
{
var redisConfiguration = configuration.GetSection("Redis").Get<RedisConfiguration>();
if (redisConfiguration != null)
@ -97,20 +65,97 @@ public static class CustomHealthCheck
hcBuilder.AddRedis(redisConfiguration.ConfigurationOptions.ToString(),
name: "redis",
tags: new string[] { "redis" },
tags: new string[] { "redis", "services" },
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>();
if (rabbitMQConfiguration != null)
{
hcBuilder.AddRabbitMQ(x => rabbitMQConfiguration.GetConnectionFactory(),
name: "rabbitMQ",
tags: new string[] { "rabbitMQ" },
tags: new string[] { "rabbitMQ", "services" },
timeout: new TimeSpan(0, 0, 15));
}
return services;
}
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 hcBuilder;
}
}

View File

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

View File

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

View File

@ -1,139 +1,140 @@
// (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.Web.Api.Models;
public class EmployeeFullDto : EmployeeDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public List<Contact> Contacts { get; set; }
public ApiDateTime Birthday { get; set; }
public string Sex { get; set; }
public EmployeeStatus Status { get; set; }
public EmployeeActivationStatus ActivationStatus { get; set; }
public ApiDateTime Terminated { get; set; }
public string Department { get; set; }
public ApiDateTime WorkFrom { get; set; }
public List<GroupSummaryDto> Groups { get; set; }
public string Location { get; set; }
public string Notes { get; set; }
public string AvatarMax { get; set; }
public string AvatarMedium { get; set; }
public string Avatar { get; set; }
public bool IsAdmin { get; set; }
public bool IsLDAP { get; set; }
public List<string> ListAdminModules { get; set; }
public bool IsOwner { get; set; }
public bool IsVisitor { get; set; }
public string CultureName { get; set; }
public string MobilePhone { get; set; }
public MobilePhoneActivationStatus MobilePhoneActivationStatus { get; set; }
// (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.Web.Api.Models;
public class EmployeeFullDto : EmployeeDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public List<Contact> Contacts { get; set; }
public ApiDateTime Birthday { get; set; }
public string Sex { get; set; }
public EmployeeStatus Status { get; set; }
public EmployeeActivationStatus ActivationStatus { get; set; }
public ApiDateTime Terminated { get; set; }
public string Department { get; set; }
public ApiDateTime WorkFrom { get; set; }
public List<GroupSummaryDto> Groups { get; set; }
public string Location { get; set; }
public string Notes { get; set; }
public string AvatarMax { get; set; }
public string AvatarMedium { get; set; }
public string Avatar { get; set; }
public bool IsAdmin { get; set; }
public bool IsLDAP { get; set; }
public List<string> ListAdminModules { get; set; }
public bool IsOwner { get; set; }
public bool IsVisitor { get; set; }
public bool IsCollaborator { get; set; }
public string CultureName { get; set; }
public string MobilePhone { get; set; }
public MobilePhoneActivationStatus MobilePhoneActivationStatus { get; set; }
public bool IsSSO { get; set; }
public DarkThemeSettingsEnum? Theme { get; set; }
public long QuotaLimit { get; set; }
public double UsedSpace { get; set; }
public static new EmployeeFullDto GetSample()
{
return new EmployeeFullDto
{
Avatar = "url to big avatar",
AvatarSmall = "url to small avatar",
AvatarMax = "url to max avatar",
Contacts = new List<Contact> { Contact.GetSample() },
Email = "my@gmail.com",
FirstName = "Mike",
Id = Guid.Empty,
IsAdmin = false,
ListAdminModules = new List<string> { "projects", "crm" },
UserName = "Mike.Zanyatski",
LastName = "Zanyatski",
Title = "Manager",
Groups = new List<GroupSummaryDto> { GroupSummaryDto.GetSample() },
AvatarMedium = "url to medium avatar",
Birthday = ApiDateTime.GetSample(),
Department = "Marketing",
Location = "Palo Alto",
Notes = "Notes to worker",
Sex = "male",
Status = EmployeeStatus.Active,
WorkFrom = ApiDateTime.GetSample(),
Terminated = ApiDateTime.GetSample(),
CultureName = "en-EN",
IsLDAP = false,
IsSSO = false
};
}
}
[Scope]
public class EmployeeFullDtoHelper : EmployeeDtoHelper
{
private readonly ApiContext _context;
private readonly WebItemSecurity _webItemSecurity;
public static new EmployeeFullDto GetSample()
{
return new EmployeeFullDto
{
Avatar = "url to big avatar",
AvatarSmall = "url to small avatar",
AvatarMax = "url to max avatar",
Contacts = new List<Contact> { Contact.GetSample() },
Email = "my@gmail.com",
FirstName = "Mike",
Id = Guid.Empty,
IsAdmin = false,
ListAdminModules = new List<string> { "projects", "crm" },
UserName = "Mike.Zanyatski",
LastName = "Zanyatski",
Title = "Manager",
Groups = new List<GroupSummaryDto> { GroupSummaryDto.GetSample() },
AvatarMedium = "url to medium avatar",
Birthday = ApiDateTime.GetSample(),
Department = "Marketing",
Location = "Palo Alto",
Notes = "Notes to worker",
Sex = "male",
Status = EmployeeStatus.Active,
WorkFrom = ApiDateTime.GetSample(),
Terminated = ApiDateTime.GetSample(),
CultureName = "en-EN",
IsLDAP = false,
IsSSO = false
};
}
}
[Scope]
public class EmployeeFullDtoHelper : EmployeeDtoHelper
{
private readonly ApiContext _context;
private readonly WebItemSecurity _webItemSecurity;
private readonly ApiDateTimeHelper _apiDateTimeHelper;
private readonly WebItemManager _webItemManager;
private readonly SettingsManager _settingsManager;
private readonly IQuotaService _quotaService;
public EmployeeFullDtoHelper(
ApiContext context,
UserManager userManager,
UserPhotoManager userPhotoManager,
WebItemSecurity webItemSecurity,
CommonLinkUtility commonLinkUtility,
DisplayUserSettingsHelper displayUserSettingsHelper,
public EmployeeFullDtoHelper(
ApiContext context,
UserManager userManager,
UserPhotoManager userPhotoManager,
WebItemSecurity webItemSecurity,
CommonLinkUtility commonLinkUtility,
DisplayUserSettingsHelper displayUserSettingsHelper,
ApiDateTimeHelper apiDateTimeHelper,
WebItemManager webItemManager,
SettingsManager settingsManager,
IQuotaService quotaService)
: base(context, displayUserSettingsHelper, userPhotoManager, commonLinkUtility, userManager)
{
_context = context;
_webItemSecurity = webItemSecurity;
IQuotaService quotaService)
: base(context, displayUserSettingsHelper, userPhotoManager, commonLinkUtility, userManager)
{
_context = context;
_webItemSecurity = webItemSecurity;
_apiDateTimeHelper = apiDateTimeHelper;
_webItemManager = webItemManager;
_settingsManager = settingsManager;
_quotaService = quotaService;
}
public static Expression<Func<User, UserInfo>> GetExpression(ApiContext apiContext)
{
if (apiContext?.Fields == null)
{
return null;
}
var newExpr = Expression.New(typeof(UserInfo));
//i => new UserInfo { ID = i.id }
var parameter = Expression.Parameter(typeof(User), "i");
}
public static Expression<Func<User, UserInfo>> GetExpression(ApiContext apiContext)
{
if (apiContext?.Fields == null)
{
return null;
}
var newExpr = Expression.New(typeof(UserInfo));
//i => new UserInfo { ID = i.id }
var parameter = Expression.Parameter(typeof(User), "i");
var bindExprs = new List<MemberAssignment>();
//foreach (var field in apiContext.Fields)
@ -151,12 +152,12 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
bindExprs.Add(Expression.Bind(typeof(UserInfo).GetProperty("Id"),
Expression.Property(parameter, typeof(User).GetProperty("Id"))));
}
var body = Expression.MemberInit(newExpr, bindExprs);
var lambda = Expression.Lambda<Func<User, UserInfo>>(body, parameter);
return lambda;
}
var body = Expression.MemberInit(newExpr, bindExprs);
var lambda = Expression.Lambda<Func<User, UserInfo>>(body, parameter);
return lambda;
}
public async Task<EmployeeFullDto> GetSimple(UserInfo userInfo)
{
var result = new EmployeeFullDto
@ -176,97 +177,97 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
return result;
}
public async Task<EmployeeFullDto> GetFull(UserInfo userInfo)
{
var result = new EmployeeFullDto
{
UserName = userInfo.UserName,
FirstName = userInfo.FirstName,
LastName = userInfo.LastName,
Birthday = _apiDateTimeHelper.Get(userInfo.BirthDate),
Status = userInfo.Status,
ActivationStatus = userInfo.ActivationStatus & ~EmployeeActivationStatus.AutoGenerated,
Terminated = _apiDateTimeHelper.Get(userInfo.TerminatedDate),
WorkFrom = _apiDateTimeHelper.Get(userInfo.WorkFromDate),
Email = userInfo.Email,
IsVisitor = _userManager.IsUser(userInfo),
IsAdmin = _userManager.IsDocSpaceAdmin(userInfo),
IsOwner = userInfo.IsOwner(_context.Tenant),
IsLDAP = userInfo.IsLDAP(),
public async Task<EmployeeFullDto> GetFull(UserInfo userInfo)
{
var result = new EmployeeFullDto
{
UserName = userInfo.UserName,
FirstName = userInfo.FirstName,
LastName = userInfo.LastName,
Birthday = _apiDateTimeHelper.Get(userInfo.BirthDate),
Status = userInfo.Status,
ActivationStatus = userInfo.ActivationStatus & ~EmployeeActivationStatus.AutoGenerated,
Terminated = _apiDateTimeHelper.Get(userInfo.TerminatedDate),
WorkFrom = _apiDateTimeHelper.Get(userInfo.WorkFromDate),
Email = userInfo.Email,
IsVisitor = _userManager.IsUser(userInfo),
IsAdmin = _userManager.IsDocSpaceAdmin(userInfo),
IsOwner = userInfo.IsOwner(_context.Tenant),
IsCollaborator = _userManager.IsCollaborator(userInfo),
IsLDAP = userInfo.IsLDAP(),
IsSSO = userInfo.IsSSO()
};
await Init(result, userInfo);
var quotaSettings = _settingsManager.Load<TenantUserQuotaSettings>();
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));
var userQuotaSettings = _settingsManager.LoadForUser<UserQuotaSettings>(userInfo);
var userQuotaSettings = _settingsManager.Load<UserQuotaSettings>(userInfo);
result.QuotaLimit = userQuotaSettings != null ? userQuotaSettings.UserQuota : quotaSettings.DefaultUserQuota;
}
if (userInfo.Sex.HasValue)
{
result.Sex = userInfo.Sex.Value ? "male" : "female";
}
if (!string.IsNullOrEmpty(userInfo.Location))
{
result.Location = userInfo.Location;
}
if (!string.IsNullOrEmpty(userInfo.Notes))
{
result.Notes = userInfo.Notes;
}
if (!string.IsNullOrEmpty(userInfo.MobilePhone))
{
result.MobilePhone = userInfo.MobilePhone;
}
result.MobilePhoneActivationStatus = userInfo.MobilePhoneActivationStatus;
if (!string.IsNullOrEmpty(userInfo.CultureName))
{
result.CultureName = userInfo.CultureName;
}
if (userInfo.Sex.HasValue)
{
result.Sex = userInfo.Sex.Value ? "male" : "female";
}
if (!string.IsNullOrEmpty(userInfo.Location))
{
result.Location = userInfo.Location;
}
if (!string.IsNullOrEmpty(userInfo.Notes))
{
result.Notes = userInfo.Notes;
}
if (!string.IsNullOrEmpty(userInfo.MobilePhone))
{
result.MobilePhone = userInfo.MobilePhone;
}
result.MobilePhoneActivationStatus = userInfo.MobilePhoneActivationStatus;
if (!string.IsNullOrEmpty(userInfo.CultureName))
{
result.CultureName = userInfo.CultureName;
}
FillConacts(result, userInfo);
FillGroups(result, userInfo);
var cacheKey = Math.Abs(userInfo.LastModified.GetHashCode());
if (_context.Check("avatarMax"))
{
result.AvatarMax = await _userPhotoManager.GetMaxPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (_context.Check("avatarMedium"))
{
result.AvatarMedium = await _userPhotoManager.GetMediumPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (_context.Check("avatar"))
{
result.Avatar = await _userPhotoManager.GetBigPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (_context.Check("listAdminModules"))
{
if (_context.Check("avatarMax"))
{
result.AvatarMax = await _userPhotoManager.GetMaxPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (_context.Check("avatarMedium"))
{
result.AvatarMedium = await _userPhotoManager.GetMediumPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (_context.Check("avatar"))
{
result.Avatar = await _userPhotoManager.GetBigPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (_context.Check("listAdminModules"))
{
var listAdminModules = userInfo.GetListAdminModules(_webItemSecurity, _webItemManager);
if (listAdminModules.Count > 0)
{
result.ListAdminModules = listAdminModules;
}
}
return result;
if (listAdminModules.Count > 0)
{
result.ListAdminModules = listAdminModules;
}
}
return result;
}
private void FillGroups(EmployeeFullDto result, UserInfo userInfo)
{
@ -289,27 +290,27 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
result.Department = "";
}
}
private void FillConacts(EmployeeFullDto employeeWraperFull, UserInfo userInfo)
{
if (userInfo.ContactsList == null)
{
return;
}
var contacts = new List<Contact>();
for (var i = 0; i < userInfo.ContactsList.Count; i += 2)
{
if (i + 1 < userInfo.ContactsList.Count)
{
contacts.Add(new Contact(userInfo.ContactsList[i], userInfo.ContactsList[i + 1]));
}
private void FillConacts(EmployeeFullDto employeeWraperFull, UserInfo userInfo)
{
if (userInfo.ContactsList == null)
{
return;
}
if (contacts.Count > 0)
{
employeeWraperFull.Contacts = contacts;
}
}
var contacts = new List<Contact>();
for (var i = 0; i < userInfo.ContactsList.Count; i += 2)
{
if (i + 1 < userInfo.ContactsList.Count)
{
contacts.Add(new Contact(userInfo.ContactsList[i], userInfo.ContactsList[i + 1]));
}
}
if (contacts.Count > 0)
{
employeeWraperFull.Contacts = contacts;
}
}
}

View File

@ -106,3 +106,4 @@ global using RabbitMQ.Client.Events;
global using StackExchange.Redis.Extensions.Core.Abstractions;
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 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 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"), "Visitor");
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 User = new Role(new Guid("aced04fa-dd96-4b35-af3e-346bf1eb972d"), "User");
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 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,16 +39,17 @@ public class SubscriptionManager
{
Constants.DocSpaceAdmin.ID,
Constants.Everyone.ID,
Constants.RoomAdmin.ID
Constants.RoomAdmin.ID,
Constants.Collaborator.ID,
};
public SubscriptionManager(CachedSubscriptionService service, TenantManager tenantManager, ICache cache)
{
_service = service ?? throw new ArgumentNullException("subscriptionManager");
_tenantManager = tenantManager;
_cache = cache;
}
}
public void Subscribe(string sourceID, string actionID, string objectID, string recipientID)
{
var s = new SubscriptionRecord
@ -79,7 +80,7 @@ public class SubscriptionManager
_service.SaveSubscription(s);
}
public void Unsubscribe(string sourceID, string actionID, string objectID, string recipientID)
{
var s = new SubscriptionRecord
@ -110,17 +111,17 @@ public class SubscriptionManager
_service.SaveSubscription(s);
}
public void UnsubscribeAll(string sourceID, string actionID, string objectID)
{
_service.RemoveSubscriptions(GetTenant(), sourceID, actionID, objectID);
}
public void UnsubscribeAll(string sourceID, string actionID)
{
_service.RemoveSubscriptions(GetTenant(), sourceID, actionID);
}
public string[] GetSubscriptionMethod(string sourceID, string actionID, string recipientID)
{
IEnumerable<SubscriptionMethod> methods;
@ -144,27 +145,27 @@ public class SubscriptionManager
return m != null ? m.Methods : Array.Empty<string>();
}
public string[] GetRecipients(string sourceID, string actionID, string objectID)
{
return _service.GetRecipients(GetTenant(), sourceID, actionID, objectID);
}
public object GetSubscriptionRecord(string sourceID, string actionID, string recipientID, string objectID)
{
return _service.GetSubscription(GetTenant(), sourceID, actionID, recipientID, objectID);
}
public string[] GetSubscriptions(string sourceID, string actionID, string recipientID, bool checkSubscribe = true)
{
return _service.GetSubscriptions(GetTenant(), sourceID, actionID, recipientID, checkSubscribe);
}
public bool IsUnsubscribe(string sourceID, string recipientID, string actionID, string objectID)
{
return _service.IsUnsubscribe(GetTenant(), sourceID, actionID, recipientID, objectID);
}
public void UpdateSubscriptionMethod(string sourceID, string actionID, string recipientID, string[] senderNames)
{
var m = new SubscriptionMethod
@ -215,5 +216,5 @@ public class SubscriptionManager
private int GetTenant()
{
return _tenantManager.GetCurrentTenant().Id;
}
}
}
}

View File

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

View File

@ -61,7 +61,7 @@ public class UserManager
private readonly CardDavAddressbook _cardDavAddressbook;
private readonly ILogger<UserManager> _log;
private readonly ICache _cache;
private readonly TenantQuotaFeatureCheckerCount<CountRoomAdminFeature> _countRoomAdminChecker;
private readonly TenantQuotaFeatureCheckerCount<CountPaidUserFeature> _countPaidUserChecker;
private readonly TenantQuotaFeatureCheckerCount<CountUserFeature> _activeUsersFeatureChecker;
private readonly Constants _constants;
private readonly UserFormatter _userFormatter;
@ -86,7 +86,7 @@ public class UserManager
CardDavAddressbook cardDavAddressbook,
ILogger<UserManager> log,
ICache cache,
TenantQuotaFeatureCheckerCount<CountRoomAdminFeature> countRoomAdrminChecker,
TenantQuotaFeatureCheckerCount<CountPaidUserFeature> countPaidUserChecker,
TenantQuotaFeatureCheckerCount<CountUserFeature> activeUsersFeatureChecker,
UserFormatter userFormatter
)
@ -102,7 +102,7 @@ public class UserManager
_cardDavAddressbook = cardDavAddressbook;
_log = log;
_cache = cache;
_countRoomAdminChecker = countRoomAdrminChecker;
_countPaidUserChecker = countPaidUserChecker;
_activeUsersFeatureChecker = activeUsersFeatureChecker;
_constants = _userManagerConstants.Constants;
_userFormatter = userFormatter;
@ -120,7 +120,7 @@ public class UserManager
CardDavAddressbook cardDavAddressbook,
ILogger<UserManager> log,
ICache cache,
TenantQuotaFeatureCheckerCount<CountRoomAdminFeature> tenantQuotaFeatureChecker,
TenantQuotaFeatureCheckerCount<CountPaidUserFeature> tenantQuotaFeatureChecker,
TenantQuotaFeatureCheckerCount<CountUserFeature> activeUsersFeatureChecker,
IHttpContextAccessor httpContextAccessor,
UserFormatter userFormatter)
@ -157,10 +157,16 @@ public class UserManager
switch (type)
{
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;
case EmployeeType.User:
users = users.Where(u => this.IsUser(u));
users = users.Where(this.IsUser);
break;
}
@ -354,15 +360,14 @@ public class UserManager
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))
{
return SystemUsers[u.Id];
}
_permissionContext.DemandPermissions(new UserSecurityProvider(u.Id, isVisitor ? EmployeeType.User : EmployeeType.RoomAdmin),
Constants.Action_AddRemoveUser);
_permissionContext.DemandPermissions(new UserSecurityProvider(u.Id, type), Constants.Action_AddRemoveUser);
if (!_coreBaseSettings.Personal)
{
@ -379,13 +384,13 @@ public class UserManager
throw new InvalidOperationException("User already exist.");
}
if (isVisitor)
if (type is EmployeeType.User)
{
await _activeUsersFeatureChecker.CheckAppend();
}
else
else if (paidUserQuotaCheck)
{
await _countRoomAdminChecker.CheckAppend();
await _countPaidUserChecker.CheckAppend();
}
var newUser = _userService.SaveUser(_tenantManager.GetCurrentTenant().Id, u);
@ -886,16 +891,23 @@ public class UserManager
}
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)
{
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)
{
return !user;
return !isUser && !isCollaborator;
}
}

View File

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

View File

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

View File

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

View File

@ -227,7 +227,8 @@ public class EFUserService : IUserService
if (sortBy == "type")
{
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
from @group in joinedGroup.DefaultIfEmpty()
select new { user, @group };
@ -235,12 +236,18 @@ public class EFUserService : IUserService
if (sortOrderAsc)
{
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
{
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
@ -300,33 +307,29 @@ public class EFUserService : IUserService
using var userDbContext = _dbContextFactory.CreateDbContext();
using var tr = userDbContext.Database.BeginTransaction();
userDbContext.Acl.RemoveRange(userDbContext.Acl.Where(r => r.Tenant == tenant && ids.Any(i => i == r.Subject)));
userDbContext.Subscriptions.RemoveRange(userDbContext.Subscriptions.Where(r => r.Tenant == tenant && stringIds.Any(i => i == r.Recipient)));
userDbContext.SubscriptionMethods.RemoveRange(userDbContext.SubscriptionMethods.Where(r => r.Tenant == tenant && stringIds.Any(i => i == r.Recipient)));
userDbContext.Acl.Where(r => r.Tenant == tenant && ids.Any(i => i == r.Subject)).ExecuteDelete();
userDbContext.Subscriptions.Where(r => r.Tenant == tenant && stringIds.Any(i => i == r.Recipient)).ExecuteDelete();
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 groups = userDbContext.Groups.Where(r => r.Tenant == tenant && ids.Any(i => i == r.Id));
if (immediate)
{
userDbContext.UserGroups.RemoveRange(userGroups);
userDbContext.Groups.RemoveRange(groups);
userGroups.ExecuteDelete();
groups.ExecuteDelete();
}
else
{
foreach (var ug in userGroups)
{
ug.Removed = true;
ug.LastModified = DateTime.UtcNow;
}
foreach (var g in groups)
{
g.Removed = true;
g.LastModified = DateTime.UtcNow;
}
userGroups.ExecuteUpdate(ug => ug
.SetProperty(p => p.Removed, true)
.SetProperty(p => p.LastModified, DateTime.UtcNow));
groups.ExecuteUpdate(g => g
.SetProperty(p => p.Removed, true)
.SetProperty(p => p.LastModified, DateTime.UtcNow));
}
userDbContext.SaveChanges();
tr.Commit();
});
}
@ -346,10 +349,10 @@ public class EFUserService : IUserService
using var userDbContext = _dbContextFactory.CreateDbContext();
using var tr = userDbContext.Database.BeginTransaction();
userDbContext.Acl.RemoveRange(userDbContext.Acl.Where(r => r.Tenant == tenant && r.Subject == id));
userDbContext.Subscriptions.RemoveRange(userDbContext.Subscriptions.Where(r => r.Tenant == tenant && r.Recipient == id.ToString()));
userDbContext.SubscriptionMethods.RemoveRange(userDbContext.SubscriptionMethods.Where(r => r.Tenant == tenant && r.Recipient == id.ToString()));
userDbContext.Photos.RemoveRange(userDbContext.Photos.Where(r => r.Tenant == tenant && r.UserId == id));
userDbContext.Acl.Where(r => r.Tenant == tenant && r.Subject == id).ExecuteDelete();
userDbContext.Subscriptions.Where(r => r.Tenant == tenant && r.Recipient == id.ToString()).ExecuteDelete();
userDbContext.SubscriptionMethods.Where(r => r.Tenant == tenant && r.Recipient == id.ToString()).ExecuteDelete();
userDbContext.Photos.Where(r => r.Tenant == tenant && r.UserId == id).ExecuteDelete();
var userGroups = userDbContext.UserGroups.Where(r => r.Tenant == tenant && r.Userid == id);
var users = userDbContext.Users.Where(r => r.Tenant == tenant && r.Id == id);
@ -357,29 +360,24 @@ public class EFUserService : IUserService
if (immediate)
{
userDbContext.UserGroups.RemoveRange(userGroups);
userDbContext.Users.RemoveRange(users);
userDbContext.UserSecurity.RemoveRange(userSecurity);
userGroups.ExecuteDelete();
users.ExecuteDelete();
userSecurity.ExecuteDelete();
}
else
{
foreach (var ug in userGroups)
{
ug.Removed = true;
ug.LastModified = DateTime.UtcNow;
}
userGroups.ExecuteUpdate(ug => ug
.SetProperty(p => p.Removed, true)
.SetProperty(p => p.LastModified, DateTime.UtcNow));
foreach (var u in users)
{
u.Removed = true;
u.Status = EmployeeStatus.Terminated;
u.TerminatedDate = DateTime.UtcNow;
u.LastModified = DateTime.UtcNow;
}
users.ExecuteUpdate(ug => ug
.SetProperty(p => p.Removed, true)
.SetProperty(p => p.LastModified, DateTime.UtcNow)
.SetProperty(p => p.TerminatedDate, DateTime.UtcNow)
.SetProperty(p => p.Status, EmployeeStatus.Terminated)
);
}
userDbContext.SaveChanges();
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);
if (immediate)
{
userDbContext.UserGroups.RemoveRange(userGroups);
userGroups.ExecuteDelete();
}
else
{
foreach (var u in userGroups)
{
u.LastModified = DateTime.UtcNow;
u.Removed = true;
}
userGroups.ExecuteUpdate(ug => ug
.SetProperty(p => p.Removed, true)
.SetProperty(p => p.LastModified, DateTime.UtcNow));
}
var user = userDbContext.Users.First(r => r.Tenant == tenant && r.Id == userId);
user.LastModified = DateTime.UtcNow;
userDbContext.SaveChanges();
@ -749,4 +746,4 @@ public class DbUserSecurity
{
public User User { get; set; }
public UserSecurity UserSecurity { get; set; }
}
}

View File

@ -39,7 +39,7 @@ public class HostedSolution
internal UserFormatter UserFormatter { get; set; }
internal TenantManager ClientTenantManager { get; set; }
internal TenantUtil TenantUtil { get; set; }
internal DbSettingsManager SettingsManager { get; set; }
internal SettingsManager SettingsManager { get; set; }
internal CoreSettings CoreSettings { get; set; }
public string Region { get; private set; }
@ -51,7 +51,7 @@ public class HostedSolution
UserFormatter userFormatter,
TenantManager clientTenantManager,
TenantUtil tenantUtil,
DbSettingsManager settingsManager,
SettingsManager settingsManager,
CoreSettings coreSettings)
{
TenantService = tenantService;
@ -196,9 +196,9 @@ public class HostedSolution
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 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);
}

View File

@ -28,8 +28,8 @@ namespace ASC.Core.Common.Log;
internal static partial class DbSettingsManagerLogger
{
[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")]
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,
DocumentsStoreForcesave = 5048,
DocumentsUploadingFormatsSettingsUpdated = 5033,
DocumentsExternalShareSettingsUpdated = 5069, // last
DocumentsExternalShareSettingsUpdated = 5069,
DocumentsKeepNewFileNameSettingsUpdated = 5083, // last
FileConverted = 5035,

View File

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

View File

@ -1,36 +1,36 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Core.Common.Quota.Features;
public class CountRoomAdminFeature : TenantQuotaFeatureCount
{
public override bool Paid { get => true; }
public override string Name { get => "manager"; }
public CountRoomAdminFeature(TenantQuota tenantQuota) : base(tenantQuota)
{
}
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Core.Common.Quota.Features;
public class CountPaidUserFeature : TenantQuotaFeatureCount
{
public override bool Paid { get => true; }
public override string Name { get => "manager"; }
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
// 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;
[Scope]
@ -50,6 +52,11 @@ class RoleProvider : IRoleProvider
.ToList();
}
}
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;
}
@ -75,4 +82,4 @@ class RoleProvider : IRoleProvider
return roles;
}
}
}

View File

@ -31,23 +31,30 @@ namespace ASC.Core.Common.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 Rule(UserConstants.Action_AddRemoveUser.ID),
new(UserConstants.Action_EditGroups.ID, Constants.User),
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 Rule(UserConstants.Action_AddRemoveUser.ID),
new(UserConstants.Action_EditGroups.ID, Constants.User),
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
// 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;
public class UserGroupObject : SecurityObject
{
private ISubject User { get; set; }
private Guid GroupId { get; set; }
private readonly Guid _groupId;
public UserGroupObject(ISubject user, Guid groupId)
{
SecurityId = user.ID;
User = user;
GroupId = groupId;
_groupId = groupId;
ObjectType = typeof(UserGroupObject);
FullId = $"{ObjectType.FullName}|{User.ID}|{GroupId}";
FullId = $"{ObjectType.FullName}|{user.ID}|{_groupId}";
}
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()
{
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.RoomAdmin => new[] { AuthConstants.RoomAdmin },
EmployeeType.Collaborator => new[] { AuthConstants.Collaborator },
EmployeeType.User => new[] { AuthConstants.User },
_ => Array.Empty<IRole>(),
};

View File

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

View File

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

View File

@ -24,9 +24,9 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Action = ASC.Common.Security.Authorizing.Action;
using AuthConst = ASC.Common.Security.Authorizing.Constants;
using Action = ASC.Common.Security.Authorizing.Action;
using AuthConst = ASC.Common.Security.Authorizing.Constants;
namespace ASC.Core.Users;
[Singletone]
@ -59,7 +59,7 @@ public sealed class Constants
private readonly IConfiguration _configuration;
#region system group and category groups
#region system group and category groups
public static readonly Guid SysGroupCategoryId = new Guid("{7717039D-FBE9-45ad-81C1-68A1AA10CE1F}");
@ -87,12 +87,19 @@ public sealed class Constants
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[]
{
GroupEveryone,
GroupUser,
GroupManager,
GroupAdmin,
{
GroupEveryone,
GroupUser,
GroupManager,
GroupAdmin,
GroupCollaborator,
};
public static readonly UserInfo LostUser = new UserInfo
@ -119,10 +126,10 @@ public sealed class Constants
Name = "Unknown"
};
#endregion
#endregion
#region authorization rules module to work with users
#region authorization rules module to work with users
public static readonly Action Action_EditUser = new Action(
new Guid("{EF5E6790-F346-4b6e-B662-722BC28CB0DB}"),
@ -136,5 +143,5 @@ public sealed class Constants
new Guid("{1D4FEEAC-0BF3-4aa9-B096-6D6B104B79B5}"),
"Edit categories and groups");
#endregion
}
#endregion
}

View File

@ -40,7 +40,12 @@ public static class UserExtensions
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)
@ -65,6 +70,17 @@ public static class UserExtensions
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)
{
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)
{
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";
@ -146,4 +170,4 @@ public static class UserExtensions
ui.ContactsList = newContacts;
}
}
}

View File

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

View File

@ -40,7 +40,7 @@ public class Helpers
ConfigurationConstants.CoreSystem.ID,
ConfigurationConstants.Guest.ID,
UserConstants.LostUser.Id
};
};
private readonly Guid[] _systemGroups = new[]
{
@ -50,6 +50,7 @@ public class Helpers
UserConstants.GroupEveryone.ID,
UserConstants.GroupUser.ID,
UserConstants.GroupManager.ID,
UserConstants.GroupCollaborator.ID,
new Guid("{EA942538-E68E-4907-9394-035336EE0BA8}"), //community product
new Guid("{1e044602-43b5-4d79-82f3-fd6208a11960}"), //projects product
new Guid("{6743007C-6F95-4d20-8C88-A8601CE5E76D}"), //crm product
@ -59,7 +60,7 @@ public class Helpers
new Guid("{32D24CB5-7ECE-4606-9C94-19216BA42086}"), //calendar product
new Guid("{37620AE5-C40B-45ce-855A-39DD7D76A1FA}"), //birthdays product
new Guid("{BF88953E-3C43-4850-A3FB-B1E43AD53A3E}") //talk product
};
};
public Helpers(InstanceCrypto instanceCrypto)
{

View File

@ -117,14 +117,17 @@ public class CommonChunkedUploadSession : ICloneable
switch (value.ValueKind)
{
case JsonValueKind.String:
newItems.Add(item.Key, item.Value.ToString());
newItems.Add(item.Key, item.Value.ToString());
break;
case JsonValueKind.Number:
newItems.Add(item.Key, Int32.Parse(item.Value.ToString()));
break;
case JsonValueKind.Array:
newItems.Add(item.Key, value.EnumerateArray().Select(o => o.ToString()).ToList());
break;
newItems.Add(item.Key, value.EnumerateArray().Select(o => o.ToString()).ToList());
break;
case JsonValueKind.Object:
newItems.Add(item.Key, JsonSerializer.Deserialize<Dictionary<int, string>>(item.Value.ToString()));
break;
default:
newItems.Add(item.Key, item.Value);
break;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,7 +69,13 @@ public class DbWorker
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);
await webhooksDbContext.SaveChangesAsync();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
{
"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",
"RoleCommentator": "Commentator",
"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.",
"RoleEditor": "Editor",
"RoleEditorDescription": "Operations with existing files: viewing, editing, form filling, reviewing, commenting.",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,8 @@ import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
//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();

View File

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

View File

@ -86,54 +86,15 @@ const ArticleBodyContent = (props) => {
? RoomSearchArea.Archive
: RoomSearchArea.Active;
fetchRooms(folderId, filter)
.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) {
setIsLoading(false);
} else {
hideLoader();
}
});
fetchRooms(folderId, filter).finally(() => {
if (filesSection) {
setIsLoading(false);
} else {
hideLoader();
}
});
} else {
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))
.finally(() => {
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 PersonAdminReactSvgUrl from "PUBLIC_DIR/images/person.admin.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 InviteAgainReactSvgUrl from "PUBLIC_DIR/images/invite.again.react.svg?url";
import React from "react";
@ -29,7 +30,7 @@ import { Events, EmployeeType } from "@docspace/common/constants";
import { getMainButtonItems } from "SRC_DIR/helpers/plugins";
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 { resendInvitesAgain } from "@docspace/common/api/people";
@ -38,30 +39,36 @@ const StyledButton = styled(Button)`
font-weight: 700;
font-size: 16px;
padding: 0;
opacity: 1;
opacity: ${(props) => (props.isDisabled ? 0.6 : 1)};
background-color: ${({ currentColorScheme }) =>
currentColorScheme.main.accent} !important;
background: ${({ currentColorScheme }) => currentColorScheme.main.accent};
border: ${({ currentColorScheme }) => currentColorScheme.main.accent};
:hover {
background-color: ${({ currentColorScheme }) =>
currentColorScheme.main.accent};
opacity: 0.85;
background: ${({ currentColorScheme }) => currentColorScheme.main.accent};
border: ${({ currentColorScheme }) => currentColorScheme.main.accent};
}
${(props) =>
!props.isDisabled &&
css`
:hover {
background-color: ${({ currentColorScheme }) =>
currentColorScheme.main.accent};
opacity: 0.85;
background: ${({ currentColorScheme }) =>
currentColorScheme.main.accent};
border: ${({ currentColorScheme }) => currentColorScheme.main.accent};
}
:active {
background-color: ${({ currentColorScheme }) =>
currentColorScheme.main.accent};
background: ${({ currentColorScheme }) => currentColorScheme.main.accent};
border: ${({ currentColorScheme }) => currentColorScheme.main.accent};
opacity: 1;
filter: brightness(90%);
cursor: pointer;
}
:active {
background-color: ${({ currentColorScheme }) =>
currentColorScheme.main.accent};
background: ${({ currentColorScheme }) =>
currentColorScheme.main.accent};
border: ${({ currentColorScheme }) => currentColorScheme.main.accent};
opacity: 1;
filter: brightness(90%);
cursor: pointer;
}
`}
.button-content {
color: ${({ currentColorScheme }) => currentColorScheme.text.accent};
@ -113,6 +120,8 @@ const ArticleMainButtonContent = (props) => {
setInvitePanelOptions,
mainButtonMobileVisible,
security,
} = props;
const isAccountsPage = selectedTreeNode[0] === "accounts";
@ -297,6 +306,15 @@ const ArticleMainButtonContent = (props) => {
action: EmployeeType.User,
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",
className: "main-button_drop-down",
@ -429,26 +447,27 @@ const ArticleMainButtonContent = (props) => {
? t("Common:Invite")
: t("Common:Actions");
const isDisabled =
((!canCreate || (!canCreateFiles && !isRoomsFolder)) && !canInvite) ||
isArchiveFolder;
const isDisabled = isAccountsPage ? !canInvite : !security?.Create;
const isProfile = history.location.pathname === "/accounts/view/@self";
return (
<>
{isMobileArticle ? (
<>
{!isArticleLoading && !isProfile && (canCreateFiles || canInvite) && (
<MobileView
t={t}
titleProp={t("Upload")}
actionOptions={actions}
buttonOptions={uploadActions}
isRooms={isRoomsFolder}
mainButtonMobileVisible={mainButtonMobileVisible}
onMainButtonClick={onCreateRoom}
/>
)}
{!isArticleLoading &&
!isProfile &&
(security?.Create || canInvite) && (
<MobileView
t={t}
titleProp={t("Upload")}
actionOptions={actions}
buttonOptions={uploadActions}
isRooms={isRoomsFolder}
mainButtonMobileVisible={mainButtonMobileVisible}
onMainButtonClick={onCreateRoom}
/>
)}
</>
) : isRoomsFolder ? (
<StyledButton
@ -534,9 +553,11 @@ export default inject(
const { enablePlugins, currentColorScheme } = auth.settingsStore;
const security = selectedFolderStore.security;
const currentFolderId = selectedFolderStore.id;
const { isAdmin, isOwner, isVisitor } = auth.userStore.user;
const { isAdmin, isOwner } = auth.userStore.user;
const { canCreateFiles } = accessRightsStore;
@ -572,9 +593,9 @@ export default inject(
isAdmin,
isOwner,
isVisitor,
mainButtonMobileVisible,
security,
};
}
)(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,10 +11,17 @@ const StyledLink = styled(Link)`
line-height: 16px;
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 (
tReady && (
<SnackBar
@ -22,7 +29,12 @@ const ConfirmEmailBar = ({ t, tReady, onClick, onClose, onLoad }) => {
text={
<>
{t("ConfirmEmailDescription")}{" "}
<StyledLink onClick={onClick}>{t("RequestActivation")}</StyledLink>
<StyledLink
currentColorScheme={currentColorScheme}
onClick={onClick}
>
{t("RequestActivation")}
</StyledLink>
</>
}
isCampaigns={false}

View File

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

View File

@ -85,7 +85,18 @@ const PeopleSelector = ({
}, [isLoading]);
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);
@ -98,6 +109,10 @@ const PeopleSelector = ({
icon,
label: displayName || email,
role,
isOwner,
isAdmin,
isVisitor,
isCollaborator,
};
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,6 +31,17 @@ export const getAccessOptions = (
access:
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: {
key: "user",
label: t("Common:User"),
@ -73,6 +84,7 @@ export const getAccessOptions = (
case RoomsType.FillingFormsRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.formFiller,
accesses.viewer,
@ -81,6 +93,7 @@ export const getAccessOptions = (
case RoomsType.EditingRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.editor,
accesses.viewer,
@ -89,6 +102,7 @@ export const getAccessOptions = (
case RoomsType.ReviewRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.reviewer,
accesses.commentator,
@ -98,6 +112,7 @@ export const getAccessOptions = (
case RoomsType.ReadOnlyRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.viewer,
];
@ -105,6 +120,7 @@ export const getAccessOptions = (
case RoomsType.CustomRoom:
options = [
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.editor,
accesses.formFiller,
@ -119,6 +135,7 @@ export const getAccessOptions = (
options = [
...options,
accesses.roomAdmin,
accesses.collaborator,
{ key: "s1", isSeparator: withSeparator },
accesses.user,
];

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import {
StyledCircle,
StyledCircleWrap,
StyledLoadingButton,
} from "@docspace/common/components/StyledLoadingButton";
@ -16,7 +16,6 @@ const LoadingButton = (props) => {
onClick,
isConversion,
inConversion,
...rest
} = props;
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

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

View File

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

View File

@ -10,26 +10,3 @@ export const thumbnailStatuses = {
};
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 history from "@docspace/common/history";
import { CategoryType } from "./constants";
import { FolderType } from "@docspace/common/constants";
import { FolderType, ShareAccessRights } from "@docspace/common/constants";
import { translations } from "./autoGeneratedTranslations";
export const setDocumentTitle = (subTitle = null) => {
@ -125,8 +125,12 @@ export const getCategoryType = (location) => {
categoryType = CategoryType.Favorite;
} else if (pathname.startsWith("/recent")) {
categoryType = CategoryType.Recent;
} else if (pathname.startsWith("/trash")) {
} else if (pathname.startsWith("/files/trash")) {
categoryType = CategoryType.Trash;
} else if (pathname.startsWith("/settings")) {
categoryType = CategoryType.Settings;
} else if (pathname.startsWith("/accounts/filter")) {
categoryType = CategoryType.Accounts;
}
return categoryType;
@ -186,3 +190,24 @@ export const getCategoryUrl = (categoryType, folderId = null) => {
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
withManager
path={["/accounts"]}
component={HomeRedirectToFilter}
/>

View File

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

View File

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

View File

@ -142,6 +142,7 @@ const PeopleTableRow = (props) => {
role,
isVisitor,
isCollaborator,
} = item;
const isPending = statusType === "pending" || statusType === "disabled";
@ -168,6 +169,12 @@ const PeopleTableRow = (props) => {
label: t("Common:RoomAdmin"),
action: "manager",
};
const collaboratorOption = {
key: "collaborator",
title: t("Common:Collaborator"),
label: t("Common:Collaborator"),
action: "collaborator",
};
const userOption = {
key: "user",
title: t("Common:User"),
@ -179,10 +186,12 @@ const PeopleTableRow = (props) => {
options.push(managerOption);
if (isCollaborator || isVisitor) options.push(collaboratorOption);
isVisitor && options.push(userOption);
return options;
}, [t, isOwner, isVisitor]);
}, [t, isOwner, isVisitor, isCollaborator]);
const onAbort = () => {
setIsLoading(false);
@ -227,6 +236,8 @@ const PeopleTableRow = (props) => {
return t("Common:DocSpaceAdmin");
case "manager":
return t("Common:RoomAdmin");
case "collaborator":
return t("Common:Collaborator");
case "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 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 employeeStatus = result(
@ -69,7 +73,6 @@ const SectionFilterContent = ({
}) => {
const [selectedFilterValues, setSelectedFilterValues] = React.useState(null);
//TODO: add new options from filter after update backend and fix manager from role
const onFilter = (data) => {
const status = getStatus(data);
@ -80,8 +83,12 @@ const SectionFilterContent = ({
const newFilter = filter.clone();
if (status === 3) {
newFilter.employeeStatus = 2;
newFilter.employeeStatus = EmployeeStatus.Disabled;
newFilter.activationStatus = null;
} else if (status === 2) {
console.log(status);
newFilter.employeeStatus = EmployeeStatus.Active;
newFilter.activationStatus = status;
} else {
newFilter.employeeStatus = null;
newFilter.activationStatus = status;
@ -94,6 +101,7 @@ const SectionFilterContent = ({
newFilter.group = group;
newFilter.payments = payments;
//console.log(newFilter);
setIsLoading(true);
fetchPeople(newFilter, true).finally(() => setIsLoading(false));
@ -123,7 +131,6 @@ const SectionFilterContent = ({
fetchPeople(newFilter, true).finally(() => setIsLoading(false));
};
// TODO: change translation keys
const getData = async () => {
const statusItems = [
{
@ -172,6 +179,12 @@ const SectionFilterContent = ({
group: "filter-type",
label: t("Common:RoomAdmin"),
},
{
id: "filter_type-room-admin",
key: EmployeeType.Collaborator,
group: "filter-type",
label: t("Common:Collaborator"),
},
{
id: "filter_type-user",
key: EmployeeType.Guest,
@ -312,6 +325,9 @@ const SectionFilterContent = ({
case EmployeeType.User:
label = t("Common:RoomAdmin");
break;
case EmployeeType.Collaborator:
label = t("Common:Collaborator");
break;
case EmployeeType.Guest:
label = t("Common:User");
break;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,10 @@
import styled, { css } from "styled-components";
import { Base } from "@docspace/components/themes";
import { hugeMobile, tablet } from "@docspace/components/utils/device";
const StyledAccountsItemTitle = styled.div`
min-height: 104px;
height: 104px;
min-height: 80px;
height: 80px;
max-height: 104px;
display: flex;
@ -11,8 +12,29 @@ const StyledAccountsItemTitle = styled.div`
justify-content: start;
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 {
padding-top: 24px;
min-width: 80px;
}

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import GalleryItemTitle from "./GalleryItemTitle";
const ItemTitle = ({
selection,
gallerySelected,
isRooms,
isAccounts,
isGallery,
@ -30,7 +31,9 @@ const ItemTitle = ({
);
if (isGallery)
return <GalleryItemTitle selection={selection} getIcon={getIcon} />;
return (
<GalleryItemTitle gallerySelected={gallerySelected} getIcon={getIcon} />
);
const filesItemSelection =
isRooms &&
@ -51,12 +54,14 @@ const ItemTitle = ({
);
};
export default inject(({ auth, settingsStore, peopleStore }) => {
export default inject(({ auth, settingsStore, peopleStore, oformsStore }) => {
const { selectionParentRoom, roomsView } = auth.infoPanelStore;
const { getIcon } = settingsStore;
const { getUserContextOptions } = peopleStore.contextOptionsStore;
const { gallerySelected } = oformsStore;
return {
gallerySelected,
getUserContextOptions,
selectionParentRoom,
roomsView,

View File

@ -22,7 +22,7 @@ const Accounts = ({
const [statusLabel, setStatusLabel] = React.useState("");
const [isLoading, setIsLoading] = React.useState(false);
const { role, id, isVisitor } = selection;
const { role, id, isVisitor, isCollaborator } = selection;
React.useEffect(() => {
getStatusLabel();
@ -50,6 +50,8 @@ const Accounts = ({
return t("Common:DocSpaceAdmin");
case "manager":
return t("Common:RoomAdmin");
case "collaborator":
return t("Common:Collaborator");
case "user":
return t("Common:User");
}
@ -72,6 +74,13 @@ const Accounts = ({
label: t("Common:RoomAdmin"),
action: "manager",
};
const collaboratorOption = {
id: "info-account-type_collaborator",
key: "collaborator",
title: t("Common:Collaborator"),
label: t("Common:Collaborator"),
action: "collaborator",
};
const userOption = {
id: "info-account-type_user",
key: "user",
@ -84,10 +93,12 @@ const Accounts = ({
options.push(managerOption);
if (isVisitor || isCollaborator) options.push(collaboratorOption);
isVisitor && options.push(userOption);
return options;
}, [t, isAdmin, isOwner, isVisitor]);
}, [t, isAdmin, isOwner, isVisitor, isCollaborator]);
const onAbort = () => {
setIsLoading(false);

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