diff --git a/build/install/common/publish-backend.sh b/build/install/common/publish-backend.sh index 1c5f7f151d..e1340101f6 100644 --- a/build/install/common/publish-backend.sh +++ b/build/install/common/publish-backend.sh @@ -79,6 +79,7 @@ services_name_backend+=(ASC.Web.Studio) services_name_backend+=(ASC.Data.Backup.BackgroundTasks) services_name_backend+=(ASC.ClearEvents) services_name_backend+=(ASC.ApiSystem) +services_name_backend+=(ASC.Web.HealthChecks.UI) # Publish backend services for i in ${!services_name_backend[@]}; do diff --git a/build/install/docker/.env b/build/install/docker/.env index 4559085d61..6ee164e98c 100644 --- a/build/install/docker/.env +++ b/build/install/docker/.env @@ -68,6 +68,7 @@ PROXY_HOST=${CONTAINER_PREFIX}proxy DOCEDITOR_HOST=${CONTAINER_PREFIX}doceditor LOGIN_HOST=${CONTAINER_PREFIX}login + HELTHCHECKS_HOST=${CONTAINER_PREFIX}healthchecks # proxy upstream environment # SERVICE_API_SYSTEM=${API_SYSTEM_HOST}:${SERVICE_PORT} diff --git a/build/install/docker/Dockerfile.app b/build/install/docker/Dockerfile.app index 161e8f0a87..58a0c6fb96 100644 --- a/build/install/docker/Dockerfile.app +++ b/build/install/docker/Dockerfile.app @@ -296,6 +296,16 @@ COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Web.St CMD ["ASC.Web.Studio.dll", "ASC.Web.Studio"] +## ASC.Web.HealthChecks.UI ## +FROM dotnetrun AS healthchecks +WORKDIR ${BUILD_PATH}/services/ASC.Web.HealthChecks.UI/service + +COPY --chown=onlyoffice:onlyoffice docker-healthchecks-entrypoint.sh ./docker-healthchecks-entrypoint.sh +COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Web.HealthChecks.UI/service/ . + +ENTRYPOINT ["./docker-healthchecks-entrypoint.sh"] +CMD ["ASC.Web.HealthChecks.UI.dll", "ASC.Web.HealthChecks.UI"] + ## ASC.Migration.Runner ## FROM $DOTNET_RUN AS onlyoffice-migration-runner ARG BUILD_PATH diff --git a/build/install/docker/build.yml b/build/install/docker/build.yml index 840e8e7baf..b163395f84 100644 --- a/build/install/docker/build.yml +++ b/build/install/docker/build.yml @@ -133,3 +133,10 @@ services: dockerfile: "${DOCKERFILE}" target: onlyoffice-migration-runner image: "${REPO}/${DOCKER_IMAGE_PREFIX}-migration-runner:${DOCKER_TAG}" + + onlyoffice-healthchecks: + build: + context: ./ + dockerfile: "${DOCKERFILE}" + target: healthchecks + image: "${REPO}/${DOCKER_IMAGE_PREFIX}-healthchecks:${DOCKER_TAG}" diff --git a/build/install/docker/docker-healthchecks-entrypoint.sh b/build/install/docker/docker-healthchecks-entrypoint.sh new file mode 100755 index 0000000000..2e581e1896 --- /dev/null +++ b/build/install/docker/docker-healthchecks-entrypoint.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# read parameters +if [ -n "$1" ]; then + RUN_DLL="${1}"; + shift +fi + +if [ -n "$1" ]; then + NAME_SERVICE="${1}"; + shift +fi + +echo "Executing -- ${NAME_SERVICE}" + +PRODUCT=${PRODUCT:-"onlyoffice"} +CONTAINER_PREFIX=${PRODUCT}- +SERVICE_PORT=${SERVICE_PORT:-"5050"} +SHEME=${SHEME:-"http"} +URLS=${URLS:-"${SHEME}://0.0.0.0:${SERVICE_PORT}"} +PATH_TO_CONF=${PATH_TO_CONF:-"/var/www/services/ASC.Web.HealthChecks.UI/service"} + +API_SYSTEM_HOST=${API_SYSTEM_HOST:-"${CONTAINER_PREFIX}api-system:${SERVICE_PORT}"} +BACKUP_HOST=${BACKUP_HOST:-"${CONTAINER_PREFIX}backup:${SERVICE_PORT}"} +BACKUP_BACKGRUOND_TASKS_HOST=${BACKUP_BACKGRUOND_TASKS_HOST:-"${CONTAINER_PREFIX}backup-background-tasks:${SERVICE_PORT}"} +CLEAR_EVENTS_HOST=${CLEAR_EVENTS_HOST:-"${CONTAINER_PREFIX}clear-events:${SERVICE_PORT}"} +FILES_HOST=${FILES_HOST:-"${CONTAINER_PREFIX}files:${SERVICE_PORT}"} +FILES_SERVICES_HOST=${FILES_SERVICES_HOST:-"${CONTAINER_PREFIX}files-services:${SERVICE_PORT}"} +NOTIFY_HOST=${NOTIFY_HOST:-"${CONTAINER_PREFIX}notify:${SERVICE_PORT}"} +PEOPLE_SERVER_HOST=${PEOPLE_SERVER_HOST:-"${CONTAINER_PREFIX}people-server:${SERVICE_PORT}"} +STUDIO_NOTIFY_HOST=${STUDIO_NOTIFY_HOST:-"${CONTAINER_PREFIX}studio-notify:${SERVICE_PORT}"} +API_HOST=${API_HOST:-"${CONTAINER_PREFIX}api:${SERVICE_PORT}"} +STUDIO_HOST=${STUDIO_HOST:-"${CONTAINER_PREFIX}studio:${SERVICE_PORT}"} + +sed -i "s!localhost:5010!${API_SYSTEM_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5012!${BACKUP_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5032!${BACKUP_BACKGRUOND_TASKS_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5027!${CLEAR_EVENTS_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5007!${FILES_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5009!${FILES_SERVICES_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5005!${NOTIFY_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5004!${PEOPLE_SERVER_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5006!${API_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5000!${STUDIO_NOTIFY_HOST}!g" ${PATH_TO_CONF}/appsettings.json +sed -i "s!localhost:5003!${STUDIO_HOST}!g" ${PATH_TO_CONF}/appsettings.json + +dotnet ${RUN_DLL} --urls=${URLS} diff --git a/build/install/docker/helthchecks.yml b/build/install/docker/helthchecks.yml new file mode 100644 index 0000000000..52208b9f79 --- /dev/null +++ b/build/install/docker/helthchecks.yml @@ -0,0 +1,29 @@ +version: "3.8" +x-service: + &x-service-base + container_name: base + restart: always + expose: + - ${SERVICE_PORT} + volumes: + #- /app/onlyoffice/CommunityServer/data:/app/onlyoffice/data + - app_data:/app/onlyoffice/data + - files_data:/var/www/products/ASC.Files/server/ + - people_data:/var/www/products/ASC.People/server/ + +services: + onlyoffice-health-checks-ui: + <<: *x-service-base + image: "${REPO}/${DOCKER_IMAGE_PREFIX}-healthchecks:${DOCKER_TAG}" + container_name: ${HELTHCHECKS_HOST} + +networks: + default: + external: + name: ${NETWORK_NAME} + +volumes: + app_data: + files_data: + people_data: + diff --git a/common/Tools/ASC.Migration.PersonalToDocspace b/common/Tools/ASC.Migration.PersonalToDocspace deleted file mode 100644 index cb49c97571..0000000000 Binary files a/common/Tools/ASC.Migration.PersonalToDocspace and /dev/null differ diff --git a/common/Tools/ASC.Migration.PersonalToDocspace.exe b/common/Tools/ASC.Migration.PersonalToDocspace.exe deleted file mode 100644 index a98ed5e83b..0000000000 Binary files a/common/Tools/ASC.Migration.PersonalToDocspace.exe and /dev/null differ diff --git a/common/Tools/ASC.MigrationPersonalToDocspace/MigrationCreator.cs b/common/Tools/ASC.MigrationPersonalToDocspace/MigrationCreator.cs index 53f6d17c88..d384c00356 100644 --- a/common/Tools/ASC.MigrationPersonalToDocspace/MigrationCreator.cs +++ b/common/Tools/ASC.MigrationPersonalToDocspace/MigrationCreator.cs @@ -42,8 +42,10 @@ public class MigrationCreator private string _pathToSave; private string _userName; private string _mail; - private string _toRegion; - private int _tenant; + private string _toRegion; + private string _toAlias; + private string _fromAlias; + private int _fromTenantId; private readonly object _locker = new object(); private readonly int _limit = 1000; private readonly List _namesModules = new List() @@ -55,6 +57,13 @@ public class MigrationCreator ModuleName.WebStudio }; + private readonly List _namesModulesForAlreadyExistPortal = new List() + { + ModuleName.Core, + ModuleName.Files, + ModuleName.Files2, + }; + public string NewAlias { get; private set; } public MigrationCreator( @@ -73,9 +82,9 @@ public class MigrationCreator _moduleProvider = moduleProvider; } - public string Create(int tenant, string userName, string mail, string toRegion) + public string Create(string fromAlias, string userName, string mail, string toRegion, string toAlias) { - Init(tenant, userName, mail, toRegion); + Init(fromAlias, userName, mail, toRegion, toAlias); var id = GetUserId(); var fileName = _userName + ".tar.gz"; @@ -88,49 +97,92 @@ public class MigrationCreator return fileName; } - private void Init(int tenant, string userName, string mail, string toRegion) + private void Init(string fromAlias, string userName, string mail, string toRegion, string toAlias) { - _modules = _moduleProvider.AllModules.Where(m => _namesModules.Contains(m.ModuleName)).ToList(); - _pathToSave = ""; _toRegion = toRegion; _userName = userName; _mail = mail; - _tenant = tenant; + _fromAlias = fromAlias; + _toAlias = toAlias; + + using var dbContextTenant = _dbFactory.CreateDbContext(); + var tenant = dbContextTenant.Tenants.SingleOrDefault(q => q.Alias == _fromAlias); + + if (tenant == null) + { + throw new ArgumentException("tenant was not found"); + } + _fromTenantId = tenant.Id; + + _modules = string.IsNullOrEmpty(_toAlias) + ? _moduleProvider.AllModules.Where(m => _namesModules.Contains(m.ModuleName)).ToList() + : _moduleProvider.AllModules.Where(m => _namesModulesForAlreadyExistPortal.Contains(m.ModuleName)).ToList(); } private Guid GetUserId() { try { - var userDbContext = _dbFactory.CreateDbContext(); + using var userDbContext = _dbFactory.CreateDbContext(); User user = null; if (string.IsNullOrEmpty(_userName) || string.IsNullOrEmpty(_mail)) { if (string.IsNullOrEmpty(_userName)) { - user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _tenant && q.Status == EmployeeStatus.Active && q.Email == _mail); + user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _fromTenantId && q.Status == EmployeeStatus.Active && q.Email == _mail); _userName = user.UserName; } else { - user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _tenant && q.Status == EmployeeStatus.Active && q.UserName == _userName); + user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _fromTenantId && q.Status == EmployeeStatus.Active && q.UserName == _userName); } } else { - user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _tenant && q.Status == EmployeeStatus.Active && q.UserName == _userName && q.Email == _mail); + user = userDbContext.Users.FirstOrDefault(q => q.Tenant == _fromTenantId && q.Status == EmployeeStatus.Active && q.UserName == _userName && q.Email == _mail); + } + if (!string.IsNullOrEmpty(_toAlias)) + { + using var dbContextTenant = _dbFactory.CreateDbContext(_toRegion); + using var userDbContextToregion = _dbFactory.CreateDbContext(_toRegion); + var tenant = dbContextTenant.Tenants.SingleOrDefault(t => t.Alias == _toAlias); + if (tenant == null) + { + throw new ArgumentException("tenant was not found"); + } + else + { + if (userDbContextToregion.Users.Any(q => q.Tenant == tenant.Id && q.UserName == _userName || q.Email == _mail)) + { + throw new ArgumentException("username already exist in the portal"); + } + } } return user.Id; + } + catch (ArgumentException e) + { + throw e; } catch (Exception) { - throw new Exception("username was not found"); + throw new ArgumentException("username was not found"); } } private void DoMigrationDb(Guid id, IDataWriteOperator writer) - { + { + if (!string.IsNullOrEmpty(_toAlias)) + { + using (var connection = _dbFactory.OpenConnection()) + { + var tenantsModule = _moduleProvider.AllModules.Single(q => q.ModuleName == ModuleName.Tenants); + var coreUserTable = tenantsModule.Tables.Single(q => q.Name == "core_user"); + ArhiveTable(coreUserTable, writer, tenantsModule, connection, id); + } + } + foreach (var module in _modules) { var tablesToProcess = module.Tables.Where(t => t.InsertMethod != InsertMethod.None).ToList(); @@ -143,55 +195,63 @@ public class MigrationCreator { continue; } - - Console.WriteLine($"backup table {table.Name}"); - using (var data = new DataTable(table.Name)) - { - ActionInvoker.Try( - state => - { - data.Clear(); - int counts; - var offset = 0; - do - { - var t = (TableInfo)state; - var dataAdapter = _dbFactory.CreateDataAdapter(); - dataAdapter.SelectCommand = module.CreateSelectCommand(connection.Fix(), _tenant, t, _limit, offset, id).WithTimeout(600); - counts = ((DbDataAdapter)dataAdapter).Fill(data); - offset += _limit; - } while (counts == _limit); - - }, - table, - maxAttempts: 5, - onFailure: error => { throw ThrowHelper.CantBackupTable(table.Name, error); }); - - foreach (var col in data.Columns.Cast().Where(col => col.DataType == typeof(DateTime))) - { - col.DateTimeMode = DataSetDateTime.Unspecified; - } - - module.PrepareData(data); - - if (data.TableName == "tenants_tenants") - { - ChangeAlias(data); - ChangeName(data); - } - - using (var file = _tempStream.Create()) - { - data.WriteXml(file, XmlWriteMode.WriteSchema); - data.Clear(); - - writer.WriteEntry(KeyHelper.GetTableZipKey(module, data.TableName), file); - } - } + ArhiveTable(table, writer, module, connection, id); } } } + } + private void ArhiveTable(TableInfo table, IDataWriteOperator writer, IModuleSpecifics module, DbConnection connection, Guid id) + { + Console.WriteLine($"backup table {table.Name}"); + using (var data = new DataTable(table.Name)) + { + ActionInvoker.Try( + state => + { + data.Clear(); + int counts; + var offset = 0; + do + { + var t = (TableInfo)state; + var dataAdapter = _dbFactory.CreateDataAdapter(); + dataAdapter.SelectCommand = module.CreateSelectCommand(connection.Fix(), _fromTenantId, t, _limit, offset, id).WithTimeout(600); + counts = ((DbDataAdapter)dataAdapter).Fill(data); + offset += _limit; + } while (counts == _limit); + + }, + table, + maxAttempts: 5, + onFailure: error => { throw ThrowHelper.CantBackupTable(table.Name, error); }); + + foreach (var col in data.Columns.Cast().Where(col => col.DataType == typeof(DateTime))) + { + col.DateTimeMode = DataSetDateTime.Unspecified; + } + + module.PrepareData(data); + + if (data.TableName == "tenants_tenants") + { + ChangeAlias(data); + ChangeName(data); + } + + WriteEnrty(data, writer, module); + } + } + + private void WriteEnrty(DataTable data, IDataWriteOperator writer, IModuleSpecifics module) + { + using (var file = _tempStream.Create()) + { + data.WriteXml(file, XmlWriteMode.WriteSchema); + data.Clear(); + + writer.WriteEntry(KeyHelper.GetTableZipKey(module, data.TableName), file); + } } private void ChangeAlias(DataTable data) @@ -255,8 +315,8 @@ public class MigrationCreator private void ChangeName(DataTable data) { data.Rows[0]["name"] = ""; - } - + } + private void DoMigrationStorage(Guid id, IDataWriteOperator writer) { Console.WriteLine($"start backup storage"); @@ -266,7 +326,7 @@ public class MigrationCreator Console.WriteLine($"start backup fileGroup: {group.Key}"); foreach (var file in group) { - var storage = _storageFactory.GetStorage(_tenant, group.Key); + var storage = _storageFactory.GetStorage(_fromTenantId, group.Key); var file1 = file; ActionInvoker.Try(state => { @@ -307,9 +367,9 @@ public class MigrationCreator var module = _storageFactoryConfig.GetModuleList().Where(m => m == "files").Single(); - var store = _storageFactory.GetStorage(_tenant, module); + var store = _storageFactory.GetStorage(_fromTenantId, module); - var dbFiles = filesDbContext.Files.Where(q => q.CreateBy == id && q.TenantId == _tenant).ToList(); + var dbFiles = filesDbContext.Files.Where(q => q.CreateBy == id && q.TenantId == _fromTenantId).ToList(); var tasks = new List(20); foreach (var dbFile in dbFiles) @@ -331,7 +391,7 @@ public class MigrationCreator private async Task FindFiles(List list, IDataStore store, DbFile dbFile, string module) { var files = await store.ListFilesRelativeAsync(string.Empty, $"\\{GetUniqFileDirectory(dbFile.Id)}", "*.*", true) - .Select(path => new BackupFileInfo(string.Empty, module, $"{GetUniqFileDirectory(dbFile.Id)}\\{path}", _tenant)) + .Select(path => new BackupFileInfo(string.Empty, module, $"{GetUniqFileDirectory(dbFile.Id)}\\{path}", _fromTenantId)) .ToListAsync(); lock (_locker) diff --git a/common/Tools/ASC.MigrationPersonalToDocspace/MigrationRunner.cs b/common/Tools/ASC.MigrationPersonalToDocspace/MigrationRunner.cs index 9dbb0a2061..ee386df41a 100644 --- a/common/Tools/ASC.MigrationPersonalToDocspace/MigrationRunner.cs +++ b/common/Tools/ASC.MigrationPersonalToDocspace/MigrationRunner.cs @@ -61,13 +61,29 @@ public class MigrationRunner _logger = logger; } - public async Task Run(string backupFile, string region) + public async Task Run(string backupFile, string region, string fromAlias, string toAlias) { _region = region; _modules = _moduleProvider.AllModules.Where(m => _namesModules.Contains(m.ModuleName)).ToList(); _backupFile = backupFile; var columnMapper = new ColumnMapper(); - + if (!string.IsNullOrEmpty(toAlias)) + { + using var dbContextTenant = _dbFactory.CreateDbContext(); + var fromTenant = dbContextTenant.Tenants.SingleOrDefault(q => q.Alias == fromAlias); + + using var dbContextToTenant = _dbFactory.CreateDbContext(region); + var toTenant = dbContextToTenant.Tenants.SingleOrDefault(q => q.Alias == toAlias); + + toTenant.Status = TenantStatus.Restoring; + toTenant.StatusChanged = DateTime.UtcNow; + + dbContextTenant.Tenants.Update(toTenant); + dbContextToTenant.SaveChanges(); + + columnMapper.SetMapping("tenants_tenants", "id", fromTenant.Id, toTenant.Id); + columnMapper.Commit(); + } using (var dataReader = new ZipReadOperator(_backupFile)) { foreach (var module in _modules) diff --git a/common/Tools/ASC.MigrationPersonalToDocspace/Program.cs b/common/Tools/ASC.MigrationPersonalToDocspace/Program.cs index 7c91f6ee52..5a78a2fcad 100644 --- a/common/Tools/ASC.MigrationPersonalToDocspace/Program.cs +++ b/common/Tools/ASC.MigrationPersonalToDocspace/Program.cs @@ -37,8 +37,7 @@ var param = Parser.Default.ParseArguments(args).Value; { FromRegion = "personal", ToRegion = "personal", - Tenant = 1, - UserName = "administrator" + FromAlias = "localhost" };*/ var builder = WebApplication.CreateBuilder(options); @@ -94,11 +93,12 @@ if(string.IsNullOrEmpty(param.UserName) && string.IsNullOrEmpty(param.Mail)) var app = builder.Build(); Console.WriteLine("backup start"); var migrationCreator = app.Services.GetService(); -var fileName = migrationCreator.Create(param.Tenant, param.UserName, param.Mail, param.ToRegion); +var fileName = migrationCreator.Create(param.FromAlias, param.UserName, param.Mail, param.ToRegion, param.ToAlias); Console.WriteLine("backup was success"); + Console.WriteLine("restore start"); var migrationRunner = app.Services.GetService(); -await migrationRunner.Run(fileName, param.ToRegion); +await migrationRunner.Run(fileName, param.ToRegion, param.FromAlias, param.ToAlias); Console.WriteLine("restore was success"); Directory.GetFiles(AppContext.BaseDirectory).Where(f => f.Equals(fileName)).ToList().ForEach(File.Delete); @@ -109,12 +109,14 @@ if (Directory.Exists(AppContext.BaseDirectory + "\\temp")) } Console.WriteLine("migration was success"); -Console.WriteLine($"new alias is - {migrationCreator.NewAlias}"); - +if (!string.IsNullOrEmpty(migrationCreator.NewAlias)) +{ + Console.WriteLine($"new alias is - {migrationCreator.NewAlias}"); +} public sealed class Options { - [Option('t', "tenant", Required = true)] - public int Tenant { get; set; } + [Option('a', "FromAlias", Required = true)] + public string FromAlias { get; set; } [Option('u', "username", Required = false, HelpText = "enter username or mail for find user")] public string UserName { get; set; } @@ -122,9 +124,12 @@ public sealed class Options [Option('m', "mail", Required = false, HelpText = "enter username or mail for find user")] public string Mail { get; set; } - [Option("toregion", Required = true)] + [Option('t' ,"toregion", Required = true)] public string ToRegion { get; set; } - [Option("fromregion", Required = false, Default = "personal")] + [Option('f' ,"fromregion", Required = false, Default = "personal")] public string FromRegion { get; set; } + + [Option("ToAlias", Required = false, HelpText = "if you wish migration to already exist portal, enter the alias")] + public string ToAlias { get; set; } } \ No newline at end of file diff --git a/common/Tools/ASC.MigrationPersonalToDocspace/appsettings.personalToDocspace.json b/common/Tools/ASC.MigrationPersonalToDocspace/appsettings.personalToDocspace.json index b9fa62a3bd..8fc5fca2c5 100644 --- a/common/Tools/ASC.MigrationPersonalToDocspace/appsettings.personalToDocspace.json +++ b/common/Tools/ASC.MigrationPersonalToDocspace/appsettings.personalToDocspace.json @@ -42,7 +42,7 @@ "property": [ { "name": "$STORAGE_ROOT", - "value": "..\\Data\\" + "value": "..\\..\\..\\Data\\" } ] } @@ -90,7 +90,7 @@ "property": [ { "name": "$STORAGE_ROOT", - "value": "..\\Data\\" + "value": "..\\..\\..\\Data\\" } ] } diff --git a/common/Tools/appsettings.personalToDocspace.json b/common/Tools/appsettings.personalToDocspace.json deleted file mode 100644 index b9fa62a3bd..0000000000 --- a/common/Tools/appsettings.personalToDocspace.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "DbProviderFactories": { - "mysql": { - "name": "MySQL Data Provider", - "invariant": "MySql.Data.MySqlClient", - "description": ".Net Framework Data Provider for MySQL", - "type": "MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data" - } - }, - "core": { - "machinekey": "1123askdasjklasbnd", - "payment": { - "delay": 10, - "region": "test", - "test": true, - "url": "", - "key": "", - "secret": "" - } - }, - - "regions": { - "personal": { - "ConnectionStrings": { - "default": { - "name": "default", - "connectionString": "Server=localhost;Database=onlyoffice;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;ConnectionReset=false", - "providerName": "MySql.Data.MySqlClient" - } - }, - "storage": { - "appender": [ - { - "name": "generic", - "append": "~/" - } - ], - "handler": [ - { - "name": "disc", - "type": "ASC.Data.Storage.DiscStorage.DiscDataStore, ASC.Data.Storage", - "property": [ - { - "name": "$STORAGE_ROOT", - "value": "..\\Data\\" - } - ] - } - ], - "module": [ - { - "name": "files", - "data": "e67be73d-f9ae-4ce1-8fec-1880cb518cb4", - "type": "disc", - "path": "$STORAGE_ROOT\\Products\\Files", - "expires": "0:16:0", - "domain": [ - { - "name": "files_temp", - "visible": false, - "data": "00000000-0000-0000-0000-000000000000", - "path": "$STORAGE_ROOT\\Products\\Files\\{0}\\temp", - "virtualpath": "~/products/community/modules/wiki/data/filestemp", - "expires": "0:10:0" - } - ] - } - ] - } - }, - "docSpace": { - "ConnectionStrings": { - "default": { - "name": "default", - "connectionString": "Server=localhost;Database=onlyoffice1;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;ConnectionReset=false", - "providerName": "MySql.Data.MySqlClient" - } - }, - "storage": { - "appender": [ - { - "name": "generic", - "append": "~/" - } - ], - "handler": [ - { - "name": "disc", - "type": "ASC.Data.Storage.DiscStorage.DiscDataStore, ASC.Data.Storage", - "property": [ - { - "name": "$STORAGE_ROOT", - "value": "..\\Data\\" - } - ] - } - ], - "module": [ - { - "name": "files", - "data": "e67be73d-f9ae-4ce1-8fec-1880cb518cb4", - "type": "disc", - "path": "$STORAGE_ROOT\\Products\\Files1", - "expires": "0:16:0", - "domain": [ - { - "name": "files_temp", - "visible": false, - "data": "00000000-0000-0000-0000-000000000000", - "path": "$STORAGE_ROOT\\Products\\Files1\\{0}\\temp", - "virtualpath": "~/products/community/modules/wiki/data/filestemp", - "expires": "0:10:0" - } - ] - } - ] - } - } - } -} diff --git a/packages/client/public/locales/de/DeleteUsersDialog.json b/packages/client/public/locales/de/DeleteUsersDialog.json index 36a9e1ccd0..d14af95333 100644 --- a/packages/client/public/locales/de/DeleteUsersDialog.json +++ b/packages/client/public/locales/de/DeleteUsersDialog.json @@ -1,5 +1,4 @@ { "DeleteGroupUsersSuccessMessage": "Benutzer wurden erfolgreich gelöscht.", - "DeleteUsers": "Benutzer löschen", - "DeleteUsersMessage": "Die ausgewählten deaktivierten Benutzer werden aus dem Portal gelöscht. Persönliche Dokumente dieser Benutzer, die für andere verfügbar sind, werden auch gelöscht. Um dies zu vermeiden, müssen Sie den Datenübertragungsprozess starten." + "DeleteUsers": "Benutzer löschen" } diff --git a/packages/client/public/locales/en/DeleteUsersDialog.json b/packages/client/public/locales/en/DeleteUsersDialog.json index 0a2e63d22e..93226bdb67 100644 --- a/packages/client/public/locales/en/DeleteUsersDialog.json +++ b/packages/client/public/locales/en/DeleteUsersDialog.json @@ -1,5 +1,5 @@ { "DeleteGroupUsersSuccessMessage": "Users have been successfully deleted.", "DeleteUsers": "Delete users", - "DeleteUsersMessage": "The selected disabled users will be deleted from the DocSpace. Personal documents of these users which are available to others will be deleted. To avoid this, you need to start the data transfer process before deleting." + "DeleteUsersMessage": "The selected disabled users will be deleted from the DocSpace. Personal documents of these users which are available to others will be deleted." } diff --git a/packages/client/public/locales/fr/DeleteUsersDialog.json b/packages/client/public/locales/fr/DeleteUsersDialog.json index 845f7f1aa7..d48029413c 100644 --- a/packages/client/public/locales/fr/DeleteUsersDialog.json +++ b/packages/client/public/locales/fr/DeleteUsersDialog.json @@ -1,5 +1,4 @@ { "DeleteGroupUsersSuccessMessage": "Les utilisateurs ont été supprimés avec succès.", - "DeleteUsers": "Supprimer les utilisateurs", - "DeleteUsersMessage": "Les utilisateurs désactivés sélectionnés seront supprimés du portail. Les documents personnels de ces utilisateurs qui sont partagés avec d'autres seront supprimés. Pour éviter cela, vous devez démarrer le processus de transfert de données avant la suppression." + "DeleteUsers": "Supprimer les utilisateurs" } diff --git a/packages/client/public/locales/it/DeleteUsersDialog.json b/packages/client/public/locales/it/DeleteUsersDialog.json index 7dd7083d9f..6513d2ae65 100644 --- a/packages/client/public/locales/it/DeleteUsersDialog.json +++ b/packages/client/public/locales/it/DeleteUsersDialog.json @@ -1,5 +1,4 @@ { "DeleteGroupUsersSuccessMessage": "Gli utenti sono stati eliminati con successo.", - "DeleteUsers": "Eliminare utenti", - "DeleteUsersMessage": "Gli utenti disabilitati selezionati verranno eliminati dal portale. I documenti personali di questi utenti disponibili per gli altri verranno eliminati. Per evitarlo, è necessario avviare il processo di trasferimento dei dati prima di eliminarli." + "DeleteUsers": "Eliminare utenti" } diff --git a/packages/client/public/locales/ja-JP/DeleteUsersDialog.json b/packages/client/public/locales/ja-JP/DeleteUsersDialog.json index 091dc1bff8..8707fde0cd 100644 --- a/packages/client/public/locales/ja-JP/DeleteUsersDialog.json +++ b/packages/client/public/locales/ja-JP/DeleteUsersDialog.json @@ -1,5 +1,4 @@ { "DeleteGroupUsersSuccessMessage": "ユーザーが正常に削除されました。", - "DeleteUsers": "ユーザーを削除する", - "DeleteUsersMessage": "選択した無効のユーザーは、ポータルサイトから削除されます。これらのユーザーの個人文書で、他の人が利用できるものも削除されます。これを避けるには、削除する前にデータ転送プロセスを開始する必要があります。" + "DeleteUsers": "ユーザーを削除する" } diff --git a/packages/client/public/locales/ru/DeleteUsersDialog.json b/packages/client/public/locales/ru/DeleteUsersDialog.json index 2fa05dbbbd..91cdf8ed06 100644 --- a/packages/client/public/locales/ru/DeleteUsersDialog.json +++ b/packages/client/public/locales/ru/DeleteUsersDialog.json @@ -1,5 +1,5 @@ { "DeleteGroupUsersSuccessMessage": "Пользователи были успешно удалены", "DeleteUsers": "Удалить пользователей", - "DeleteUsersMessage": "Выбранные заблокированные пользователи будут удалены с портала. Будут удалены личные документы этих пользователей, доступные для других. Чтобы избежать этого, нужно перед удалением запустить процесс передачи данных." + "DeleteUsersMessage": "Выбранные заблокированные пользователи будут удалены с портала. Будут удалены личные документы этих пользователей, доступные для других." } diff --git a/packages/client/public/locales/zh-CN/DeleteUsersDialog.json b/packages/client/public/locales/zh-CN/DeleteUsersDialog.json index c20a28183a..1677159a46 100644 --- a/packages/client/public/locales/zh-CN/DeleteUsersDialog.json +++ b/packages/client/public/locales/zh-CN/DeleteUsersDialog.json @@ -1,5 +1,4 @@ { "DeleteGroupUsersSuccessMessage": "用户已成功删除。", - "DeleteUsers": "删除用户", - "DeleteUsersMessage": "选择的禁用用户将从门户网站中删除。这些用户对其他人可用的个人文档将被删除。为了避免这种情况,您必须在删除前启动数据传输过程。" + "DeleteUsers": "删除用户" } diff --git a/packages/client/src/components/EmptyContainer/EmptyFolderContainer.js b/packages/client/src/components/EmptyContainer/EmptyFolderContainer.js index 48f0c3b8e0..5be67cf9df 100644 --- a/packages/client/src/components/EmptyContainer/EmptyFolderContainer.js +++ b/packages/client/src/components/EmptyContainer/EmptyFolderContainer.js @@ -26,6 +26,7 @@ const EmptyFolderContainer = ({ tReady, isLoadedFetchFiles, viewAs, + setIsLoadedEmptyPage, }) => { const onBackToParentFolder = () => { setIsLoading(true); @@ -35,10 +36,21 @@ const EmptyFolderContainer = ({ : fetchFiles(parentId).finally(() => setIsLoading(false)); }; + React.useEffect(() => { + if (isLoadedFetchFiles && tReady) { + setIsLoadedEmptyPage(true); + } else { + setIsLoadedEmptyPage(false); + } + }, [isLoadedFetchFiles, tReady]); + React.useEffect(() => { setIsEmptyPage(true); - return () => setIsEmptyPage(false); + return () => { + setIsEmptyPage(false); + setIsLoadedEmptyPage(false); + }; }, []); const onInviteUsersClick = () => { @@ -161,6 +173,7 @@ export default inject( setIsEmptyPage, isLoadedFetchFiles, viewAs, + setIsLoadedEmptyPage, } = filesStore; const { navigationPath, @@ -198,6 +211,7 @@ export default inject( folderId, isLoadedFetchFiles, viewAs, + setIsLoadedEmptyPage, }; } )(withTranslation(["Files", "Translations"])(observer(EmptyFolderContainer))); diff --git a/packages/client/src/components/EmptyContainer/RootFolderContainer.js b/packages/client/src/components/EmptyContainer/RootFolderContainer.js index 2a64909ecb..b21ed86be9 100644 --- a/packages/client/src/components/EmptyContainer/RootFolderContainer.js +++ b/packages/client/src/components/EmptyContainer/RootFolderContainer.js @@ -48,6 +48,7 @@ const RootFolderContainer = (props) => { setIsEmptyPage, isVisitor, sectionWidth, + setIsLoadedEmptyPage, } = props; const personalDescription = t("EmptyFolderDecription"); @@ -87,8 +88,13 @@ const RootFolderContainer = (props) => { return () => { setIsEmptyPage(false); + setIsLoadedEmptyPage(false); }; - }, [isEmptyPage, setIsEmptyPage, rootFolderType]); + }, []); + + React.useEffect(() => { + setIsLoadedEmptyPage(!isLoading); + }, [isLoading]); const onGoToPersonal = () => { const newFilter = filter.clone(); @@ -313,31 +319,17 @@ const RootFolderContainer = (props) => { const headerText = isPrivacyFolder ? privateRoomHeader : title; const emptyFolderProps = getEmptyFolderProps(); - React.useEffect(() => { - let timeout; - - if (isLoading) { - setShowLoader(isLoading); - } else { - timeout = setTimeout(() => setShowLoader(isLoading), 300); - } - - return () => clearTimeout(timeout); - }, [isLoading]); + if (isLoading) { + return ; + } return ( - <> - {showLoader ? ( - - ) : ( - - )} - + ); }; @@ -362,6 +354,7 @@ export default inject( setAlreadyFetchingRooms, isEmptyPage, setIsEmptyPage, + setIsLoadedEmptyPage, } = filesStore; const { title, rootFolderType } = selectedFolderStore; const { isPrivacyFolder, myFolderId } = treeFoldersStore; @@ -387,6 +380,7 @@ export default inject( setAlreadyFetchingRooms, isEmptyPage, setIsEmptyPage, + setIsLoadedEmptyPage, }; } )(withTranslation(["Files"])(observer(RootFolderContainer))); diff --git a/packages/client/src/pages/Home/Section/Filter/index.js b/packages/client/src/pages/Home/Section/Filter/index.js index 2c251a458a..7c60a17494 100644 --- a/packages/client/src/pages/Home/Section/Filter/index.js +++ b/packages/client/src/pages/Home/Section/Filter/index.js @@ -169,10 +169,23 @@ const SectionFilterContent = ({ searchTitleOpenLocation, isLoadedLocationFiles, setIsLoadedSearchFiles, + isLoadedEmptyPage, + isEmptyPage, clearSearch, setClearSearch, }) => { const [selectedFilterValues, setSelectedFilterValues] = React.useState(null); + const [isLoadedFilter, setIsLoadedFilter] = React.useState(false); + + React.useEffect(() => { + if (isEmptyPage) { + setIsLoadedFilter(isLoadedEmptyPage); + } + + if (!isEmptyPage && !isLoadedEmptyPage) { + setIsLoadedFilter(true); + } + }, [isLoadedEmptyPage, isEmptyPage]); React.useEffect(() => { if (!(searchTitleOpenLocation && isLoadedLocationFiles)) return; @@ -1300,6 +1313,10 @@ const SectionFilterContent = ({ } }; + if (!isLoadedFilter) { + return ; + } + return ( )} - {!isEmptyPage && !isErrorRoomNotAvailable && ( + {!isLoadedEmptyPage && !isErrorRoomNotAvailable && ( {isFrame ? ( showFilter && @@ -548,6 +549,12 @@ class PureHome extends React.Component { )} + {isLoadedEmptyPage && ( + +
+ + )} + {(context) => ( @@ -622,7 +629,7 @@ export default inject( refreshFiles, setViewAs, isEmptyPage, - + isLoadedEmptyPage, disableDrag, isErrorRoomNotAvailable, } = filesStore; @@ -773,6 +780,7 @@ export default inject( setViewAs, withPaging, isEmptyPage, + isLoadedEmptyPage, }; } )(withRouter(observer(Home))); diff --git a/packages/client/src/store/FilesStore.js b/packages/client/src/store/FilesStore.js index 992b3ee6ab..6bf5c6043a 100644 --- a/packages/client/src/store/FilesStore.js +++ b/packages/client/src/store/FilesStore.js @@ -113,6 +113,8 @@ class FilesStore { clearSearch = false; + isLoadedEmptyPage = false; + constructor( authStore, selectedFolderStore, @@ -135,7 +137,6 @@ class FilesStore { this.roomsController = new AbortController(); this.filesController = new AbortController(); - const { socketHelper, withPaging } = authStore.settingsStore; socketHelper.on("s:modify-folder", async (opt) => { @@ -438,6 +439,10 @@ class FilesStore { this.isEmptyPage = isEmptyPage; }; + setIsLoadedEmptyPage = (isLoadedEmptyPage) => { + this.isLoadedEmptyPage = isLoadedEmptyPage; + }; + get tooltipOptions() { if (!this.dragging) return null; @@ -2958,22 +2963,26 @@ class FilesStore { (f) => f.id === item.id && f.isFolder === item.isFolder ); if (fileIndex === -1) { - this.setSelection([item, ...this.selection]); + this.setSelection([...this.selection, item]); } else { this.deselectFile(item); } }; withShiftSelect = (item) => { + const caretStart = this.hotkeyCaretStart + ? this.hotkeyCaretStart + : this.filesList[0]; + const caret = this.hotkeyCaret ? this.hotkeyCaret : caretStart; + + if (!caret || !caretStart) return; + const startCaretIndex = this.filesList.findIndex( - (f) => - f.id === this.hotkeyCaretStart.id && - f.isFolder === this.hotkeyCaretStart.isFolder + (f) => f.id === caretStart.id && f.isFolder === caretStart.isFolder ); const caretIndex = this.filesList.findIndex( - (f) => - f.id === this.hotkeyCaret.id && f.isFolder === this.hotkeyCaret.isFolder + (f) => f.id === caret.id && f.isFolder === caret.isFolder ); const itemIndex = this.filesList.findIndex( @@ -2994,6 +3003,11 @@ class FilesStore { ); if (selectionIndex === -1) { newSelection.push(filesItem); + } else { + newSelection = newSelection.filter( + (_, fIndex) => selectionIndex !== fIndex + ); + newSelection.push(filesItem); } if (isMoveDown) { @@ -3003,13 +3017,18 @@ class FilesStore { } } + const lastSelection = this.selection[this.selection.length - 1]; + const indexOfLast = this.filesList.findIndex( + (f) => f.id === lastSelection.id && f.isFolder === lastSelection.isFolder + ); + newSelection = newSelection.filter((f) => { const listIndex = this.filesList.findIndex( (x) => x.id === f.id && x.isFolder === f.isFolder ); if (isMoveDown) { - const isSelect = itemIndex > startCaretIndex; + const isSelect = listIndex < indexOfLast; if (isSelect) return true; if (listIndex >= startCaretIndex) { @@ -3018,7 +3037,7 @@ class FilesStore { return listIndex >= itemIndex; } } else { - const isSelect = itemIndex < startCaretIndex; + const isSelect = listIndex > indexOfLast; if (isSelect) return true; if (listIndex <= startCaretIndex) {