diff --git a/ASC.Web.sln b/ASC.Web.sln index 59c50743b2..13419aedf1 100644 --- a/ASC.Web.sln +++ b/ASC.Web.sln @@ -79,7 +79,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Data.Encryption", "comm EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Projects", "products\ASC.Projects\Server\ASC.Projects.csproj", "{D1A33923-5680-4B86-A7DA-41E78350D997}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASC.CRM", "products\ASC.CRM\Server\ASC.CRM.csproj", "{277F4A2C-07CC-4BC5-B4F3-9695BB2DFFB9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.CRM", "products\ASC.CRM\Server\ASC.CRM.csproj", "{277F4A2C-07CC-4BC5-B4F3-9695BB2DFFB9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Mail", "products\ASC.Mail\Server\ASC.Mail.csproj", "{137CA67B-D0F5-4746-B8BC-1888D2859B90}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Calendar", "products\ASC.Calendar\Server\ASC.Calendar.csproj", "{F39933F8-7598-492F-9DD3-E25780D68288}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -227,6 +231,14 @@ Global {277F4A2C-07CC-4BC5-B4F3-9695BB2DFFB9}.Debug|Any CPU.Build.0 = Debug|Any CPU {277F4A2C-07CC-4BC5-B4F3-9695BB2DFFB9}.Release|Any CPU.ActiveCfg = Release|Any CPU {277F4A2C-07CC-4BC5-B4F3-9695BB2DFFB9}.Release|Any CPU.Build.0 = Release|Any CPU + {137CA67B-D0F5-4746-B8BC-1888D2859B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {137CA67B-D0F5-4746-B8BC-1888D2859B90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {137CA67B-D0F5-4746-B8BC-1888D2859B90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {137CA67B-D0F5-4746-B8BC-1888D2859B90}.Release|Any CPU.Build.0 = Release|Any CPU + {F39933F8-7598-492F-9DD3-E25780D68288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F39933F8-7598-492F-9DD3-E25780D68288}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F39933F8-7598-492F-9DD3-E25780D68288}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F39933F8-7598-492F-9DD3-E25780D68288}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build/install/common/publish-backend.sh b/build/install/common/publish-backend.sh index 3a0c3b87c4..93a54547d1 100644 --- a/build/install/common/publish-backend.sh +++ b/build/install/common/publish-backend.sh @@ -60,6 +60,8 @@ servers_products_name_backend=(ASC.CRM) servers_products_name_backend+=(ASC.Files) servers_products_name_backend+=(ASC.People) servers_products_name_backend+=(ASC.Projects) +servers_products_name_backend+=(ASC.Calendar) +servers_products_name_backend+=(ASC.Mail) # Publish server backend products for i in ${!servers_products_name_backend[@]}; do diff --git a/build/install/docker/.env b/build/install/docker/.env index 823635a2ee..ddf27d85e8 100644 --- a/build/install/docker/.env +++ b/build/install/docker/.env @@ -47,10 +47,12 @@ # service host # API_SYSTEM_HOST=${CONTAINER_PREFIX}api-system BACKUP_HOST=${CONTAINER_PREFIX}backup + CALENDAR_HOST=${CONTAINER_PREFIX}calendar CRM_HOST=${CONTAINER_PREFIX}crm STORAGE_ENCRYPTION_HOST=${CONTAINER_PREFIX}storage-encryption FILES_HOST=${CONTAINER_PREFIX}files FILES_SERVICES_HOST=${CONTAINER_PREFIX}files-services + MAIL_HOST=${CONTAINER_PREFIX}mail STORAGE_MIGRATION_HOST=${CONTAINER_PREFIX}storage-migration NOTIFY_HOST=${CONTAINER_PREFIX}notify PEOPLE_SERVER_HOST=${CONTAINER_PREFIX}people-server @@ -68,9 +70,11 @@ SERVICE_API_SYSTEM=${API_SYSTEM_HOST}:${SERVICE_PORT} SERVICE_BACKUP=${BACKUP_HOST}:${SERVICE_PORT} SERVICE_CRM=${CRM_HOST}:${SERVICE_PORT} + SERVICE_CALENDAR=${CALENDAR_HOST}:${SERVICE_PORT} SERVICE_STORAGE_ENCRYPTION=${STORAGE_ENCRYPTION_HOST}:${SERVICE_PORT} SERVICE_FILES=${FILES_HOST}:${SERVICE_PORT} SERVICE_FILES_SERVICES=${FILES_SERVICES_HOST}:${SERVICE_PORT} + SERVICE_MAIL=${MAIL_HOST}:${SERVICE_PORT} SERVICE_STORAGE_MIGRATION=${STORAGE_MIGRATION_HOST}:${SERVICE_PORT} SERVICE_NOTIFY=${NOTIFY_HOST}:${SERVICE_PORT} SERVICE_PEOPLE_SERVER=${PEOPLE_SERVER_HOST}:${SERVICE_PORT} diff --git a/build/install/docker/Dockerfile-app b/build/install/docker/Dockerfile-app index dd3d677de8..1c14684454 100644 --- a/build/install/docker/Dockerfile-app +++ b/build/install/docker/Dockerfile-app @@ -44,12 +44,20 @@ RUN echo "nameserver 8.8.8.8" | tee /etc/resolv.conf > /dev/null && \ cd ${SRC_PATH}/build/install/common/ && \ bash build-frontend.sh -sp ${SRC_PATH} && \ bash build-backend.sh -sp ${SRC_PATH} -ar "--disable-parallel" && \ - bash publish-backend.sh -sp ${SRC_PATH} -bp ${BUILD_PATH} -ar "--disable-parallel" - -COPY config/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/mysql.cnf -COPY config/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf + bash publish-backend.sh -sp ${SRC_PATH} -bp ${BUILD_PATH} -ar "--disable-parallel" && \ + rm -rf ${SRC_PATH}/common/* && \ + rm -rf ${SRC_PATH}/web/ASC.Web.Core/* && \ + rm -rf ${SRC_PATH}/web/ASC.Web.Studio/* && \ + rm -rf ${SRC_PATH}/products/ASC.Calendar/Server/* && \ + rm -rf ${SRC_PATH}/products/ASC.CRM/Server/* && \ + rm -rf ${SRC_PATH}/products/ASC.Files/Server/* && \ + rm -rf ${SRC_PATH}/products/ASC.Files/Service/* && \ + rm -rf ${SRC_PATH}/products/ASC.Mail/Server/* && \ + rm -rf ${SRC_PATH}/products/ASC.People/Server/* && \ + rm -rf ${SRC_PATH}/products/ASC.Projects/Server/* -RUN sed -i 's/Server=.*;Port=/Server=127.0.0.1;Port=/' /app/onlyoffice/config/appsettings.test.json + +COPY config/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/mysql.cnf RUN rm -rf /var/lib/apt/lists/* @@ -107,6 +115,8 @@ COPY --from=base ${SRC_PATH}/web/ASC.Web.Login/dist ${BUILD_PATH}/studio/login COPY --from=base ${SRC_PATH}/products/ASC.People/Client/dist ${BUILD_PATH}/products/ASC.People/client COPY --from=base ${SRC_PATH}/products/ASC.Projects/Client/dist ${BUILD_PATH}/products/ASC.Projects/client COPY --from=base ${SRC_PATH}/web/ASC.Web.Client/dist ${BUILD_PATH}/studio/client +COPY --from=base ${SRC_PATH}/products/ASC.Calendar/Client/dist ${BUILD_PATH}/products/ASC.Calendar/client +COPY --from=base ${SRC_PATH}/products/ASC.Mail/Client/dist ${BUILD_PATH}/products/ASC.Mail/client COPY /config/nginx/templates/upstream.conf.template /etc/nginx/templates/upstream.conf.template @@ -122,6 +132,8 @@ RUN chown nginx:nginx /etc/nginx/* -R && \ sed -i 's/localhost:5020/$service_projects_server/' /etc/nginx/conf.d/onlyoffice.conf && \ sed -i 's/localhost:5000/$service_api/' /etc/nginx/conf.d/onlyoffice.conf && \ sed -i 's/localhost:5003/$service_studio/' /etc/nginx/conf.d/onlyoffice.conf && \ + sed -i 's/localhost:5023/$service_calendar/' /etc/nginx/conf.d/onlyoffice.conf && \ + sed -i 's/localhost:5022/$service_mail/' /etc/nginx/conf.d/onlyoffice.conf && \ sed -i 's/localhost:9999/$service_urlshortener/' /etc/nginx/conf.d/onlyoffice.conf && \ sed -i 's/172.*/$document_server;/' /etc/nginx/conf.d/onlyoffice.conf && \ # configute the image nginx whith less privileged https://hub.docker.com/_/nginx @@ -146,6 +158,15 @@ COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Data.B CMD ["ASC.Data.Backup.dll", "ASC.Data.Backup", "core:products:folder=/var/www/products/", "core:products:subfolder=server"] +## ASC.Calendar ## +FROM builder AS calendar +WORKDIR ${BUILD_PATH}/products/ASC.Calendar/server/ + +COPY --chown=onlyoffice:onlyoffice docker-entrypoint.sh . +COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/products/ASC.Calendar/server/ . + +CMD ["ASC.Calendar.dll", "ASC.Calendar"] + ## ASC.CRM ## FROM builder AS crm WORKDIR ${BUILD_PATH}/products/ASC.CRM/server/ @@ -182,6 +203,15 @@ COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Files. CMD ["ASC.Files.Service.dll", "ASC.Files.Service", "core:products:folder=/var/www/products/", "core:products:subfolder=server"] +## ASC.Mail ## +FROM builder AS mail +WORKDIR ${BUILD_PATH}/products/ASC.Mail/server/ + +COPY --chown=onlyoffice:onlyoffice docker-entrypoint.sh . +COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/products/ASC.Mail/server/ . + +CMD ["ASC.Mail.dll", "ASC.Mail"] + ## ASC.Data.Storage.Migration ## FROM builder AS data_storage_migration WORKDIR ${BUILD_PATH}/services/storage.migration/service/ diff --git a/build/install/docker/appserver.yml b/build/install/docker/appserver.yml index 3eef1db072..4912140010 100644 --- a/build/install/docker/appserver.yml +++ b/build/install/docker/appserver.yml @@ -29,6 +29,9 @@ x-service: - people_data:/var/www/products/ASC.People/server/ - crm_data:/var/www/products/ASC.CRM/server/ - project_data:/var/www/products/ASC.Projects/server/ + - calendar_data:/var/www/products/ASC.Calendar/server/ + - mail_data:/var/www/products/ASC.Mail/server/ + services: onlyoffice-elasticsearch: @@ -95,6 +98,11 @@ services: image: "${REPO}/${STATUS}appserver-backup:${SRV_VERSION}" container_name: ${BACKUP_HOST} + onlyoffice-calendar: + <<: *x-service-base + image: "${REPO}/${STATUS}appserver-calendar:${SRV_VERSION}" + container_name: ${CALENDAR_HOST} + onlyoffice-crm: <<: *x-service-base image: "${REPO}/${STATUS}appserver-crm:${SRV_VERSION}" @@ -115,6 +123,11 @@ services: image: "${REPO}/${STATUS}appserver-files-services:${SRV_VERSION}" container_name: ${FILES_SERVICES_HOST} + onlyoffice-mail: + <<: *x-service-base + image: "${REPO}/${STATUS}appserver-mail:${SRV_VERSION}" + container_name: ${MAIL_HOST} + onlyoffice-storage-migration: <<: *x-service-base image: "${REPO}/${STATUS}appserver-storage-migration:${SRV_VERSION}" @@ -187,10 +200,12 @@ services: depends_on: - onlyoffice-api-system - onlyoffice-backup + - onlyoffice-calendar - onlyoffice-crm - onlyoffice-storage-encryption - onlyoffice-files - onlyoffice-files-services + - onlyoffice-mail - onlyoffice-storage-migration - onlyoffice-people-server - onlyoffice-projects-server @@ -204,10 +219,12 @@ services: environment: - SERVICE_API_SYSTEM=${SERVICE_API_SYSTEM} - SERVICE_BACKUP=${SERVICE_BACKUP} + - SERVICE_CALENDAR=${SERVICE_CALENDAR} - SERVICE_CRM=${SERVICE_CRM} - SERVICE_STORAGE_ENCRYPTION=${SERVICE_STORAGE_ENCRYPTION} - SERVICE_FILES=${SERVICE_FILES} - SERVICE_FILES_SERVICES=${SERVICE_FILES_SERVICES} + - SERVICE_MAIL=${SERVICE_MAIL} - SERVICE_STORAGE_MIGRATION=${SERVICE_STORAGE_MIGRATION} - SERVICE_NOTIFY=${SERVICE_NOTIFY} - SERVICE_PEOPLE_SERVER=${SERVICE_PEOPLE_SERVER} @@ -240,3 +257,5 @@ volumes: people_data: crm_data: project_data: + calendar_data: + mail_data: diff --git a/build/install/docker/build.yml b/build/install/docker/build.yml index 1e6209d635..9a43b41e51 100644 --- a/build/install/docker/build.yml +++ b/build/install/docker/build.yml @@ -15,6 +15,13 @@ services: target: backup image: "${REPO}/${STATUS}appserver-backup:${SRV_VERSION}" + onlyoffice-calendar: + build: + context: ./ + dockerfile: "${DOCKERFILE}" + target: calendar + image: "${REPO}/${STATUS}appserver-calendar:${SRV_VERSION}" + onlyoffice-crm: build: context: ./ @@ -43,6 +50,13 @@ services: target: files_services image: "${REPO}/${STATUS}appserver-files-services:${SRV_VERSION}" + onlyoffice-mail: + build: + context: ./ + dockerfile: "${DOCKERFILE}" + target: mail + image: "${REPO}/${STATUS}appserver-mail:${SRV_VERSION}" + onlyoffice-storage-migration: build: context: ./ diff --git a/build/install/docker/config/nginx/templates/upstream.conf.template b/build/install/docker/config/nginx/templates/upstream.conf.template index 675432e66c..a14517cd5a 100644 --- a/build/install/docker/config/nginx/templates/upstream.conf.template +++ b/build/install/docker/config/nginx/templates/upstream.conf.template @@ -10,6 +10,11 @@ map $SERVICE_BACKUP $service_backup { $SERVICE_BACKUP $SERVICE_BACKUP; } +map $SERVICE_CALENDAR $service_calendar { + volatile; + $SERVICE_CALENDAR $SERVICE_CALENDAR; +} + map $SERVICE_CRM $service_crm { volatile; $SERVICE_CRM $SERVICE_CRM; @@ -30,6 +35,11 @@ map $SERVICE_FILES_SERVICES $service_files_services { $SERVICE_FILES_SERVICES $SERVICE_FILES_SERVICES; } +map $SERVICE_MAIL $service_mail { + volatile; + $SERVICE_MAIL $SERVICE_MAIL; +} + map $SERVICE_STORAGE_MIGRATION $service_storage_migration { volatile; $SERVICE_STORAGE_MIGRATION $SERVICE_STORAGE_MIGRATION; diff --git a/build/install/docker/notify.yml b/build/install/docker/notify.yml index 0d813e085a..7a437cf4d3 100644 --- a/build/install/docker/notify.yml +++ b/build/install/docker/notify.yml @@ -29,6 +29,8 @@ x-service: - people_data:/var/www/products/ASC.People/server/ - crm_data:/var/www/products/ASC.CRM/server/ - project_data:/var/www/products/ASC.Projects/server/ + - calendar_data:/var/www/products/ASC.Calendar/server/ + - mail_data:/var/www/products/ASC.Mail/server/ services: onlyoffice-notify: @@ -47,3 +49,5 @@ volumes: people_data: crm_data: project_data: + calendar_data: + mail_data: diff --git a/build/run/CalendarClient.bat b/build/run/CalendarClient.bat new file mode 100644 index 0000000000..297b74d734 --- /dev/null +++ b/build/run/CalendarClient.bat @@ -0,0 +1,2 @@ +echo "RUN ASC.Web.Calendar" +call set BROWSER=none&&npm start --prefix ../../products/ASC.Calendar/Client \ No newline at end of file diff --git a/build/run/CalendarServer.bat b/build/run/CalendarServer.bat new file mode 100644 index 0000000000..1072ae298a --- /dev/null +++ b/build/run/CalendarServer.bat @@ -0,0 +1,2 @@ +echo "RUN ASC.Calendar" +call dotnet run --project ..\..\products\ASC.Calendar\Server\ASC.Calendar.csproj --no-build --$STORAGE_ROOT=..\..\..\Data --log__dir=..\..\..\Logs --log__name=calendar \ No newline at end of file diff --git a/build/run/MailClient.bat b/build/run/MailClient.bat new file mode 100644 index 0000000000..ea4da86a48 --- /dev/null +++ b/build/run/MailClient.bat @@ -0,0 +1,2 @@ +echo "RUN ASC.Web.Mail" +call set BROWSER=none&&npm start --prefix ../../products/ASC.Mail/Client \ No newline at end of file diff --git a/build/run/MailServer.bat b/build/run/MailServer.bat new file mode 100644 index 0000000000..d44b27df0e --- /dev/null +++ b/build/run/MailServer.bat @@ -0,0 +1,2 @@ +echo "RUN ASC.Mail" +call dotnet run --project ..\..\products\ASC.Mail\Server\ASC.Mail.csproj --no-build --$STORAGE_ROOT=..\..\..\Data --log__dir=..\..\..\Logs --log__name=mail \ No newline at end of file diff --git a/build/run/WebEditor.bat b/build/run/WebEditorClient.bat similarity index 100% rename from build/run/WebEditor.bat rename to build/run/WebEditorClient.bat diff --git a/build/run/WebLogin.bat b/build/run/WebLoginClient.bat similarity index 100% rename from build/run/WebLogin.bat rename to build/run/WebLoginClient.bat diff --git a/common/ASC.Api.Core/ASC.Api.Core.csproj b/common/ASC.Api.Core/ASC.Api.Core.csproj index 8c2b537fa4..a6f4e553ce 100644 --- a/common/ASC.Api.Core/ASC.Api.Core.csproj +++ b/common/ASC.Api.Core/ASC.Api.Core.csproj @@ -1,10 +1,7 @@ - + net5.0 - - Library - diff --git a/common/ASC.Api.Core/Core/ApiContext.cs b/common/ASC.Api.Core/Core/ApiContext.cs index 7430cf2e74..90f87b3eab 100644 --- a/common/ASC.Api.Core/Core/ApiContext.cs +++ b/common/ASC.Api.Core/Core/ApiContext.cs @@ -42,12 +42,14 @@ namespace ASC.Api.Core private static int MaxCount = 1000; public IHttpContextAccessor HttpContextAccessor { get; set; } public Tenant tenant; - public Tenant Tenant { get { return tenant ??= TenantManager.GetCurrentTenant(HttpContextAccessor.HttpContext); } } + public Tenant Tenant { get { return tenant ??= TenantManager.GetCurrentTenant(HttpContextAccessor?.HttpContext); } } public ApiContext(IHttpContextAccessor httpContextAccessor, SecurityContext securityContext, TenantManager tenantManager) { - if (httpContextAccessor == null || httpContextAccessor.HttpContext == null) return; + SecurityContext = securityContext; + TenantManager = tenantManager; HttpContextAccessor = httpContextAccessor; + if (httpContextAccessor.HttpContext == null) return; Count = MaxCount; var query = HttpContextAccessor.HttpContext.Request.Query; @@ -85,9 +87,6 @@ namespace ASC.Api.Core { UpdatedSince = Convert.ToDateTime(updatedSince); } - - SecurityContext = securityContext; - TenantManager = tenantManager; } public string[] Fields { get; set; } diff --git a/common/ASC.Api.Core/Properties/launchSettings.json b/common/ASC.Api.Core/Properties/launchSettings.json deleted file mode 100644 index 125fe58e42..0000000000 --- a/common/ASC.Api.Core/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:65458/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "ASC.Api.Core": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:65459/" - } - } -} \ No newline at end of file diff --git a/common/ASC.Common/DependencyInjection/AutofacExtension.cs b/common/ASC.Common/DependencyInjection/AutofacExtension.cs index f6a51d0247..8dec7cb508 100644 --- a/common/ASC.Common/DependencyInjection/AutofacExtension.cs +++ b/common/ASC.Common/DependencyInjection/AutofacExtension.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Reflection; using System.Runtime.Loader; +using ASC.Common.Utils; + using Autofac; using Autofac.Configuration; @@ -82,13 +84,13 @@ namespace ASC.Common.DependencyInjection if (!Path.IsPathRooted(folder)) { - if (currentDir.EndsWith(Path.Combine(Path.GetFileName(folder), Assembly.GetEntryAssembly().GetName().Name, subfolder))) + if (currentDir.EndsWith(CrossPlatform.PathCombine(Path.GetFileName(folder), Assembly.GetEntryAssembly().GetName().Name, subfolder))) { - productsDir = Path.GetFullPath(Path.Combine("..", "..")); + productsDir = Path.GetFullPath(CrossPlatform.PathCombine("..", "..")); } else { - productsDir = Path.GetFullPath(Path.Combine(currentDir, folder)); + productsDir = Path.GetFullPath(CrossPlatform.PathCombine(currentDir, folder)); } } else @@ -129,8 +131,8 @@ namespace ASC.Common.DependencyInjection string GetFullPath(string n) { - var productPath = Path.Combine(productsDir, n, subfolder); - return GetPath(Path.Combine(productPath, "bin"), n, SearchOption.AllDirectories) ?? GetPath(productPath, n, SearchOption.TopDirectoryOnly); + var productPath = CrossPlatform.PathCombine(productsDir, n, subfolder); + return GetPath(CrossPlatform.PathCombine(productPath, "bin"), n, SearchOption.AllDirectories) ?? GetPath(productPath, n, SearchOption.TopDirectoryOnly); } static string GetPath(string dirPath, string dll, SearchOption searchOption) @@ -155,7 +157,7 @@ namespace ASC.Common.DependencyInjection public Assembly Resolving(AssemblyLoadContext context, AssemblyName assemblyName) { - var path = Path.Combine(Path.GetDirectoryName(ResolvePath), $"{assemblyName.Name}.dll"); + var path = CrossPlatform.PathCombine(Path.GetDirectoryName(ResolvePath), $"{assemblyName.Name}.dll"); if (!File.Exists(path)) return null; diff --git a/common/ASC.Common/Logging/Log.cs b/common/ASC.Common/Logging/Log.cs index 02454a9566..472a13124d 100644 --- a/common/ASC.Common/Logging/Log.cs +++ b/common/ASC.Common/Logging/Log.cs @@ -384,7 +384,7 @@ namespace ASC.Common.Logging Configuration = configuration; ConfigurationExtension = configurationExtension; - LogManager.Configuration = new NLog.Config.XmlLoggingConfiguration(Path.Combine(Configuration["pathToConf"], "nlog.config")); + LogManager.Configuration = new NLog.Config.XmlLoggingConfiguration(CrossPlatform.PathCombine(Configuration["pathToConf"], "nlog.config")); LogManager.ThrowConfigExceptions = false; var settings = ConfigurationExtension.GetSetting("log"); diff --git a/common/ASC.Common/Logging/SelfCleaningTarget.cs b/common/ASC.Common/Logging/SelfCleaningTarget.cs index f3c462bab6..bcf43b3c17 100644 --- a/common/ASC.Common/Logging/SelfCleaningTarget.cs +++ b/common/ASC.Common/Logging/SelfCleaningTarget.cs @@ -29,6 +29,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using ASC.Common.Utils; + using NLog; using NLog.Common; using NLog.Targets; @@ -87,7 +89,7 @@ namespace ASC.Common.Logging return; if (!Path.IsPathRooted(dirPath)) - dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dirPath); + dirPath = CrossPlatform.PathCombine(AppDomain.CurrentDomain.BaseDirectory, dirPath); var directory = new DirectoryInfo(dirPath); diff --git a/common/ASC.Common/Utils/CrossPlatformUtils.cs b/common/ASC.Common/Utils/CrossPlatformUtils.cs new file mode 100644 index 0000000000..2633858cc9 --- /dev/null +++ b/common/ASC.Common/Utils/CrossPlatformUtils.cs @@ -0,0 +1,53 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2018 + * + * This program is freeware. You can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html). + * In accordance with Section 7(a) of the GNU GPL 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 more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html + * + * You can contact Ascensio System SIA by email at sales@onlyoffice.com + * + * The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display + * Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3. + * + * Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains + * relevant author attributions when distributing the software. If the display of the logo in its graphic + * form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE" + * in every copy of the program you distribute. + * Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks. + * +*/ + +using System.IO; +using System.Linq; + +namespace ASC.Common.Utils +{ + public static class CrossPlatform + { + public static string PathCombine(string basePath, params string[] additional) + { + var splits = additional.Select(s => s.Split(pathSplitCharacters)).ToArray(); + var totalLength = splits.Sum(arr => arr.Length); + var segments = new string[totalLength + 1]; + segments[0] = basePath; + var i = 0; + foreach (var split in splits) + { + foreach (var value in split) + { + i++; + segments[i] = value; + } + } + return Path.Combine(segments); + } + + static char[] pathSplitCharacters = new char[] { '/', '\\' }; + } +} diff --git a/common/ASC.Core.Common/Security/EmailValidationKeyProvider.cs b/common/ASC.Core.Common/Security/EmailValidationKeyProvider.cs index 373bd3ab0b..fca1e67d55 100644 --- a/common/ASC.Core.Common/Security/EmailValidationKeyProvider.cs +++ b/common/ASC.Core.Common/Security/EmailValidationKeyProvider.cs @@ -54,7 +54,8 @@ namespace ASC.Security.Cryptography private readonly ILog log; private static readonly DateTime _from = new DateTime(2010, 01, 01, 0, 0, 0, DateTimeKind.Utc); - internal readonly TimeSpan ValidInterval; + internal readonly TimeSpan ValidEmailKeyInterval; + internal readonly TimeSpan ValidAuthKeyInterval; private MachinePseudoKeys MachinePseudoKeys { get; } private TenantManager TenantManager { get; } @@ -67,8 +68,13 @@ namespace ASC.Security.Cryptography { validInterval = TimeSpan.FromDays(7); } + if (!TimeSpan.TryParse(configuration["auth:validinterval"], out var authValidInterval)) + { + validInterval = TimeSpan.FromDays(7); + } - ValidInterval = validInterval; + ValidEmailKeyInterval = validInterval; + ValidAuthKeyInterval = authValidInterval; log = options.CurrentValue; } @@ -232,26 +238,31 @@ namespace ASC.Security.Cryptography switch (type) { case ConfirmType.EmpInvite: - checkKeyResult = Provider.ValidateEmailKey(email + type + (int)emplType, key, Provider.ValidInterval); - break; + checkKeyResult = Provider.ValidateEmailKey(email + type + (int)emplType, key, Provider.ValidEmailKeyInterval); + break; + case ConfirmType.LinkInvite: - checkKeyResult = Provider.ValidateEmailKey(type.ToString() + (int)emplType, key, Provider.ValidInterval); - break; + checkKeyResult = Provider.ValidateEmailKey(type.ToString() + (int)emplType, key, Provider.ValidEmailKeyInterval); + break; + case ConfirmType.PortalOwnerChange: - checkKeyResult = Provider.ValidateEmailKey(email + type + uiD.HasValue, key, Provider.ValidInterval); - break; + checkKeyResult = Provider.ValidateEmailKey(email + type + uiD.HasValue, key, Provider.ValidEmailKeyInterval); + break; + case ConfirmType.EmailChange: - checkKeyResult = Provider.ValidateEmailKey(email + type + AuthContext.CurrentAccount.ID, key, Provider.ValidInterval); + checkKeyResult = Provider.ValidateEmailKey(email + type + AuthContext.CurrentAccount.ID, key, Provider.ValidEmailKeyInterval); break; case ConfirmType.PasswordChange: var hash = Authentication.GetUserPasswordStamp(UserManager.GetUserByEmail(email).ID).ToString("s"); - checkKeyResult = Provider.ValidateEmailKey(email + type + hash, key, Provider.ValidInterval); - break; + checkKeyResult = Provider.ValidateEmailKey(email + type + hash, key, Provider.ValidEmailKeyInterval); + break; + case ConfirmType.Activation: - checkKeyResult = Provider.ValidateEmailKey(email + type + uiD, key, Provider.ValidInterval); - break; + checkKeyResult = Provider.ValidateEmailKey(email + type + uiD, key, Provider.ValidEmailKeyInterval); + break; + case ConfirmType.ProfileRemove: // validate UiD if (p == 1) @@ -261,13 +272,22 @@ namespace ASC.Security.Cryptography return ValidationResult.Invalid; } - checkKeyResult = Provider.ValidateEmailKey(email + type + uiD, key, Provider.ValidInterval); + checkKeyResult = Provider.ValidateEmailKey(email + type + uiD, key, Provider.ValidEmailKeyInterval); break; + case ConfirmType.Wizard: - checkKeyResult = Provider.ValidateEmailKey("" + type, key, Provider.ValidInterval); - break; + checkKeyResult = Provider.ValidateEmailKey("" + type, key, Provider.ValidEmailKeyInterval); + break; + + case ConfirmType.PhoneActivation: + case ConfirmType.PhoneAuth: + case ConfirmType.TfaActivation: + case ConfirmType.TfaAuth: + checkKeyResult = Provider.ValidateEmailKey(email + type, key, Provider.ValidAuthKeyInterval); + break; + default: - checkKeyResult = Provider.ValidateEmailKey(email + type, key, Provider.ValidInterval); + checkKeyResult = Provider.ValidateEmailKey(email + type, key, Provider.ValidEmailKeyInterval); break; } diff --git a/common/ASC.Data.Encryption/Crypt.cs b/common/ASC.Data.Encryption/Crypt.cs index 9168d75d8b..8c24572a5f 100644 --- a/common/ASC.Data.Encryption/Crypt.cs +++ b/common/ASC.Data.Encryption/Crypt.cs @@ -29,6 +29,7 @@ using System.IO; using System.Security.Cryptography; using ASC.Common; +using ASC.Common.Utils; using ASC.Core.Encryption; using Microsoft.Extensions.Configuration; @@ -292,12 +293,12 @@ namespace ASC.Data.Encryption { var dir = string.IsNullOrEmpty(TempDir) ? Path.GetDirectoryName(filePath) : TempDir; var name = Path.GetFileNameWithoutExtension(filePath); - var result = Path.Combine(dir, string.Format("{0}_{1}{2}", Storage, name, ext)); + var result = CrossPlatform.PathCombine(dir, string.Format("{0}_{1}{2}", Storage, name, ext)); var index = 1; while (File.Exists(result)) { - result = Path.Combine(dir, string.Format("{0}_{1}({2}){3}", Storage, name, index++, ext)); + result = CrossPlatform.PathCombine(dir, string.Format("{0}_{1}({2}){3}", Storage, name, index++, ext)); } return result; diff --git a/common/ASC.Data.Reassigns/ASC.Data.Reassigns.csproj b/common/ASC.Data.Reassigns/ASC.Data.Reassigns.csproj index 8a14afe88c..0d6760d0a0 100644 --- a/common/ASC.Data.Reassigns/ASC.Data.Reassigns.csproj +++ b/common/ASC.Data.Reassigns/ASC.Data.Reassigns.csproj @@ -1,10 +1,7 @@ - + net5.0 - - Library - diff --git a/common/ASC.Data.Reassigns/Properties/launchSettings.json b/common/ASC.Data.Reassigns/Properties/launchSettings.json deleted file mode 100644 index 502293cd88..0000000000 --- a/common/ASC.Data.Reassigns/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:64832/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "ASC.Data.Reassigns": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:64833/" - } - } -} \ No newline at end of file diff --git a/common/ASC.Data.Storage/ASC.Data.Storage.csproj b/common/ASC.Data.Storage/ASC.Data.Storage.csproj index 9d6cca42c3..df0b9c8f57 100644 --- a/common/ASC.Data.Storage/ASC.Data.Storage.csproj +++ b/common/ASC.Data.Storage/ASC.Data.Storage.csproj @@ -1,10 +1,7 @@ - + net5.0 - - Library - diff --git a/common/ASC.Data.Storage/BaseStorage.cs b/common/ASC.Data.Storage/BaseStorage.cs index 640d2ecf00..15d89e8d4e 100644 --- a/common/ASC.Data.Storage/BaseStorage.cs +++ b/common/ASC.Data.Storage/BaseStorage.cs @@ -32,6 +32,7 @@ using System.Linq; using System.Web; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Core; using ASC.Data.Storage.Configuration; using ASC.Security.Cryptography; @@ -305,7 +306,7 @@ namespace ASC.Data.Storage var filePaths = ListFilesRelative(domain, path, pattern, recursive); return Array.ConvertAll( filePaths, - x => GetUri(domain, Path.Combine(PathUtils.Normalize(path), x))); + x => GetUri(domain, CrossPlatform.PathCombine(PathUtils.Normalize(path), x))); } public bool IsFile(string path) diff --git a/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSessionHolder.cs b/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSessionHolder.cs index 9813a5ca0d..5e75d5b45d 100644 --- a/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSessionHolder.cs +++ b/common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSessionHolder.cs @@ -30,6 +30,7 @@ using System.IO; using System.Linq; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Data.Storage; using Microsoft.Extensions.Options; @@ -177,7 +178,7 @@ namespace ASC.Core.ChunkedUploader private string GetPathWithId(string id) { - return Path.Combine(StoragePath, id + ".session"); + return CrossPlatform.PathCombine(StoragePath, id + ".session"); } } } \ No newline at end of file diff --git a/common/ASC.Data.Storage/DiscStorage/DiscDataHandler.cs b/common/ASC.Data.Storage/DiscStorage/DiscDataHandler.cs index 057910fe0a..9c2aceaa12 100644 --- a/common/ASC.Data.Storage/DiscStorage/DiscDataHandler.cs +++ b/common/ASC.Data.Storage/DiscStorage/DiscDataHandler.cs @@ -30,6 +30,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; +using ASC.Common.Utils; using ASC.Common.Web; using Microsoft.AspNetCore.Builder; @@ -103,12 +104,12 @@ namespace ASC.Data.Storage.DiscStorage { var pathInfo = GetRouteValue("pathInfo").Replace('/', Path.DirectorySeparatorChar); - var path = Path.Combine(physPath, pathInfo); + var path = CrossPlatform.PathCombine(physPath, pathInfo); var tenant = GetRouteValue("0"); if (string.IsNullOrEmpty(tenant)) { - tenant = Path.Combine(GetRouteValue("t1"), GetRouteValue("t2"), GetRouteValue("t3")); + tenant = CrossPlatform.PathCombine(GetRouteValue("t1"), GetRouteValue("t2"), GetRouteValue("t3")); } path = path.Replace("{0}", tenant); diff --git a/common/ASC.Data.Storage/DiscStorage/DiscDataStore.cs b/common/ASC.Data.Storage/DiscStorage/DiscDataStore.cs index a65c4a2a59..0b7fcf4d14 100644 --- a/common/ASC.Data.Storage/DiscStorage/DiscDataStore.cs +++ b/common/ASC.Data.Storage/DiscStorage/DiscDataStore.cs @@ -31,6 +31,7 @@ using System.Linq; using ASC.Common; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Core; using ASC.Core.Encryption; using ASC.Data.Storage.Configuration; @@ -647,7 +648,7 @@ namespace ASC.Data.Storage.DiscStorage // Copy each file into it's new directory. foreach (var fi in source.GetFiles()) { - var fp = Path.Combine(target.ToString(), fi.Name); + var fp = CrossPlatform.PathCombine(target.ToString(), fi.Name); fi.CopyTo(fp, true); var size = Crypt.GetFileSize(fp); QuotaUsedAdd(newdomain, size); @@ -698,7 +699,7 @@ namespace ASC.Data.Storage.DiscStorage { var pathMap = GetPath(domain); //Build Dir - var target = Path.Combine(pathMap.PhysicalPath, PathUtils.Normalize(path)); + var target = CrossPlatform.PathCombine(pathMap.PhysicalPath, PathUtils.Normalize(path)); ValidatePath(target); return target; } diff --git a/common/ASC.Data.Storage/DiscStorage/MappedPath.cs b/common/ASC.Data.Storage/DiscStorage/MappedPath.cs index a3becfb559..0156727572 100644 --- a/common/ASC.Data.Storage/DiscStorage/MappedPath.cs +++ b/common/ASC.Data.Storage/DiscStorage/MappedPath.cs @@ -25,8 +25,10 @@ using System.Collections.Generic; -using System.IO; - +using System.IO; + +using ASC.Common.Utils; + namespace ASC.Data.Storage.DiscStorage { internal class MappedPath @@ -44,7 +46,7 @@ namespace ASC.Data.Storage.DiscStorage tenant = tenant.Trim('/'); ppath = PathUtils.ResolvePhysicalPath(ppath, storageConfig); - PhysicalPath = ppath.IndexOf('{') == -1 && appendTenant ? Path.Combine(ppath, tenant) : string.Format(ppath, tenant); + PhysicalPath = ppath.IndexOf('{') == -1 && appendTenant ? CrossPlatform.PathCombine(ppath, tenant) : string.Format(ppath, tenant); } public MappedPath AppendDomain(string domain) @@ -52,7 +54,7 @@ namespace ASC.Data.Storage.DiscStorage domain = domain.Replace('.', '_'); //Domain prep. Remove dots return new MappedPath(PathUtils) { - PhysicalPath = Path.Combine(PhysicalPath, PathUtils.Normalize(domain, true)), + PhysicalPath = CrossPlatform.PathCombine(PhysicalPath, PathUtils.Normalize(domain, true)), }; } } diff --git a/common/ASC.Data.Storage/PathUtils.cs b/common/ASC.Data.Storage/PathUtils.cs index 00076f729e..81f0f9b983 100644 --- a/common/ASC.Data.Storage/PathUtils.cs +++ b/common/ASC.Data.Storage/PathUtils.cs @@ -29,6 +29,7 @@ using System.Collections.Generic; using System.IO; using ASC.Common; +using ASC.Common.Utils; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -113,7 +114,7 @@ namespace ASC.Data.Storage if (!Path.IsPathRooted(physPath)) { - physPath = Path.GetFullPath(Path.Combine(HostEnvironment.ContentRootPath, physPath.Trim(Path.DirectorySeparatorChar))); + physPath = Path.GetFullPath(CrossPlatform.PathCombine(HostEnvironment.ContentRootPath, physPath.Trim(Path.DirectorySeparatorChar))); } return physPath; } diff --git a/common/ASC.Data.Storage/Properties/launchSettings.json b/common/ASC.Data.Storage/Properties/launchSettings.json deleted file mode 100644 index fde01bbe09..0000000000 --- a/common/ASC.Data.Storage/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:63105/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "ASC.Data.Storage": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:63106/" - } - } -} \ No newline at end of file diff --git a/common/ASC.Data.Storage/StaticUploader.cs b/common/ASC.Data.Storage/StaticUploader.cs index 80e7cbdb14..4ed1377231 100644 --- a/common/ASC.Data.Storage/StaticUploader.cs +++ b/common/ASC.Data.Storage/StaticUploader.cs @@ -35,6 +35,7 @@ using ASC.Common; using ASC.Common.Caching; using ASC.Common.Logging; using ASC.Common.Threading.Progress; +using ASC.Common.Utils; using ASC.Core; using ASC.Core.Common.Settings; using ASC.Core.Tenants; @@ -269,7 +270,7 @@ namespace ASC.Data.Storage foreach (var file in directoryFiles) { var filePath = file.Substring(mappedPath.TrimEnd('/').Length); - tasks.Add(StaticUploader.UploadFileAsync(Path.Combine(relativePath, filePath), file, (res) => StepDone())); + tasks.Add(StaticUploader.UploadFileAsync(CrossPlatform.PathCombine(relativePath, filePath), file, (res) => StepDone())); } await Task.WhenAll(tasks); @@ -280,7 +281,7 @@ namespace ASC.Data.Storage foreach (var file in directoryFiles) { var filePath = file.Substring(mappedPath.TrimEnd('/').Length); - StaticUploader.UploadFileAsync(Path.Combine(relativePath, filePath), file, (res) => StepDone()).Wait(); + StaticUploader.UploadFileAsync(CrossPlatform.PathCombine(relativePath, filePath), file, (res) => StepDone()).Wait(); } } } diff --git a/common/ASC.Data.Storage/StorageHandler.cs b/common/ASC.Data.Storage/StorageHandler.cs index 0cbf0c0428..64a65d16e7 100644 --- a/common/ASC.Data.Storage/StorageHandler.cs +++ b/common/ASC.Data.Storage/StorageHandler.cs @@ -34,6 +34,7 @@ using System.Threading.Tasks; using System.Web; using ASC.Common; +using ASC.Common.Utils; using ASC.Core; using ASC.Security.Cryptography; @@ -75,7 +76,7 @@ namespace ASC.Data.Storage.DiscStorage } var storage = storageFactory.GetStorage(tenantManager.GetCurrentTenant().TenantId.ToString(CultureInfo.InvariantCulture), _module); - var path = Path.Combine(_path, GetRouteValue("pathInfo").Replace('/', Path.DirectorySeparatorChar)); + var path = CrossPlatform.PathCombine(_path, GetRouteValue("pathInfo").Replace('/', Path.DirectorySeparatorChar)); var header = context.Request.Query[Constants.QUERY_HEADER].FirstOrDefault() ?? ""; var auth = context.Request.Query[Constants.QUERY_AUTH].FirstOrDefault() ?? ""; diff --git a/common/ASC.Data.Storage/WebPath.cs b/common/ASC.Data.Storage/WebPath.cs index bbe2ec5281..8596f8b840 100644 --- a/common/ASC.Data.Storage/WebPath.cs +++ b/common/ASC.Data.Storage/WebPath.cs @@ -33,6 +33,7 @@ using System.Net; using ASC.Common; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Core; using ASC.Core.Common.Settings; using ASC.Data.Storage.Configuration; @@ -219,7 +220,7 @@ namespace ASC.Data.Storage if (Uri.IsWellFormedUriString(path, UriKind.Relative) && HttpContextAccessor?.HttpContext != null) { //Local - Existing[path] = File.Exists(Path.Combine(HostEnvironment.ContentRootPath, path)); + Existing[path] = File.Exists(CrossPlatform.PathCombine(HostEnvironment.ContentRootPath, path)); } if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) { diff --git a/common/ASC.FederatedLogin/ASC.FederatedLogin.csproj b/common/ASC.FederatedLogin/ASC.FederatedLogin.csproj index f4043aa003..8e7bd5a699 100644 --- a/common/ASC.FederatedLogin/ASC.FederatedLogin.csproj +++ b/common/ASC.FederatedLogin/ASC.FederatedLogin.csproj @@ -1,10 +1,7 @@ - + net5.0 - - Library - diff --git a/common/ASC.FederatedLogin/LoginProviders/BaseLoginProvider.cs b/common/ASC.FederatedLogin/LoginProviders/BaseLoginProvider.cs index 5eb3bcc0cb..b8a7e7cf63 100644 --- a/common/ASC.FederatedLogin/LoginProviders/BaseLoginProvider.cs +++ b/common/ASC.FederatedLogin/LoginProviders/BaseLoginProvider.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.Threading; +using ASC.Common; using ASC.Common.Caching; using ASC.Common.Utils; using ASC.Core; @@ -160,5 +161,18 @@ namespace ASC.FederatedLogin.LoginProviders } public abstract LoginProfile GetLoginProfile(string accessToken); + } + + public class BaseLoginProviderExtension + { + public static void Register(DIHelper services) + { + services.TryAdd(); + services.TryAdd(); + services.TryAdd(); + services.TryAdd(); + services.TryAdd(); + services.TryAdd(); + } } } diff --git a/common/ASC.FederatedLogin/Properties/launchSettings.json b/common/ASC.FederatedLogin/Properties/launchSettings.json deleted file mode 100644 index 39c714f21b..0000000000 --- a/common/ASC.FederatedLogin/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:58519/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "ASC.FederatedLogin": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:58520/" - } - } -} \ No newline at end of file diff --git a/common/ASC.Notify.Textile/ASC.Notify.Textile.csproj b/common/ASC.Notify.Textile/ASC.Notify.Textile.csproj index d88f119483..1027372d1f 100644 --- a/common/ASC.Notify.Textile/ASC.Notify.Textile.csproj +++ b/common/ASC.Notify.Textile/ASC.Notify.Textile.csproj @@ -1,4 +1,4 @@ - + 9.0.30729 net5.0 @@ -6,9 +6,6 @@ Ascensio System SIA ASC.Notify.Textile (c) Ascensio System SIA. All rights reserved - - Library - full diff --git a/common/ASC.Notify.Textile/Properties/launchSettings.json b/common/ASC.Notify.Textile/Properties/launchSettings.json deleted file mode 100644 index f521a452e1..0000000000 --- a/common/ASC.Notify.Textile/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:56421/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "ASC.Notify.Textile": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:56422/" - } - } -} \ No newline at end of file diff --git a/common/ASC.Resource.Manager/Program.cs b/common/ASC.Resource.Manager/Program.cs index a43f51a60d..9100c48bb2 100644 --- a/common/ASC.Resource.Manager/Program.cs +++ b/common/ASC.Resource.Manager/Program.cs @@ -303,6 +303,7 @@ namespace ASC.Resource.Manager var bag = new ConcurrentBag(); var csFiles = Directory.GetFiles(Path.GetFullPath(path), "*.cs", SearchOption.AllDirectories).Except(Directory.GetFiles(Path.GetFullPath(path), "*Resource.Designer.cs", SearchOption.AllDirectories)); + csFiles = csFiles.Concat(Directory.GetFiles(Path.GetFullPath(path), "*.cshtml", SearchOption.AllDirectories)).ToArray(); csFiles = csFiles.Concat(Directory.GetFiles(Path.GetFullPath(path), "*.aspx", SearchOption.AllDirectories)).ToArray(); csFiles = csFiles.Concat(Directory.GetFiles(Path.GetFullPath(path), "*.Master", SearchOption.AllDirectories)).ToArray(); csFiles = csFiles.Concat(Directory.GetFiles(Path.GetFullPath(path), "*.ascx", SearchOption.AllDirectories)).ToArray(); diff --git a/common/ASC.Textile/ASC.Textile.csproj b/common/ASC.Textile/ASC.Textile.csproj index 464b0d56b3..af1f629945 100644 --- a/common/ASC.Textile/ASC.Textile.csproj +++ b/common/ASC.Textile/ASC.Textile.csproj @@ -19,6 +19,8 @@ none - + + + \ No newline at end of file diff --git a/common/ASC.VoipService/ASC.VoipService.csproj b/common/ASC.VoipService/ASC.VoipService.csproj index b31161c6fa..673768a1f0 100644 --- a/common/ASC.VoipService/ASC.VoipService.csproj +++ b/common/ASC.VoipService/ASC.VoipService.csproj @@ -13,8 +13,11 @@ + + + @@ -31,10 +34,6 @@ - - - - diff --git a/common/services/ASC.ApiSystem/ASC.ApiSystem.csproj b/common/services/ASC.ApiSystem/ASC.ApiSystem.csproj index b2b33868a8..fdf210a60c 100644 --- a/common/services/ASC.ApiSystem/ASC.ApiSystem.csproj +++ b/common/services/ASC.ApiSystem/ASC.ApiSystem.csproj @@ -14,6 +14,8 @@ + + diff --git a/common/services/ASC.ApiSystem/Program.cs b/common/services/ASC.ApiSystem/Program.cs index 806dea63e9..53b4778c65 100644 --- a/common/services/ASC.ApiSystem/Program.cs +++ b/common/services/ASC.ApiSystem/Program.cs @@ -24,8 +24,13 @@ */ +using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +using ASC.Common.Utils; using Autofac.Extensions.DependencyInjection; @@ -37,18 +42,40 @@ namespace ASC.ApiSystem { public class Program { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); + public async static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); } - public static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); + var builder = webBuilder.UseStartup(); + + builder.ConfigureKestrel((hostingContext, serverOptions) => + { + var kestrelConfig = hostingContext.Configuration.GetSection("Kestrel"); + + if (!kestrelConfig.Exists()) return; + + var unixSocket = kestrelConfig.GetValue("ListenUnixSocket"); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + if (!String.IsNullOrWhiteSpace(unixSocket)) + { + unixSocket = String.Format(unixSocket, hostingContext.HostingEnvironment.ApplicationName.Replace("ASC.", "").Replace(".", "")); + + serverOptions.ListenUnixSocket(unixSocket); + } + } + }); }) .ConfigureAppConfiguration((hostingContext, config) => { @@ -56,7 +83,7 @@ namespace ASC.ApiSystem var path = buided["pathToConf"]; if (!Path.IsPathRooted(path)) { - path = Path.GetFullPath(Path.Combine(hostingContext.HostingEnvironment.ContentRootPath, path)); + path = Path.GetFullPath(CrossPlatform.PathCombine(hostingContext.HostingEnvironment.ContentRootPath, path)); } config.SetBasePath(path); @@ -74,6 +101,5 @@ namespace ASC.ApiSystem }); }); - } } } diff --git a/common/services/ASC.ApiSystem/Properties/launchSettings.json b/common/services/ASC.ApiSystem/Properties/launchSettings.json index 00d4f36b02..ef70f2c7c2 100644 --- a/common/services/ASC.ApiSystem/Properties/launchSettings.json +++ b/common/services/ASC.ApiSystem/Properties/launchSettings.json @@ -1,33 +1,29 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5010", - "sslPort": 0 - } - }, +{ "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "apisystem", - "log__dir": "../../../Logs" - } - }, - "ASC.ApiSystem": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, - "applicationUrl": "http://localhost:5010", + "launchUrl": "http://localhost:5010/portal/test", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "apisystem", - "log__dir": "../../../Logs" + "log__dir": "../../../Logs", + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5010" } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "launchUrl": "http://localhost:5010/portal/test", + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "apisystem", + "log__dir": "../../../Logs", + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5010" + }, + "distributionName": "Ubuntu-20.04" } } } diff --git a/common/services/ASC.AuditTrail/ASC.AuditTrail.csproj b/common/services/ASC.AuditTrail/ASC.AuditTrail.csproj index 86d5b9db3c..c59abe326f 100644 --- a/common/services/ASC.AuditTrail/ASC.AuditTrail.csproj +++ b/common/services/ASC.AuditTrail/ASC.AuditTrail.csproj @@ -1,10 +1,7 @@ - + net5.0 - - Library - diff --git a/common/services/ASC.AuditTrail/Properties/launchSettings.json b/common/services/ASC.AuditTrail/Properties/launchSettings.json deleted file mode 100644 index 4b8d5c624f..0000000000 --- a/common/services/ASC.AuditTrail/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:59977/", - "sslPort": 44344 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "ASC.AuditTrail": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" - } - } -} \ No newline at end of file diff --git a/common/services/ASC.Data.Backup/ASC.Data.Backup.csproj b/common/services/ASC.Data.Backup/ASC.Data.Backup.csproj index a9b9e5afa7..d76c4348fb 100644 --- a/common/services/ASC.Data.Backup/ASC.Data.Backup.csproj +++ b/common/services/ASC.Data.Backup/ASC.Data.Backup.csproj @@ -23,6 +23,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/common/services/ASC.Data.Backup/Core/DbBackupProvider.cs b/common/services/ASC.Data.Backup/Core/DbBackupProvider.cs index b3c7dc4e7f..8efd1f97f5 100644 --- a/common/services/ASC.Data.Backup/Core/DbBackupProvider.cs +++ b/common/services/ASC.Data.Backup/Core/DbBackupProvider.cs @@ -34,7 +34,8 @@ using System.Threading; using System.Xml.Linq; using ASC.Common; - +using ASC.Common.Utils; + namespace ASC.Data.Backup { [Scope] @@ -103,7 +104,7 @@ namespace ASC.Data.Backup { var map = new ExeConfigurationFileMap { - ExeConfigFilename = string.Compare(Path.GetExtension(config), ".config", true) == 0 ? config : Path.Combine(config, "Web.config") + ExeConfigFilename = string.Compare(Path.GetExtension(config), ".config", true) == 0 ? config : CrossPlatform.PathCombine(config, "Web.config") }; return ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None); } diff --git a/common/services/ASC.Data.Backup/Core/DbHelper.cs b/common/services/ASC.Data.Backup/Core/DbHelper.cs index c86bfd2020..80c9865af6 100644 --- a/common/services/ASC.Data.Backup/Core/DbHelper.cs +++ b/common/services/ASC.Data.Backup/Core/DbHelper.cs @@ -11,6 +11,7 @@ using System.Xml.XPath; using ASC.Common; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Data.Backup.EF.Context; using Microsoft.EntityFrameworkCore; @@ -37,7 +38,7 @@ namespace ASC.Data.Backup var file = connectionString.ElementInformation.Source; if ("web.connections.config".Equals(Path.GetFileName(file), StringComparison.InvariantCultureIgnoreCase)) { - file = Path.Combine(Path.GetDirectoryName(file), "Web.config"); + file = CrossPlatform.PathCombine(Path.GetDirectoryName(file), "Web.config"); } var xconfig = XDocument.Load(file); var provider = xconfig.XPathSelectElement("/configuration/system.data/DbProviderFactories/add[@invariant='" + connectionString.ProviderName + "']"); diff --git a/common/services/ASC.Data.Backup/Core/FileBackupProvider.cs b/common/services/ASC.Data.Backup/Core/FileBackupProvider.cs index 5dff3755e4..0050a23ffd 100644 --- a/common/services/ASC.Data.Backup/Core/FileBackupProvider.cs +++ b/common/services/ASC.Data.Backup/Core/FileBackupProvider.cs @@ -32,6 +32,7 @@ using System.Xml.Linq; using ASC.Common; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Data.Storage; using Microsoft.Extensions.Options; @@ -148,7 +149,7 @@ namespace ASC.Data.Backup private string GetBackupPath(FileBackupInfo backupInfo) { - return Path.Combine(backupInfo.Module, Path.Combine(backupInfo.Domain, backupInfo.Path.Replace('/', '\\'))); + return CrossPlatform.PathCombine(backupInfo.Module, CrossPlatform.PathCombine(backupInfo.Domain, backupInfo.Path.Replace('/', '\\'))); } diff --git a/common/services/ASC.Data.Backup/Core/ZipOperator.cs b/common/services/ASC.Data.Backup/Core/ZipOperator.cs index ce4c9876c9..8438c9dfc7 100644 --- a/common/services/ASC.Data.Backup/Core/ZipOperator.cs +++ b/common/services/ASC.Data.Backup/Core/ZipOperator.cs @@ -28,6 +28,8 @@ using System; using System.Collections.Generic; using System.IO; +using ASC.Common.Utils; + using SharpCompress.Common; using SharpCompress.Readers; using SharpCompress.Writers; @@ -65,7 +67,7 @@ namespace ASC.Data.Backup public ZipReadOperator(string targetFile) { - tmpdir = Path.Combine(Path.GetDirectoryName(targetFile), Path.GetFileNameWithoutExtension(targetFile).Replace('>', '_').Replace(':', '_').Replace('?', '_')); + tmpdir = CrossPlatform.PathCombine(Path.GetDirectoryName(targetFile), Path.GetFileNameWithoutExtension(targetFile).Replace('>', '_').Replace(':', '_').Replace('?', '_')); Entries = new List(); using var stream = File.OpenRead(targetFile); @@ -82,7 +84,7 @@ namespace ASC.Data.Backup fullPath = streamReader.ReadToEnd().TrimEnd(char.MinValue); } - fullPath = Path.Combine(tmpdir, fullPath); + fullPath = CrossPlatform.PathCombine(tmpdir, fullPath); if (!Directory.Exists(Path.GetDirectoryName(fullPath))) { @@ -111,7 +113,7 @@ namespace ASC.Data.Backup public Stream GetEntry(string key) { - var filePath = Path.Combine(tmpdir, key); + var filePath = CrossPlatform.PathCombine(tmpdir, key); return File.Exists(filePath) ? File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read) : null; } diff --git a/common/services/ASC.Data.Backup/Program.cs b/common/services/ASC.Data.Backup/Program.cs index 3851ae9cd0..bccee04c6a 100644 --- a/common/services/ASC.Data.Backup/Program.cs +++ b/common/services/ASC.Data.Backup/Program.cs @@ -1,7 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Threading.Tasks; +using ASC.Common.Utils; + using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; @@ -11,14 +15,41 @@ using Microsoft.Extensions.Hosting; namespace ASC.Data.Backup { public class Program - { - public static async Task Main(string[] args) - { - await Host.CreateDefaultBuilder(args) + { + public async static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); + var builder = webBuilder.UseStartup(); + + builder.ConfigureKestrel((hostingContext, serverOptions) => + { + var kestrelConfig = hostingContext.Configuration.GetSection("Kestrel"); + + if (!kestrelConfig.Exists()) return; + + var unixSocket = kestrelConfig.GetValue("ListenUnixSocket"); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + if (!String.IsNullOrWhiteSpace(unixSocket)) + { + unixSocket = String.Format(unixSocket, hostingContext.HostingEnvironment.ApplicationName.Replace("ASC.", "").Replace(".", "")); + + serverOptions.ListenUnixSocket(unixSocket); + } + } + }); }) .ConfigureAppConfiguration((hostContext, config) => { @@ -26,7 +57,7 @@ namespace ASC.Data.Backup var path = buided["pathToConf"]; if (!Path.IsPathRooted(path)) { - path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path)); + path = Path.GetFullPath(CrossPlatform.PathCombine(hostContext.HostingEnvironment.ContentRootPath, path)); } config.SetBasePath(path); var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); @@ -44,10 +75,7 @@ namespace ASC.Data.Backup {"pathToConf", path } } ); - }) - .UseConsoleLifetime() - .Build() - .RunAsync(); - } + }); + } } diff --git a/common/services/ASC.Data.Backup/Properties/launchSettings.json b/common/services/ASC.Data.Backup/Properties/launchSettings.json index 515993c7bc..68d9288976 100644 --- a/common/services/ASC.Data.Backup/Properties/launchSettings.json +++ b/common/services/ASC.Data.Backup/Properties/launchSettings.json @@ -1,35 +1,31 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5012/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "backup", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products" - } - }, - "ASC.Data.Backup": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, + "launchUrl": "http://localhost:5012/api/2.0/backup/backuptmp", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "backup", "log__dir": "../../../Logs", - "core__products__folder": "../../../products" + "core__products__folder": "../../../products", + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5012" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "launchUrl": "http://localhost:5012/api/2.0/backup/backuptmp", + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "backup", + "log__dir": "../../../Logs", + "core__products__folder": "../../../products", + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5012" }, - "applicationUrl": "http://localhost:5012/" + "distributionName": "Ubuntu-20.04" } } -} \ No newline at end of file +} diff --git a/common/services/ASC.Data.Backup/Service/BackupSettings.cs b/common/services/ASC.Data.Backup/Service/BackupSettings.cs index 5dedc073e2..b0d9b57611 100644 --- a/common/services/ASC.Data.Backup/Service/BackupSettings.cs +++ b/common/services/ASC.Data.Backup/Service/BackupSettings.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.IO; +using ASC.Common.Utils; + namespace ASC.Data.Backup.Service { public class BackupSettings @@ -52,7 +54,7 @@ namespace ASC.Data.Backup.Service } if (Elements.Count == 0) { - return Path.Combine("..", "..", "WebStudio"); + return CrossPlatform.PathCombine("..", "..", "WebStudio"); } if (Elements.Count == 1) { diff --git a/common/services/ASC.Data.Backup/Service/BackupWorker.cs b/common/services/ASC.Data.Backup/Service/BackupWorker.cs index 77d5ca0ee7..cce798f1af 100644 --- a/common/services/ASC.Data.Backup/Service/BackupWorker.cs +++ b/common/services/ASC.Data.Backup/Service/BackupWorker.cs @@ -34,6 +34,7 @@ using ASC.Common; using ASC.Common.Caching; using ASC.Common.Logging; using ASC.Common.Threading.Progress; +using ASC.Common.Utils; using ASC.Core; using ASC.Core.Tenants; using ASC.Data.Backup.Contracts; @@ -378,7 +379,7 @@ namespace ASC.Data.Backup.Service var dateTime = coreBaseSettings.Standalone ? DateTime.Now : DateTime.UtcNow; var backupName = string.Format("{0}_{1:yyyy-MM-dd_HH-mm-ss}.{2}", tenantManager.GetTenant(TenantId).TenantAlias, dateTime, ArchiveFormat); - var tempFile = Path.Combine(TempFolder, backupName); + var tempFile = CrossPlatform.PathCombine(TempFolder, backupName); var storagePath = tempFile; try { diff --git a/common/services/ASC.Data.Backup/Storage/LocalBackupStorage.cs b/common/services/ASC.Data.Backup/Storage/LocalBackupStorage.cs index 81bd82816c..37009952c5 100644 --- a/common/services/ASC.Data.Backup/Storage/LocalBackupStorage.cs +++ b/common/services/ASC.Data.Backup/Storage/LocalBackupStorage.cs @@ -28,7 +28,8 @@ using System; using System.IO; using ASC.Common; - +using ASC.Common.Utils; + namespace ASC.Data.Backup.Storage { [Scope] @@ -40,7 +41,7 @@ namespace ASC.Data.Backup.Storage { throw new FileNotFoundException("Directory not found."); } - var storagePath = Path.Combine(storageBasePath, Path.GetFileName(localPath)); + var storagePath = CrossPlatform.PathCombine(storageBasePath, Path.GetFileName(localPath)); if (localPath != storagePath) { File.Copy(localPath, storagePath, true); diff --git a/common/services/ASC.Data.Backup/Tasks/BackupPortalTask.cs b/common/services/ASC.Data.Backup/Tasks/BackupPortalTask.cs index 0966143bfc..0a2ba268dd 100644 --- a/common/services/ASC.Data.Backup/Tasks/BackupPortalTask.cs +++ b/common/services/ASC.Data.Backup/Tasks/BackupPortalTask.cs @@ -36,6 +36,7 @@ using System.Xml.Linq; using ASC.Common; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Core; using ASC.Core.Common.EF; using ASC.Data.Backup.EF.Context; @@ -152,9 +153,9 @@ namespace ASC.Data.Backup.Tasks excluded.Add("res_"); var dir = Path.GetDirectoryName(BackupFilePath); - var subDir = Path.Combine(dir, Path.GetFileNameWithoutExtension(BackupFilePath)); - var schemeDir = Path.Combine(subDir, KeyHelper.GetDatabaseSchema()); - var dataDir = Path.Combine(subDir, KeyHelper.GetDatabaseData()); + var subDir = CrossPlatform.PathCombine(dir, Path.GetFileNameWithoutExtension(BackupFilePath)); + var schemeDir = CrossPlatform.PathCombine(subDir, KeyHelper.GetDatabaseSchema()); + var dataDir = CrossPlatform.PathCombine(subDir, KeyHelper.GetDatabaseData()); if (!Directory.Exists(schemeDir)) { @@ -227,7 +228,7 @@ namespace ASC.Data.Backup.Tasks .FirstOrDefault()); creates.Append(";"); - var path = Path.Combine(dir, t); + var path = CrossPlatform.PathCombine(dir, t); using (var stream = File.OpenWrite(path)) { var bytes = Encoding.UTF8.GetBytes(creates.ToString()); @@ -326,7 +327,7 @@ namespace ASC.Data.Backup.Tasks } } - var path = Path.Combine(dir, t); + var path = CrossPlatform.PathCombine(dir, t); var offset = 0; @@ -443,11 +444,11 @@ namespace ASC.Data.Backup.Tasks Logger.Debug("begin backup storage"); var dir = Path.GetDirectoryName(BackupFilePath); - var subDir = Path.Combine(dir, Path.GetFileNameWithoutExtension(BackupFilePath)); + var subDir = CrossPlatform.PathCombine(dir, Path.GetFileNameWithoutExtension(BackupFilePath)); for (var i = 0; i < files.Count; i += TasksLimit) { - var storageDir = Path.Combine(subDir, KeyHelper.GetStorage()); + var storageDir = CrossPlatform.PathCombine(subDir, KeyHelper.GetStorage()); if (!Directory.Exists(storageDir)) { @@ -470,7 +471,7 @@ namespace ASC.Data.Backup.Tasks var restoreInfoXml = new XElement("storage_restore", files.Select(file => (object)file.ToXElement()).ToArray()); - var tmpPath = Path.Combine(subDir, KeyHelper.GetStorageRestoreInfoZipKey()); + var tmpPath = CrossPlatform.PathCombine(subDir, KeyHelper.GetStorageRestoreInfoZipKey()); Directory.CreateDirectory(Path.GetDirectoryName(tmpPath)); using (var tmpFile = File.OpenWrite(tmpPath)) @@ -490,7 +491,7 @@ namespace ASC.Data.Backup.Tasks private async Task DoDumpFile(BackupFileInfo file, string dir) { var storage = StorageFactory.GetStorage(ConfigPath, file.Tenant.ToString(), file.Module); - var filePath = Path.Combine(dir, file.GetZipKey()); + var filePath = CrossPlatform.PathCombine(dir, file.GetZipKey()); var dirName = Path.GetDirectoryName(filePath); Logger.DebugFormat("backup file {0}", filePath); diff --git a/common/services/ASC.Data.Backup/Tasks/KeyHelper.cs b/common/services/ASC.Data.Backup/Tasks/KeyHelper.cs index aa9aaffcd0..7647abc75e 100644 --- a/common/services/ASC.Data.Backup/Tasks/KeyHelper.cs +++ b/common/services/ASC.Data.Backup/Tasks/KeyHelper.cs @@ -26,6 +26,7 @@ using System.IO; +using ASC.Common.Utils; using ASC.Data.Backup.Tasks.Modules; namespace ASC.Data.Backup.Tasks @@ -66,7 +67,7 @@ namespace ASC.Data.Backup.Tasks public static string GetZipKey(this BackupFileInfo file) { - return Path.Combine(file.Module, file.Domain, file.Path); + return CrossPlatform.PathCombine(file.Module, file.Domain, file.Path); } public static string GetStorage() diff --git a/common/services/ASC.Data.Backup/Tasks/RestorePortalTask.cs b/common/services/ASC.Data.Backup/Tasks/RestorePortalTask.cs index b881381a5c..0c4ad09ada 100644 --- a/common/services/ASC.Data.Backup/Tasks/RestorePortalTask.cs +++ b/common/services/ASC.Data.Backup/Tasks/RestorePortalTask.cs @@ -34,6 +34,7 @@ using System.Xml.Linq; using ASC.Common; using ASC.Common.Caching; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Core; using ASC.Core.Billing; using ASC.Core.Tenants; @@ -286,7 +287,7 @@ namespace ASC.Data.Backup.Tasks var key = file.GetZipKey(); if (Dump) { - key = Path.Combine(KeyHelper.GetStorage(), key); + key = CrossPlatform.PathCombine(KeyHelper.GetStorage(), key); } using var stream = dataReader.GetEntry(key); try diff --git a/common/services/ASC.Data.Backup/Tasks/TransferPortalTask.cs b/common/services/ASC.Data.Backup/Tasks/TransferPortalTask.cs index 6efbe9bb72..b13eb7aca8 100644 --- a/common/services/ASC.Data.Backup/Tasks/TransferPortalTask.cs +++ b/common/services/ASC.Data.Backup/Tasks/TransferPortalTask.cs @@ -31,6 +31,7 @@ using System.Linq; using ASC.Common; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Core.Tenants; using ASC.Data.Backup.Extensions; using ASC.Data.Backup.Tasks.Modules; @@ -246,7 +247,7 @@ namespace ASC.Data.Backup.Tasks if (!Directory.Exists(BackupDirectory ?? DefaultDirectoryName)) Directory.CreateDirectory(BackupDirectory ?? DefaultDirectoryName); - return Path.Combine(BackupDirectory ?? DefaultDirectoryName, tenantAlias + DateTime.UtcNow.ToString("(yyyy-MM-dd HH-mm-ss)") + ".backup"); + return CrossPlatform.PathCombine(BackupDirectory ?? DefaultDirectoryName, tenantAlias + DateTime.UtcNow.ToString("(yyyy-MM-dd HH-mm-ss)") + ".backup"); } } diff --git a/common/services/ASC.Data.Backup/Utils/PathHelper.cs b/common/services/ASC.Data.Backup/Utils/PathHelper.cs index 1542b691ee..4ab8ea2769 100644 --- a/common/services/ASC.Data.Backup/Utils/PathHelper.cs +++ b/common/services/ASC.Data.Backup/Utils/PathHelper.cs @@ -27,6 +27,8 @@ using System.IO; using System.Reflection; +using ASC.Common.Utils; + namespace ASC.Data.Backup.Utils { internal static class PathHelper @@ -40,7 +42,7 @@ namespace ASC.Data.Backup.Utils { if (!Path.IsPathRooted(path)) { - path = Path.Combine(basePath, path); + path = CrossPlatform.PathCombine(basePath, path); } return Path.GetFullPath(path); } @@ -49,7 +51,7 @@ namespace ASC.Data.Backup.Utils { if (!Path.HasExtension(path)) { - path = Path.Combine(path, "Web.config"); + path = CrossPlatform.PathCombine(path, "Web.config"); } return ToRootedPath(path); } @@ -59,7 +61,7 @@ namespace ASC.Data.Backup.Utils string tempPath; do { - tempPath = Path.Combine(tempDir, Path.GetRandomFileName()); + tempPath = CrossPlatform.PathCombine(tempDir, Path.GetRandomFileName()); } while (File.Exists(tempPath)); return tempPath; } diff --git a/common/services/ASC.Data.Storage.Encryption/ASC.Data.Storage.Encryption.csproj b/common/services/ASC.Data.Storage.Encryption/ASC.Data.Storage.Encryption.csproj index 8ad075f7a1..cf68a5f7eb 100644 --- a/common/services/ASC.Data.Storage.Encryption/ASC.Data.Storage.Encryption.csproj +++ b/common/services/ASC.Data.Storage.Encryption/ASC.Data.Storage.Encryption.csproj @@ -22,6 +22,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/common/services/ASC.Data.Storage.Encryption/Program.cs b/common/services/ASC.Data.Storage.Encryption/Program.cs index e6d62c73a5..f630a09f6a 100644 --- a/common/services/ASC.Data.Storage.Encryption/Program.cs +++ b/common/services/ASC.Data.Storage.Encryption/Program.cs @@ -22,10 +22,14 @@ * Pursuant to Section 7 3(e) we decline to grant you any rights under trademark law for use of our trademarks. * */ +using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Threading.Tasks; +using ASC.Common.Utils; + using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; @@ -36,13 +40,40 @@ namespace ASC.Data.Storage.Encryption { public class Program { - public static async Task Main(string[] args) - { - await Host.CreateDefaultBuilder(args) + public async static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); + var builder = webBuilder.UseStartup(); + + builder.ConfigureKestrel((hostingContext, serverOptions) => + { + var kestrelConfig = hostingContext.Configuration.GetSection("Kestrel"); + + if (!kestrelConfig.Exists()) return; + + var unixSocket = kestrelConfig.GetValue("ListenUnixSocket"); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + if (!String.IsNullOrWhiteSpace(unixSocket)) + { + unixSocket = String.Format(unixSocket, hostingContext.HostingEnvironment.ApplicationName.Replace("ASC.", "").Replace(".", "")); + + serverOptions.ListenUnixSocket(unixSocket); + } + } + }); }) .ConfigureAppConfiguration((hostContext, config) => { @@ -50,7 +81,7 @@ namespace ASC.Data.Storage.Encryption var path = buided["pathToConf"]; if (!Path.IsPathRooted(path)) { - path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path)); + path = Path.GetFullPath(CrossPlatform.PathCombine(hostContext.HostingEnvironment.ContentRootPath, path)); } config.SetBasePath(path); var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); @@ -67,10 +98,6 @@ namespace ASC.Data.Storage.Encryption .AddJsonFile("kafka.json") .AddJsonFile($"kafka.{env}.json", true) .AddEnvironmentVariables(); - }) - .UseConsoleLifetime() - .Build() - .RunAsync(); - } + }); } } diff --git a/common/services/ASC.Data.Storage.Encryption/Properties/launchSettings.json b/common/services/ASC.Data.Storage.Encryption/Properties/launchSettings.json index a918ec7506..cdd5853e1f 100644 --- a/common/services/ASC.Data.Storage.Encryption/Properties/launchSettings.json +++ b/common/services/ASC.Data.Storage.Encryption/Properties/launchSettings.json @@ -1,35 +1,29 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5019/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "Encryption", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products" - } - }, - "ASC.Data.Storage.Encryption": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "Encryption", "log__dir": "../../../Logs", - "core__products__folder": "../../../products" + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5019", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "Encryption", + "log__dir": "../../../Logs", + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5019", + "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:5019/" + "distributionName": "Ubuntu-20.04" } } } \ No newline at end of file diff --git a/common/services/ASC.Data.Storage.Migration/ASC.Data.Storage.Migration.csproj b/common/services/ASC.Data.Storage.Migration/ASC.Data.Storage.Migration.csproj index 2b3b00aab4..2f88670730 100644 --- a/common/services/ASC.Data.Storage.Migration/ASC.Data.Storage.Migration.csproj +++ b/common/services/ASC.Data.Storage.Migration/ASC.Data.Storage.Migration.csproj @@ -4,6 +4,11 @@ net5.0 + + + + + diff --git a/common/services/ASC.Data.Storage.Migration/Program.cs b/common/services/ASC.Data.Storage.Migration/Program.cs index 5af923d103..39e84558dc 100644 --- a/common/services/ASC.Data.Storage.Migration/Program.cs +++ b/common/services/ASC.Data.Storage.Migration/Program.cs @@ -6,7 +6,8 @@ using ASC.Common; using ASC.Common.Caching; using ASC.Common.DependencyInjection; using ASC.Common.Logging; - +using ASC.Common.Utils; + using Autofac; using Autofac.Extensions.DependencyInjection; @@ -18,9 +19,17 @@ namespace ASC.Data.Storage.Migration { public class Program { - public static async Task Main(string[] args) - { - await Host.CreateDefaultBuilder(args) + public async static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureAppConfiguration((hostContext, config) => { @@ -28,7 +37,7 @@ namespace ASC.Data.Storage.Migration var path = buided["pathToConf"]; if (!Path.IsPathRooted(path)) { - path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path)); + path = Path.GetFullPath(CrossPlatform.PathCombine(hostContext.HostingEnvironment.ContentRootPath, path)); } config.SetBasePath(path); var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); @@ -63,10 +72,6 @@ namespace ASC.Data.Storage.Migration .ConfigureContainer((context, builder) => { builder.Register(context.Configuration); - }) - .UseConsoleLifetime() - .Build() - .RunAsync(); - } + }); } } diff --git a/common/services/ASC.Data.Storage.Migration/Properties/launchSettings.json b/common/services/ASC.Data.Storage.Migration/Properties/launchSettings.json index 64843ba21c..0825f31aff 100644 --- a/common/services/ASC.Data.Storage.Migration/Properties/launchSettings.json +++ b/common/services/ASC.Data.Storage.Migration/Properties/launchSettings.json @@ -1,36 +1,29 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5018/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "storage.migration", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products" - } - }, - "ASC.Data.Storage.Migration": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "storage.migration", "log__dir": "../../../Logs", - "core__products__folder": "../../../products" + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5018", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "storage.migration", + "log__dir": "../../../Logs", + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5018", + "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:5018/" + "distributionName": "Ubuntu-20.04" } } -} - +} \ No newline at end of file diff --git a/common/services/ASC.ElasticSearch/ASC.ElasticSearch.csproj b/common/services/ASC.ElasticSearch/ASC.ElasticSearch.csproj index 673ee88e8b..0b356a13c8 100644 --- a/common/services/ASC.ElasticSearch/ASC.ElasticSearch.csproj +++ b/common/services/ASC.ElasticSearch/ASC.ElasticSearch.csproj @@ -18,6 +18,11 @@ none false + + + + + @@ -37,9 +42,6 @@ - - - diff --git a/common/services/ASC.Notify/ASC.Notify.csproj b/common/services/ASC.Notify/ASC.Notify.csproj index 412a5e62e2..cfe2a6763e 100644 --- a/common/services/ASC.Notify/ASC.Notify.csproj +++ b/common/services/ASC.Notify/ASC.Notify.csproj @@ -13,6 +13,8 @@ + + diff --git a/common/services/ASC.Notify/Program.cs b/common/services/ASC.Notify/Program.cs index 94fe789be4..2f329aa41a 100644 --- a/common/services/ASC.Notify/Program.cs +++ b/common/services/ASC.Notify/Program.cs @@ -6,6 +6,7 @@ using ASC.Common; using ASC.Common.Caching; using ASC.Common.DependencyInjection; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Core.Notify.Senders; using ASC.Notify.Config; @@ -20,9 +21,17 @@ namespace ASC.Notify { public class Program { - public static async Task Main(string[] args) + public async static Task Main(string[] args) { - var host = Host.CreateDefaultBuilder(args) + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureAppConfiguration((hostContext, config) => { @@ -30,7 +39,7 @@ namespace ASC.Notify var path = buided["pathToConf"]; if (!Path.IsPathRooted(path)) { - path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path)); + path = Path.GetFullPath(CrossPlatform.PathCombine(hostContext.HostingEnvironment.ContentRootPath, path)); } config.SetBasePath(path); var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); @@ -72,18 +81,6 @@ namespace ASC.Notify .ConfigureContainer((context, builder) => { builder.Register(context.Configuration); - }) - .UseConsoleLifetime() - .Build(); - - using (host) - { - // Start the host - await host.StartAsync(); - - // Wait for the host to shutdown - await host.WaitForShutdownAsync(); - } - } + }); } } diff --git a/common/services/ASC.Notify/Properties/launchSettings.json b/common/services/ASC.Notify/Properties/launchSettings.json index 0b8850d27e..ebab37a815 100644 --- a/common/services/ASC.Notify/Properties/launchSettings.json +++ b/common/services/ASC.Notify/Properties/launchSettings.json @@ -1,35 +1,29 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5005/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "notify", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products" - } - }, - "ASC.Notify": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "notify", "log__dir": "../../../Logs", - "core__products__folder": "../../../products" + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5005", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "notify", + "log__dir": "../../../Logs", + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5005", + "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:5005/" + "distributionName": "Ubuntu-20.04" } } } \ No newline at end of file diff --git a/common/services/ASC.Socket.IO.Svc/ASC.Socket.IO.Svc.csproj b/common/services/ASC.Socket.IO.Svc/ASC.Socket.IO.Svc.csproj index 8237da9e8e..85a8432814 100644 --- a/common/services/ASC.Socket.IO.Svc/ASC.Socket.IO.Svc.csproj +++ b/common/services/ASC.Socket.IO.Svc/ASC.Socket.IO.Svc.csproj @@ -16,6 +16,8 @@ + + diff --git a/common/services/ASC.Socket.IO.Svc/Program.cs b/common/services/ASC.Socket.IO.Svc/Program.cs index d1655a25d5..3275a7c1d1 100644 --- a/common/services/ASC.Socket.IO.Svc/Program.cs +++ b/common/services/ASC.Socket.IO.Svc/Program.cs @@ -32,7 +32,8 @@ using ASC.Common; using ASC.Common.Caching; using ASC.Common.DependencyInjection; using ASC.Common.Logging; - +using ASC.Common.Utils; + using Autofac; using Autofac.Extensions.DependencyInjection; @@ -45,9 +46,17 @@ namespace ASC.Socket.IO.Svc { public class Program { - public static async Task Main(string[] args) - { - var host = Host.CreateDefaultBuilder(args) + public async static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureAppConfiguration((hostContext, config) => { @@ -55,7 +64,7 @@ namespace ASC.Socket.IO.Svc var path = buided["pathToConf"]; if (!Path.IsPathRooted(path)) { - path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path)); + path = Path.GetFullPath(CrossPlatform.PathCombine(hostContext.HostingEnvironment.ContentRootPath, path)); } config.SetBasePath(path); var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); @@ -89,18 +98,7 @@ namespace ASC.Socket.IO.Svc .ConfigureContainer((context, builder) => { builder.Register(context.Configuration, false, false); - }) - .UseConsoleLifetime() - .Build(); - - using (host) - { - // Start the host - await host.StartAsync(); - - // Wait for the host to shutdown - await host.WaitForShutdownAsync(); - } - } - } -} + }); + } +} + diff --git a/common/services/ASC.Socket.IO.Svc/Properties/launchSettings.json b/common/services/ASC.Socket.IO.Svc/Properties/launchSettings.json index 5ada5a81b5..2d1f100d60 100644 --- a/common/services/ASC.Socket.IO.Svc/Properties/launchSettings.json +++ b/common/services/ASC.Socket.IO.Svc/Properties/launchSettings.json @@ -1,35 +1,29 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5017/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "socket", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products" - } - }, - "ASC.Socket.IO.Svc": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "socket", "log__dir": "../../../Logs", - "core__products__folder": "../../../products" + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5001", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "socket", + "log__dir": "../../../Logs", + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5001", + "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" + "distributionName": "Ubuntu-20.04" } } } \ No newline at end of file diff --git a/common/services/ASC.Socket.IO.Svc/SocketServiceLauncher.cs b/common/services/ASC.Socket.IO.Svc/SocketServiceLauncher.cs index 60bfecbc35..0a58945bf4 100644 --- a/common/services/ASC.Socket.IO.Svc/SocketServiceLauncher.cs +++ b/common/services/ASC.Socket.IO.Svc/SocketServiceLauncher.cs @@ -87,7 +87,7 @@ namespace ASC.Socket.IO.Svc UseShellExecute = false, FileName = "node", WindowStyle = ProcessWindowStyle.Hidden, - Arguments = string.Format("\"{0}\"", Path.GetFullPath(Path.Combine(HostEnvironment.ContentRootPath, settings.Path, "app.js"))), + Arguments = string.Format("\"{0}\"", Path.GetFullPath(CrossPlatform.PathCombine(HostEnvironment.ContentRootPath, settings.Path, "app.js"))), WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory }; StartInfo.EnvironmentVariables.Add("core.machinekey", Configuration["core:machinekey"]); @@ -105,7 +105,7 @@ namespace ASC.Socket.IO.Svc } LogDir = Logger.LogDirectory; - StartInfo.EnvironmentVariables.Add("logPath", Path.Combine(LogDir, "web.socketio.log")); + StartInfo.EnvironmentVariables.Add("logPath", CrossPlatform.PathCombine(LogDir, "web.socketio.log")); StartNode(); } catch (Exception e) diff --git a/common/services/ASC.Studio.Notify/ASC.Studio.Notify.csproj b/common/services/ASC.Studio.Notify/ASC.Studio.Notify.csproj index 145215fa33..3cbedf75cd 100644 --- a/common/services/ASC.Studio.Notify/ASC.Studio.Notify.csproj +++ b/common/services/ASC.Studio.Notify/ASC.Studio.Notify.csproj @@ -10,6 +10,11 @@ false + + + + + diff --git a/common/services/ASC.Studio.Notify/Program.cs b/common/services/ASC.Studio.Notify/Program.cs index ae93963abe..803ef994fa 100644 --- a/common/services/ASC.Studio.Notify/Program.cs +++ b/common/services/ASC.Studio.Notify/Program.cs @@ -6,6 +6,7 @@ using ASC.Common; using ASC.Common.Caching; using ASC.Common.DependencyInjection; using ASC.Common.Logging; +using ASC.Common.Utils; using ASC.Core.Notify; using ASC.Notify; using ASC.Web.Studio.Core.Notify; @@ -21,9 +22,17 @@ namespace ASC.Studio.Notify { public class Program { - public static async Task Main(string[] args) + public async static Task Main(string[] args) { - var host = Host.CreateDefaultBuilder(args) + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureAppConfiguration((hostContext, config) => { @@ -31,7 +40,7 @@ namespace ASC.Studio.Notify var path = buided["pathToConf"]; if (!Path.IsPathRooted(path)) { - path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path)); + path = Path.GetFullPath(CrossPlatform.PathCombine(hostContext.HostingEnvironment.ContentRootPath, path)); } config.SetBasePath(path); var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); @@ -66,18 +75,6 @@ namespace ASC.Studio.Notify .ConfigureContainer((context, builder) => { builder.Register(context.Configuration); - }) - .UseConsoleLifetime() - .Build(); - - using (host) - { - // Start the host - await host.StartAsync(); - - // Wait for the host to shutdown - await host.WaitForShutdownAsync(); - } - } + }); } } diff --git a/common/services/ASC.Studio.Notify/Properties/launchSettings.json b/common/services/ASC.Studio.Notify/Properties/launchSettings.json index 9333675630..65c6cbc262 100644 --- a/common/services/ASC.Studio.Notify/Properties/launchSettings.json +++ b/common/services/ASC.Studio.Notify/Properties/launchSettings.json @@ -1,33 +1,27 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5006/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "studio.notify", - "log__dir": "../../../Logs" - } - }, - "ASC.Studio.Notify": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "studio.notify", - "log__dir": "../../../Logs" + "log__dir": "../../../Logs", + "ASPNETCORE_URLS": "http://localhost:5006", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "studio.notify", + "log__dir": "../../../Logs", + "ASPNETCORE_URLS": "http://localhost:5006", + "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:5006/" + "distributionName": "Ubuntu-20.04" } } } \ No newline at end of file diff --git a/common/services/ASC.TelegramService/ASC.TelegramService.csproj b/common/services/ASC.TelegramService/ASC.TelegramService.csproj index de373e69a2..aaced03570 100644 --- a/common/services/ASC.TelegramService/ASC.TelegramService.csproj +++ b/common/services/ASC.TelegramService/ASC.TelegramService.csproj @@ -13,6 +13,8 @@ + + diff --git a/common/services/ASC.TelegramService/Program.cs b/common/services/ASC.TelegramService/Program.cs index cfb8535526..237602576e 100644 --- a/common/services/ASC.TelegramService/Program.cs +++ b/common/services/ASC.TelegramService/Program.cs @@ -22,10 +22,14 @@ * Pursuant to Section 7 3(e) we decline to grant you any rights under trademark law for use of our trademarks. * */ +using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Threading.Tasks; +using ASC.Common.Utils; + using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; @@ -36,13 +40,40 @@ namespace ASC.TelegramService { public class Program { - public static async Task Main(string[] args) - { - await Host.CreateDefaultBuilder(args) + public async static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); + var builder = webBuilder.UseStartup(); + + builder.ConfigureKestrel((hostingContext, serverOptions) => + { + var kestrelConfig = hostingContext.Configuration.GetSection("Kestrel"); + + if (!kestrelConfig.Exists()) return; + + var unixSocket = kestrelConfig.GetValue("ListenUnixSocket"); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + if (!String.IsNullOrWhiteSpace(unixSocket)) + { + unixSocket = String.Format(unixSocket, hostingContext.HostingEnvironment.ApplicationName.Replace("ASC.", "").Replace(".", "")); + + serverOptions.ListenUnixSocket(unixSocket); + } + } + }); }) .ConfigureAppConfiguration((hostContext, config) => { @@ -50,7 +81,7 @@ namespace ASC.TelegramService var path = buided["pathToConf"]; if (!Path.IsPathRooted(path)) { - path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path)); + path = Path.GetFullPath(CrossPlatform.PathCombine(hostContext.HostingEnvironment.ContentRootPath, path)); } config.SetBasePath(path); var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); @@ -67,10 +98,6 @@ namespace ASC.TelegramService .AddJsonFile("kafka.json") .AddJsonFile($"kafka.{env}.json", true) .AddEnvironmentVariables(); - }) - .UseConsoleLifetime() - .Build() - .RunAsync(); - } + }); } } diff --git a/common/services/ASC.TelegramService/Properties/launchSettings.json b/common/services/ASC.TelegramService/Properties/launchSettings.json index e94c3126bf..d3a7daf2ff 100644 --- a/common/services/ASC.TelegramService/Properties/launchSettings.json +++ b/common/services/ASC.TelegramService/Properties/launchSettings.json @@ -1,35 +1,29 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51702/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "telegram", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products" - } - }, - "ASC.TelegramService": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "telegram", "log__dir": "../../../Logs", - "core__products__folder": "../../../products" + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:51702", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "telegram", + "log__dir": "../../../Logs", + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:51702", + "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:51702/" + "distributionName": "Ubuntu-20.04" } } -} +} \ No newline at end of file diff --git a/common/services/ASC.Thumbnails.Svc/ASC.Thumbnails.Svc.csproj b/common/services/ASC.Thumbnails.Svc/ASC.Thumbnails.Svc.csproj index 6a7d3d8ae2..7f0181cd2b 100644 --- a/common/services/ASC.Thumbnails.Svc/ASC.Thumbnails.Svc.csproj +++ b/common/services/ASC.Thumbnails.Svc/ASC.Thumbnails.Svc.csproj @@ -21,5 +21,10 @@ + + + + + \ No newline at end of file diff --git a/common/services/ASC.Thumbnails.Svc/Program.cs b/common/services/ASC.Thumbnails.Svc/Program.cs index 8c5dd47656..9d5d897dc7 100644 --- a/common/services/ASC.Thumbnails.Svc/Program.cs +++ b/common/services/ASC.Thumbnails.Svc/Program.cs @@ -26,13 +26,15 @@ using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Threading.Tasks; using ASC.Common; using ASC.Common.Caching; using ASC.Common.DependencyInjection; using ASC.Common.Logging; - +using ASC.Common.Utils; + using Autofac; using Autofac.Extensions.DependencyInjection; @@ -44,18 +46,26 @@ using Microsoft.Extensions.Hosting; namespace ASC.Thumbnails.Svc { public class Program - { - public static async Task Main(string[] args) - { - var host = Host.CreateDefaultBuilder(args) - .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + { + public async static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureAppConfiguration((hostContext, config) => { var buided = config.Build(); var path = buided["pathToConf"]; if (!Path.IsPathRooted(path)) { - path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path)); + path = Path.GetFullPath(CrossPlatform.PathCombine(hostContext.HostingEnvironment.ContentRootPath, path)); } config.SetBasePath(path); var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); @@ -88,18 +98,6 @@ namespace ASC.Thumbnails.Svc .ConfigureContainer((context, builder) => { builder.Register(context.Configuration, false, false); - }) - .UseConsoleLifetime() - .Build(); - - using (host) - { - // Start the host - await host.StartAsync(); - - // Wait for the host to shutdown - await host.WaitForShutdownAsync(); - } - } + }); } } diff --git a/common/services/ASC.Thumbnails.Svc/Properties/launchSettings.json b/common/services/ASC.Thumbnails.Svc/Properties/launchSettings.json index faf382bc8c..7b1dd828b7 100644 --- a/common/services/ASC.Thumbnails.Svc/Properties/launchSettings.json +++ b/common/services/ASC.Thumbnails.Svc/Properties/launchSettings.json @@ -1,35 +1,29 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5016/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "thumbnails", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products" - } - }, - "ASC.Thumbnails.Svc": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "thumbnails", "log__dir": "../../../Logs", - "core__products__folder": "../../../products" + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5016", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "thumbnails", + "log__dir": "../../../Logs", + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5016", + "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:5016/" + "distributionName": "Ubuntu-20.04" } } } \ No newline at end of file diff --git a/common/services/ASC.Thumbnails.Svc/ThumbnailsServiceLauncher.cs b/common/services/ASC.Thumbnails.Svc/ThumbnailsServiceLauncher.cs index ad004c2b79..6e99d2d930 100644 --- a/common/services/ASC.Thumbnails.Svc/ThumbnailsServiceLauncher.cs +++ b/common/services/ASC.Thumbnails.Svc/ThumbnailsServiceLauncher.cs @@ -67,7 +67,7 @@ namespace ASC.Thumbnails.Svc UseShellExecute = false, FileName = "node", WindowStyle = ProcessWindowStyle.Hidden, - Arguments = string.Format("\"{0}\"", Path.GetFullPath(Path.Combine(HostEnvironment.ContentRootPath, settings.Path, "index.js"))), + Arguments = string.Format("\"{0}\"", Path.GetFullPath(CrossPlatform.PathCombine(HostEnvironment.ContentRootPath, settings.Path, "index.js"))), WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory }; @@ -77,7 +77,7 @@ namespace ASC.Thumbnails.Svc savePath += "/"; } StartInfo.EnvironmentVariables.Add("port", settings.Port); - StartInfo.EnvironmentVariables.Add("logPath", Path.Combine(Logger.LogDirectory, "web.thumbnails.log")); + StartInfo.EnvironmentVariables.Add("logPath", CrossPlatform.PathCombine(Logger.LogDirectory, "web.thumbnails.log")); StartInfo.EnvironmentVariables.Add("savePath", Path.GetFullPath(savePath)); StartNode(cancellationToken); diff --git a/common/services/ASC.UrlShortener.Svc/ASC.UrlShortener.Svc.csproj b/common/services/ASC.UrlShortener.Svc/ASC.UrlShortener.Svc.csproj index cc1faf4b6a..4add2d7682 100644 --- a/common/services/ASC.UrlShortener.Svc/ASC.UrlShortener.Svc.csproj +++ b/common/services/ASC.UrlShortener.Svc/ASC.UrlShortener.Svc.csproj @@ -13,6 +13,8 @@ + + diff --git a/common/services/ASC.UrlShortener.Svc/Program.cs b/common/services/ASC.UrlShortener.Svc/Program.cs index faed7d2cb4..46384b30f0 100644 --- a/common/services/ASC.UrlShortener.Svc/Program.cs +++ b/common/services/ASC.UrlShortener.Svc/Program.cs @@ -31,7 +31,8 @@ using System.Threading.Tasks; using ASC.Common; using ASC.Common.DependencyInjection; using ASC.Common.Logging; - +using ASC.Common.Utils; + using Autofac; using Autofac.Extensions.DependencyInjection; @@ -43,59 +44,60 @@ using Microsoft.Extensions.Hosting; namespace ASC.UrlShortener.Svc { public class Program - { - public static async Task Main(string[] args) - { - var host = Host.CreateDefaultBuilder(args) - .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .ConfigureAppConfiguration((hostContext, config) => - { - var buided = config.Build(); - var path = buided["pathToConf"]; - if (!Path.IsPathRooted(path)) - { - path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path)); - } - config.SetBasePath(path); - var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); - config - .AddJsonFile("appsettings.json") - .AddJsonFile($"appsettings.{env}.json", true) - .AddJsonFile($"urlshortener.{env}.json", true) - .AddJsonFile("storage.json") - .AddJsonFile("kafka.json") - .AddJsonFile($"kafka.{env}.json", true) - .AddEnvironmentVariables() - .AddCommandLine(args) - .AddInMemoryCollection(new Dictionary - { - {"pathToConf", path } - } - ); - }) - .ConfigureServices((hostContext, services) => - { - services.AddMemoryCache(); - var diHelper = new DIHelper(services); - LogNLogExtension.ConfigureLog(diHelper, "ASC.UrlShortener.Svc"); - services.AddHostedService(); - diHelper.TryAdd(); - }) - .ConfigureContainer((context, builder) => - { - builder.Register(context.Configuration, false, false); - }) - .UseConsoleLifetime() - .Build(); - - using (host) - { - // Start the host - await host.StartAsync(); - - // Wait for the host to shutdown - await host.WaitForShutdownAsync(); - } - } + { + public async static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureAppConfiguration((hostContext, config) => + { + var buided = config.Build(); + var path = buided["pathToConf"]; + + if (!Path.IsPathRooted(path)) + { + path = Path.GetFullPath(CrossPlatform.PathCombine(hostContext.HostingEnvironment.ContentRootPath, path)); + } + + config.SetBasePath(path); + + var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production"); + + config.AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{env}.json", true) + .AddJsonFile($"urlshortener.{env}.json", true) + .AddJsonFile("storage.json") + .AddJsonFile("kafka.json") + .AddJsonFile($"kafka.{env}.json", true) + .AddEnvironmentVariables() + .AddCommandLine(args) + .AddInMemoryCollection(new Dictionary + { + {"pathToConf", path } + } + ); + }) + .ConfigureServices((hostContext, services) => + { + services.AddMemoryCache(); + + var diHelper = new DIHelper(services); + + LogNLogExtension.ConfigureLog(diHelper, "ASC.UrlShortener.Svc"); + services.AddHostedService(); + diHelper.TryAdd(); + }) + .ConfigureContainer((context, builder) => + { + builder.Register(context.Configuration, false, false); + }); } } diff --git a/common/services/ASC.UrlShortener.Svc/Properties/launchSettings.json b/common/services/ASC.UrlShortener.Svc/Properties/launchSettings.json index a125f5af8d..6fd4fcb590 100644 --- a/common/services/ASC.UrlShortener.Svc/Properties/launchSettings.json +++ b/common/services/ASC.UrlShortener.Svc/Properties/launchSettings.json @@ -1,35 +1,29 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5015/", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "$STORAGE_ROOT": "../../../Data", - "log__name": "urlshortener", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products" - } - }, - "ASC.UrlShortener.Svc": { + "Kestrel WebServer": { "commandName": "Project", "launchBrowser": false, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", "$STORAGE_ROOT": "../../../Data", "log__name": "urlshortener", "log__dir": "../../../Logs", - "core__products__folder": "../../../products" + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5017", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__name": "urlshortener", + "log__dir": "../../../Logs", + "core__products__folder": "../../../products", + "ASPNETCORE_URLS": "http://localhost:5017", + "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:5015/" + "distributionName": "Ubuntu-20.04" } } } \ No newline at end of file diff --git a/common/services/ASC.UrlShortener.Svc/UrlShortenerService.cs b/common/services/ASC.UrlShortener.Svc/UrlShortenerService.cs index d86f31d3f7..da2fd08256 100644 --- a/common/services/ASC.UrlShortener.Svc/UrlShortenerService.cs +++ b/common/services/ASC.UrlShortener.Svc/UrlShortenerService.cs @@ -116,7 +116,7 @@ namespace ASC.UrlShortener.Svc UseShellExecute = false, FileName = "node", WindowStyle = ProcessWindowStyle.Hidden, - Arguments = string.Format("\"{0}\"", Path.GetFullPath(Path.Combine(hostEnvironment.ContentRootPath, path))), + Arguments = string.Format("\"{0}\"", Path.GetFullPath(CrossPlatform.PathCombine(hostEnvironment.ContentRootPath, path))), WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory }; @@ -146,7 +146,7 @@ namespace ASC.UrlShortener.Svc } } - startInfo.EnvironmentVariables.Add("logPath", Path.GetFullPath(Path.Combine(hostEnvironment.ContentRootPath, log.LogDirectory, "web.urlshortener.log"))); + startInfo.EnvironmentVariables.Add("logPath", Path.GetFullPath(CrossPlatform.PathCombine(hostEnvironment.ContentRootPath, log.LogDirectory, "web.urlshortener.log"))); return startInfo; } diff --git a/config/appsettings.json b/config/appsettings.json index 867844c665..ef1f609431 100644 --- a/config/appsettings.json +++ b/config/appsettings.json @@ -4,6 +4,12 @@ "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" + }, + "EventLog": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } } }, "AllowedHosts": "*", diff --git a/config/autofac.products.json b/config/autofac.products.json index 20f16dd4f1..a14fc90734 100644 --- a/config/autofac.products.json +++ b/config/autofac.products.json @@ -35,6 +35,24 @@ } ], "instanceScope": "InstancePerLifetimeScope" + }, + { + "type": "ASC.Mail.Configuration.ProductEntryPoint, ASC.Mail", + "services": [ + { + "type": "ASC.Web.Core.IWebItem, ASC.Web.Core" + } + ], + "instanceScope": "InstancePerLifetimeScope" + }, + { + "type": "ASC.Calendar.Configuration.ProductEntryPoint, ASC.Calendar", + "services": [ + { + "type": "ASC.Web.Core.IWebItem, ASC.Web.Core" + } + ], + "instanceScope": "InstancePerLifetimeScope" } ] } diff --git a/config/nginx/onlyoffice-calendar.conf b/config/nginx/onlyoffice-calendar.conf new file mode 100644 index 0000000000..f7b614a51f --- /dev/null +++ b/config/nginx/onlyoffice-calendar.conf @@ -0,0 +1,9 @@ +server { + listen 5017; + root /var/www/products/ASC.Calendar/client; + index index.html; + + location / { + try_files $uri /index.html =404; + } +} \ No newline at end of file diff --git a/config/nginx/onlyoffice-mail.conf b/config/nginx/onlyoffice-mail.conf new file mode 100644 index 0000000000..0514ca1fa2 --- /dev/null +++ b/config/nginx/onlyoffice-mail.conf @@ -0,0 +1,9 @@ +server { + listen 5016; + root /var/www/products/ASC.Mail/client; + index index.html; + + location / { + try_files $uri /index.html =404; + } +} \ No newline at end of file diff --git a/config/nginx/onlyoffice.conf b/config/nginx/onlyoffice.conf index 257e40182c..1cc2e3719b 100644 --- a/config/nginx/onlyoffice.conf +++ b/config/nginx/onlyoffice.conf @@ -90,6 +90,11 @@ server { root $public_root; try_files /offline/$basename /index.html =404; } + + location ~* /thirdparty/ { + root $public_root; + try_files /thirdparty/third-party.html /index.html =404; + } } location /login { @@ -150,6 +155,16 @@ server { proxy_pass http://localhost:5021; proxy_set_header X-REWRITER-URL $X_REWRITER_URL; } + + location ~* /mail { + proxy_pass http://localhost:5022; + proxy_set_header X-REWRITER-URL $X_REWRITER_URL; + } + + location ~* /calendar { + proxy_pass http://localhost:5023; + proxy_set_header X-REWRITER-URL $X_REWRITER_URL; + } } location /storage { @@ -292,6 +307,68 @@ server { try_files /$basename /index.html =404; } + location ~* (/httphandlers/filehandler.ashx|ChunkedUploader.ashx) { + proxy_pass http://localhost:5007; + proxy_set_header X-REWRITER-URL $X_REWRITER_URL; + } + } + + location ~* /mail { + #rewrite products/mail/(.*) /$1 break; + proxy_pass http://localhost:5016; + proxy_set_header X-REWRITER-URL $X_REWRITER_URL; + + location ~* /sockjs-node { + rewrite products/projects(.*)/sockjs-node/(.*) /$2/$3 break; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Host $host; + + proxy_pass http://localhost:5016; + + proxy_redirect off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location ~* /(manifest.json|service-worker.js|appIcon.png|bg-error.png) { + root $public_root; + try_files /$basename /index.html =404; + } + + location ~* (/httphandlers/filehandler.ashx|ChunkedUploader.ashx) { + proxy_pass http://localhost:5007; + proxy_set_header X-REWRITER-URL $X_REWRITER_URL; + } + } + + location ~* /calendar { + #rewrite products/calendar/(.*) /$1 break; + proxy_pass http://localhost:5017; + proxy_set_header X-REWRITER-URL $X_REWRITER_URL; + + location ~* /sockjs-node { + rewrite products/projects(.*)/sockjs-node/(.*) /$2/$3 break; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Host $host; + + proxy_pass http://localhost:5017; + + proxy_redirect off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location ~* /(manifest.json|service-worker.js|appIcon.png|bg-error.png) { + root $public_root; + try_files /$basename /index.html =404; + } + location ~* (/httphandlers/filehandler.ashx|ChunkedUploader.ashx) { proxy_pass http://localhost:5007; proxy_set_header X-REWRITER-URL $X_REWRITER_URL; diff --git a/frontend.code-workspace b/frontend.code-workspace index 6c5ebe9355..d1bb1ea531 100644 --- a/frontend.code-workspace +++ b/frontend.code-workspace @@ -28,6 +28,14 @@ "name": "🚀 @appserver/projects", "path": "products\\ASC.Projects\\Client" }, + { + "name": "🚀 @appserver/mail", + "path": "products\\ASC.Mail\\Client" + }, + { + "name": "🚀 @appserver/calendar", + "path": "products\\ASC.Calendar\\Client" + }, { "name": "🚀 @appserver/studio", "path": "web\\ASC.Web.Client" diff --git a/lerna.json b/lerna.json index 578c8b4902..09653b99f6 100644 --- a/lerna.json +++ b/lerna.json @@ -1,13 +1,18 @@ { - "version": "0.1.3", + "version": "0.1.8", "npmClient": "yarn", "packages": [ "packages/asc-web-components", "packages/asc-web-common", "web/ASC.Web.Login", "web/ASC.Web.Client", + "web/ASC.Web.Editor", "products/ASC.People/Client", - "products/ASC.Files/Client" + "products/ASC.Files/Client", + "products/ASC.CRM/Client", + "products/ASC.Projects/Client", + "products/ASC.Mail/Client", + "products/ASC.Calendar/Client" ], "useWorkspaces": true } diff --git a/package.json b/package.json index 045cb2815d..e7f03f254d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "products/ASC.People/Client", "products/ASC.Files/Client", "products/ASC.CRM/Client", - "products/ASC.Projects/Client" + "products/ASC.Projects/Client", + "products/ASC.Mail/Client", + "products/ASC.Calendar/Client" ], "scripts": { "wipe": "rimraf node_modules yarn.lock web/**/node_modules products/**/node_modules", diff --git a/packages/asc-web-common/api/files/filter.js b/packages/asc-web-common/api/files/filter.js index d8880d0573..0c606a2ed9 100644 --- a/packages/asc-web-common/api/files/filter.js +++ b/packages/asc-web-common/api/files/filter.js @@ -183,7 +183,7 @@ class FilesFilter { dtoFilter[FILTER_TYPE] = filterType; } - if (withSubfolders === "false") { + if (withSubfolders) { dtoFilter[SEARCH_TYPE] = withSubfolders; } diff --git a/packages/asc-web-common/api/files/index.js b/packages/asc-web-common/api/files/index.js index 20abdee925..8f65a23448 100644 --- a/packages/asc-web-common/api/files/index.js +++ b/packages/asc-web-common/api/files/index.js @@ -4,12 +4,22 @@ import FilesFilter from "./filter"; import { FolderType } from "../../constants"; import find from "lodash/find"; -export function openEdit(fileId, doc) { - const params = doc ? `?doc=${doc}` : ""; +export function openEdit(fileId, version, doc) { + const params = []; // doc ? `?doc=${doc}` : ""; + + if (version) { + params.push(`version=${version}`); + } + + if (doc) { + params.push(`doc=${doc}`); + } + + const paramsString = params.length > 0 ? `?${params.join("&")}` : ""; const options = { method: "get", - url: `/files/file/${fileId}/openedit${params}`, + url: `/files/file/${fileId}/openedit${paramsString}`, }; return request(options); @@ -453,6 +463,11 @@ export function getProgress() { return request({ method: "get", url: "/files/fileops" }); } +export function checkFileConflicts(destFolderId, folderIds, fileIds) { + const data = { destFolderId, folderIds, fileIds }; + return request({ method: "post", url: "/files/fileops/move", data }); +} + export function copyToFolder( destFolderId, folderIds, diff --git a/packages/asc-web-common/api/people/index.js b/packages/asc-web-common/api/people/index.js index 96322c3ce5..92c885c0e6 100644 --- a/packages/asc-web-common/api/people/index.js +++ b/packages/asc-web-common/api/people/index.js @@ -197,6 +197,29 @@ export function updateUserType(type, userIds) { }); } +export function linkOAuth(serializedProfile) { + return request({ + method: "put", + url: "people/thirdparty/linkaccount.json", + data: { serializedProfile }, + }); +} + +export function signupOAuth(signupAccount) { + return request({ + method: "post", + url: "people/thirdparty/signup.json", + data: signupAccount, + }); +} + +export function unlinkOAuth(provider) { + return request({ + method: "delete", + url: `people/thirdparty/unlinkaccount.json?provider=${provider}`, + }); +} + export function sendInstructionsToDelete() { return request({ method: "put", diff --git a/packages/asc-web-common/api/settings/index.js b/packages/asc-web-common/api/settings/index.js index 1c3a773fa3..ae37b5b5d3 100644 --- a/packages/asc-web-common/api/settings/index.js +++ b/packages/asc-web-common/api/settings/index.js @@ -79,6 +79,49 @@ export function getLogoUrls() { }); } +export function getCustomSchemaList() { + return request({ + method: "get", + url: `settings/customschemas`, + }); +} + +export function setCurrentSchema(id) { + return request({ + method: "post", + url: "settings/customschemas", + data: { id }, + }); +} +export function setCustomSchema( + userCaption, + usersCaption, + groupCaption, + groupsCaption, + userPostCaption, + regDateCaption, + groupHeadCaption, + guestCaption, + guestsCaption +) { + const data = { + userCaption, + usersCaption, + groupCaption, + groupsCaption, + userPostCaption, + regDateCaption, + groupHeadCaption, + guestCaption, + guestsCaption, + }; + return request({ + method: "put", + url: `settings/customschemas`, + data, + }); +} + export function getCurrentCustomSchema(id) { return request({ method: "get", @@ -190,6 +233,13 @@ export function getConsumersList() { }); } +export function getAuthProviders() { + return request({ + method: "get", + url: `/people/thirdparty/providers`, + }); +} + export function updateConsumerProps(newProps) { const options = { method: "post", diff --git a/packages/asc-web-common/api/user/index.js b/packages/asc-web-common/api/user/index.js index eecac7f9b9..90c42d086b 100644 --- a/packages/asc-web-common/api/user/index.js +++ b/packages/asc-web-common/api/user/index.js @@ -13,6 +13,14 @@ export function login(userName, passwordHash) { }); } +export function thirdPartyLogin(SerializedProfile) { + return request({ + method: "post", + url: "authentication.json", + data: { SerializedProfile }, + }); +} + export function logout() { return request({ method: "post", diff --git a/packages/asc-web-common/components/PageLayout/index.js b/packages/asc-web-common/components/PageLayout/index.js index 604485b018..4ba016019f 100644 --- a/packages/asc-web-common/components/PageLayout/index.js +++ b/packages/asc-web-common/components/PageLayout/index.js @@ -89,9 +89,13 @@ class PageLayout extends React.Component { componentDidUpdate(prevProps) { if ( - this.props.hideAside && - !this.state.isArticlePinned && - this.props.hideAside !== prevProps.hideAside + (this.props.hideAside && + !this.state.isArticlePinned && + this.props.hideAside !== prevProps.hideAside) || + (this.props.isLoading !== prevProps.isLoading && + this.props.isLoaded && + this.state.isArticleVisible && + !this.state.isArticlePinned) ) { this.backdropClick(); } diff --git a/packages/asc-web-common/components/PageLayout/sub-components/article.js b/packages/asc-web-common/components/PageLayout/sub-components/article.js index 9513e42ff1..7d6766c371 100644 --- a/packages/asc-web-common/components/PageLayout/sub-components/article.js +++ b/packages/asc-web-common/components/PageLayout/sub-components/article.js @@ -62,9 +62,9 @@ const StyledArticle = styled.article` ` : ` position: fixed !important; - width: 240px !important; - min-width: 240px; - max-width: 240px; + width: 260px !important; + min-width: 260px; + max-width: 260px; position: fixed; height: 100% !important; top: 0; @@ -73,6 +73,10 @@ const StyledArticle = styled.article` .resizable-border { display: none; } + + .newItem { + right: -24px; + } ` : ` display: none; diff --git a/packages/asc-web-common/constants/index.js b/packages/asc-web-common/constants/index.js index e8ff85d158..93ecabe500 100644 --- a/packages/asc-web-common/constants/index.js +++ b/packages/asc-web-common/constants/index.js @@ -103,6 +103,30 @@ export const ShareAccessRights = Object.freeze({ CustomFilter: 8, }); +export const ConflictResolveType = Object.freeze({ + Skip: 0, + Overwrite: 1, + Duplicate: 2, +}); +export const providersData = Object.freeze({ + Google: { + label: "SignInWithGoogle", + icon: "/static/images/share.google.react.svg", + }, + Facebook: { + label: "SignInWithFacebook", + icon: "/static/images/share.facebook.react.svg", + }, + Twitter: { + label: "SignInWithTwitter", + icon: "/static/images/share.twitter.react.svg", + iconOptions: { color: "#2AA3EF" }, + }, + LinkedIn: { + label: "SignInWithLinkedIn", + icon: "/static/images/share.linkedin.react.svg", + }, +}); export const i18nBaseSettings = { lng: localStorage.getItem(LANGUAGE) || "en", supportedLngs: ["en", "ru"], diff --git a/packages/asc-web-common/package.json b/packages/asc-web-common/package.json index 173afdb3b9..6814d51dff 100644 --- a/packages/asc-web-common/package.json +++ b/packages/asc-web-common/package.json @@ -1,6 +1,6 @@ { "name": "@appserver/common", - "version": "0.0.2", + "version": "0.0.3", "private": true, "scripts": { "build": "echo 'skip it'", diff --git a/packages/asc-web-common/store/AuthStore.js b/packages/asc-web-common/store/AuthStore.js index be104c3f08..56334a47bf 100644 --- a/packages/asc-web-common/store/AuthStore.js +++ b/packages/asc-web-common/store/AuthStore.js @@ -8,7 +8,7 @@ import UserStore from "./UserStore"; import { logout as logoutDesktop, desktopConstants } from "../desktop"; import { combineUrl, isAdmin } from "../utils"; import isEmpty from "lodash/isEmpty"; -import { AppServerConfig } from "../constants"; +import { AppServerConfig, LANGUAGE } from "../constants"; const { proxyURL } = AppServerConfig; class AuthStore { @@ -20,6 +20,8 @@ class AuthStore { isAuthenticated = false; version = null; + providers = []; + constructor() { this.userStore = new UserStore(); this.moduleStore = new ModuleStore(); @@ -41,7 +43,14 @@ class AuthStore { return Promise.all(requests); }; - + setLanguage() { + if (this.userStore.user.cultureName) { + localStorage.getItem(LANGUAGE) !== this.userStore.user.cultureName && + localStorage.setItem(LANGUAGE, this.userStore.user.cultureName); + } else { + localStorage.setItem(LANGUAGE, this.settingsStore.culture || "en-US"); + } + } get isLoaded() { let success = false; if (this.isAuthenticated) { @@ -49,6 +58,8 @@ class AuthStore { this.userStore.isLoaded && this.moduleStore.isLoaded && this.settingsStore.isLoaded; + + success && this.setLanguage(); } else { success = this.settingsStore.isLoaded; } @@ -154,6 +165,22 @@ class AuthStore { } }; + thirdPartyLogin = async (SerializedProfile) => { + try { + const response = await api.user.thirdPartyLogin(SerializedProfile); + + if (!response || !response.token) throw "Empty API response"; + + setWithCredentialsStatus(true); + + await this.init(); + + return Promise.resolve(true); + } catch (e) { + return Promise.reject(e); + } + }; + reset = () => { this.userStore = new UserStore(); this.moduleStore = new ModuleStore(); @@ -250,6 +277,10 @@ class AuthStore { setProductVersion = (version) => { this.version = version; }; + + setProviders = (providers) => { + this.providers = providers; + }; } export default new AuthStore(); diff --git a/packages/asc-web-common/store/SettingsStore.js b/packages/asc-web-common/store/SettingsStore.js index 35f669724a..7af2896457 100644 --- a/packages/asc-web-common/store/SettingsStore.js +++ b/packages/asc-web-common/store/SettingsStore.js @@ -34,6 +34,7 @@ class SettingsStore { organizationName = "ONLYOFFICE"; greetingSettings = "Web Office Applications"; enableAdmMess = false; + enabledJoin = false; urlLicense = "https://gnu.org/licenses/gpl-3.0.html"; urlSupport = "https://helpdesk.onlyoffice.com/"; logoUrl = combineUrl(proxyURL, "/static/images/nav.logo.opened.react.svg"); @@ -70,6 +71,8 @@ class SettingsStore { passwordSettings = null; hasShortenService = false; + customSchemaList = []; + constructor() { makeAutoObservable(this); } @@ -84,6 +87,13 @@ class SettingsStore { return this.isLoaded && !this.wizardToken; } + get helpUrlCommonSettings() { + const substring = this.culture.substring(0, this.culture.indexOf("-")); + const lang = substring.length > 0 ? substring : "en"; + + return `https://helpcenter.onlyoffice.com/${lang}/administration/configuration.aspx#CustomizingPortal_block`; + } + setValue = (key, value) => { this[key] = value; }; @@ -117,6 +127,10 @@ class SettingsStore { this.customNames = await api.settings.getCurrentCustomSchema(id); }; + getCustomSchemaList = async () => { + this.customSchemaList = await api.settings.getCustomSchemaList(); + }; + getPortalSettings = async () => { const origSettings = await this.getSettings(); @@ -169,6 +183,33 @@ class SettingsStore { this.updateEncryptionKeys(encryptionKeys); }; + getOAuthToken = (tokenGetterWin) => { + return new Promise((resolve, reject) => { + localStorage.removeItem("code"); + let interval = null; + interval = setInterval(() => { + try { + const code = localStorage.getItem("code"); + + if (code) { + localStorage.removeItem("code"); + clearInterval(interval); + resolve(code); + } else if (tokenGetterWin && tokenGetterWin.closed) { + clearInterval(interval); + reject(); + } + } catch { + return; + } + }, 500); + }); + }; + + getLoginLink = (token, code) => { + return combineUrl(proxyURL, `/login.ashx?p=${token}&code=${code}`); + }; + setModuleInfo = (homepage, productId) => { if (this.homepage == homepage) return; this.homepage = homepage; diff --git a/packages/asc-web-common/store/UserStore.js b/packages/asc-web-common/store/UserStore.js index 1749587346..0cfa3edc5f 100644 --- a/packages/asc-web-common/store/UserStore.js +++ b/packages/asc-web-common/store/UserStore.js @@ -1,6 +1,5 @@ import { action, makeObservable, observable } from "mobx"; import api from "../api"; -import { LANGUAGE } from "../constants"; class UserStore { user = null; @@ -21,9 +20,7 @@ class UserStore { getCurrentUser = async () => { const user = await api.people.getUser(); - user.cultureName && - localStorage.getItem(LANGUAGE) !== user.cultureName && - localStorage.setItem(LANGUAGE, user.cultureName); + this.setUser(user); }; diff --git a/packages/asc-web-common/utils/index.js b/packages/asc-web-common/utils/index.js index 21fb1aa1d7..72c5265702 100644 --- a/packages/asc-web-common/utils/index.js +++ b/packages/asc-web-common/utils/index.js @@ -6,11 +6,7 @@ import TopLoaderService from "@appserver/components/top-loading-indicator"; export const toUrlParams = (obj, skipNull) => { let str = ""; for (var key in obj) { - if ( - (skipNull && !obj[key] && key !== "withSubfolders") || - (key === "withSubfolders" && obj[key] !== "false") - ) - continue; + if (skipNull && !obj[key]) continue; if (str !== "") { str += "&"; @@ -210,3 +206,10 @@ export function deleteCookie(name) { "max-age": -1, }); } + +export function clickBackdrop() { + var elms = document.getElementsByClassName("backdrop-active"); + if (elms && elms.length > 0) { + elms[0].click(); + } +} diff --git a/packages/asc-web-components/badge/index.js b/packages/asc-web-components/badge/index.js index e5bc04118b..4c9d0162fe 100644 --- a/packages/asc-web-components/badge/index.js +++ b/packages/asc-web-components/badge/index.js @@ -11,7 +11,6 @@ const Badge = (props) => { if (!props.onClick) return; e.preventDefault(); - e.stopPropagation(); props.onClick(e); }; diff --git a/packages/asc-web-components/checkbox/styled-checkbox.js b/packages/asc-web-components/checkbox/styled-checkbox.js index 1829d70bb4..7214d065e5 100644 --- a/packages/asc-web-components/checkbox/styled-checkbox.js +++ b/packages/asc-web-components/checkbox/styled-checkbox.js @@ -15,6 +15,7 @@ const StyledLabel = styled.label` .checkbox { margin-right: 12px; + overflow: visible; } /* ${(props) => diff --git a/packages/asc-web-components/context-menu-button/index.js b/packages/asc-web-components/context-menu-button/index.js index 25814966e3..5c4c585b89 100644 --- a/packages/asc-web-components/context-menu-button/index.js +++ b/packages/asc-web-components/context-menu-button/index.js @@ -80,8 +80,8 @@ class ContextMenuButton extends React.Component { } } - onIconButtonClick = () => { - if (this.props.isDisabled) { + onIconButtonClick = (e) => { + if (this.props.isDisabled || this.props.isNew) { this.stopAction; return; } @@ -95,7 +95,7 @@ class ContextMenuButton extends React.Component { !this.props.isDisabled && this.state.isOpen && this.props.onClick && - this.props.onClick() + this.props.onClick(e) ); // eslint-disable-line react/prop-types }; @@ -124,6 +124,20 @@ class ContextMenuButton extends React.Component { return true; } + callNewMenu = (e) => { + if (this.props.isDisabled || !this.props.isNew) { + this.stopAction; + return; + } + + this.setState( + { + data: this.props.getData(), + }, + () => this.props.onClick(e) + ); + }; + render() { //console.log("ContextMenuButton render", this.props); const { @@ -148,12 +162,19 @@ class ContextMenuButton extends React.Component { style, isFill, // eslint-disable-line react/prop-types asideHeader, // eslint-disable-line react/prop-types + isNew, } = this.props; const { isOpen, displayType, offsetX, offsetY } = this.state; const iconButtonName = isOpen && iconOpenName ? iconOpenName : iconName; return ( - + ); const submenu = this.renderSubmenu(item); + const dataKeys = Object.fromEntries( + Object.entries(item).filter((el) => el[0].indexOf("data-") === 0) + ); let content = ( this.onItemClick(event, item, index)} role="menuitem" > diff --git a/packages/asc-web-components/facebook-button/index.js b/packages/asc-web-components/facebook-button/index.js new file mode 100644 index 0000000000..f1224c33fa --- /dev/null +++ b/packages/asc-web-components/facebook-button/index.js @@ -0,0 +1,48 @@ +import React from "react"; +import PropTypes from "prop-types"; +import equal from "fast-deep-equal/react"; + +import Text from "../text"; +import StyledFacebookButton from "./styled-facebook-button"; +import { ReactSVG } from "react-svg"; +// eslint-disable-next-line no-unused-vars + +class FacebookButton extends React.Component { + shouldComponentUpdate(nextProps) { + return !equal(this.props, nextProps); + } + + render() { + const { label, iconName, ...otherProps } = this.props; + return ( + + + {label && ( + + {label} + + )} + + ); + } +} + +FacebookButton.propTypes = { + label: PropTypes.string, + iconName: PropTypes.string, + tabIndex: PropTypes.number, + isDisabled: PropTypes.bool, + className: PropTypes.string, + id: PropTypes.string, + style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), + onClick: PropTypes.func, + $iconOptions: PropTypes.object, +}; + +FacebookButton.defaultProps = { + tabIndex: -1, + isDisabled: false, + $iconOptions: {}, +}; + +export default FacebookButton; diff --git a/packages/asc-web-components/facebook-button/styled-facebook-button.js b/packages/asc-web-components/facebook-button/styled-facebook-button.js new file mode 100644 index 0000000000..9106af2f60 --- /dev/null +++ b/packages/asc-web-components/facebook-button/styled-facebook-button.js @@ -0,0 +1,77 @@ +import React from "react"; +import styled, { css } from "styled-components"; +import Base from "../themes/base"; +import PropTypes from "prop-types"; + +const ButtonWrapper = ({ label, iconName, isDisabled, noHover, ...props }) => ( + +); + +ButtonWrapper.propTypes = { + label: PropTypes.string, + iconName: PropTypes.string, + tabIndex: PropTypes.number, + isDisabled: PropTypes.bool, + onClick: PropTypes.func, + $iconOptions: PropTypes.object, +}; + +const StyledFacebookButton = styled(ButtonWrapper).attrs((props) => ({ + disabled: props.isDisabled ? "disabled" : "", + tabIndex: props.tabIndex, +}))` + border: none; + display: flex; + align-items: center; + background-color: #ffffff; + border: 1px solid #1877f2; + border-radius: 3px; + width: 100%; + ${(props) => !props.noHover && "cursor: pointer;"} + padding: 0; + outline: none; + + touch-callout: none; + -o-touch-callout: none; + -moz-touch-callout: none; + -webkit-touch-callout: none; + + svg { + margin: 11px; + width: 18px; + height: 18px; + min-width: 18px; + min-height: 18px; + } + + ${(props) => + props.$iconOptions && + props.$iconOptions.color && + css` + svg { + path { + fill: ${props.$iconOptions.color}; + } + } + `} + + .iconWrapper { + display: flex; + pointer-events: none; + } + + .social_button_text { + pointer-events: none; + font-family: Roboto, "Open Sans", sans-serif, Arial; + font-style: normal; + font-weight: 600; + font-size: 14px; + line-height: 14px; + color: #1877f2; + margin: 0 11px; + } +`; + +StyledFacebookButton.defaultProps = { theme: Base }; + +export default StyledFacebookButton; diff --git a/packages/asc-web-components/field-container/styled-field-container.js b/packages/asc-web-components/field-container/styled-field-container.js index 453fb63f43..dff73278c5 100644 --- a/packages/asc-web-components/field-container/styled-field-container.js +++ b/packages/asc-web-components/field-container/styled-field-container.js @@ -20,7 +20,8 @@ function getHorizontalCss(labelWidth) { width: ${labelWidth}; } .field-body { - flex-grow: ${(props) => props.theme.fieldContainer.horizontal.body.width}; + flex-grow: ${(props) => + props.theme.fieldContainer.horizontal.body.flexGrow}; } .icon-button { position: relative; diff --git a/packages/asc-web-components/group-button/index.js b/packages/asc-web-components/group-button/index.js index 1556785947..0ab679cee2 100644 --- a/packages/asc-web-components/group-button/index.js +++ b/packages/asc-web-components/group-button/index.js @@ -122,12 +122,14 @@ class GroupButton extends React.Component { isChecked={checked} isIndeterminate={isIndeterminate} onChange={this.checkboxChange} + title={itemLabel} /> )} {itemLabel} @@ -141,6 +143,7 @@ class GroupButton extends React.Component { open={this.state.isOpen} clickOutsideAction={this.clickOutsideAction} showDisabledItems={true} + title={this.state.isOpen ? "" : itemLabel} > {React.Children.map(children, (child) => ( ) : ( - {label} + + {label} + )} - {isSeparator && } + {isSeparator && } ); } diff --git a/packages/asc-web-components/package.json b/packages/asc-web-components/package.json index 7c62ba355d..b525a5a009 100644 --- a/packages/asc-web-components/package.json +++ b/packages/asc-web-components/package.json @@ -1,6 +1,6 @@ { "name": "@appserver/components", - "version": "0.0.2", + "version": "0.0.3", "private": true, "scripts": { "build": "echo 'skip it'", diff --git a/packages/asc-web-components/radio-button-group/index.js b/packages/asc-web-components/radio-button-group/index.js index 922ab8c91c..be911c5216 100644 --- a/packages/asc-web-components/radio-button-group/index.js +++ b/packages/asc-web-components/radio-button-group/index.js @@ -70,7 +70,7 @@ RadioButtonGroup.propTypes = { options: PropTypes.arrayOf( PropTypes.shape({ value: PropTypes.string.isRequired, - label: PropTypes.string, + label: PropTypes.oneOfType([PropTypes.any, PropTypes.string]), disabled: PropTypes.bool, }) ).isRequired, diff --git a/packages/asc-web-components/radio-button/index.js b/packages/asc-web-components/radio-button/index.js index c77ec74456..851b01ff23 100644 --- a/packages/asc-web-components/radio-button/index.js +++ b/packages/asc-web-components/radio-button/index.js @@ -83,7 +83,7 @@ RadioButton.propTypes = { /** Used as HTML `disabled` property for each `` tag */ isDisabled: PropTypes.bool, /** Name of the radiobutton. If missed, `value` will be used */ - label: PropTypes.string, + label: PropTypes.oneOfType([PropTypes.any, PropTypes.string]), /** Font size of link */ fontSize: PropTypes.string, /** Font weight of link */ diff --git a/packages/asc-web-components/row/index.js b/packages/asc-web-components/row/index.js index e10bfe4b59..8628092e9b 100644 --- a/packages/asc-web-components/row/index.js +++ b/packages/asc-web-components/row/index.js @@ -100,6 +100,8 @@ class Row extends React.Component { className="expandButton" getData={getOptions} directionX="right" + isNew={true} + onClick={onContextMenu} /> ) : (
diff --git a/packages/asc-web-components/save-cancel-buttons/index.js b/packages/asc-web-components/save-cancel-buttons/index.js index a527605bea..9cba20b564 100644 --- a/packages/asc-web-components/save-cancel-buttons/index.js +++ b/packages/asc-web-components/save-cancel-buttons/index.js @@ -57,6 +57,7 @@ class SaveCancelButtons extends React.Component { label={saveButtonLabel} /> ); @@ -12,6 +13,7 @@ ButtonWrapper.propTypes = { tabIndex: PropTypes.number, isDisabled: PropTypes.bool, onClick: PropTypes.func, + $iconOptions: PropTypes.object, }; const StyledSocialButton = styled(ButtonWrapper).attrs((props) => ({ @@ -20,7 +22,8 @@ const StyledSocialButton = styled(ButtonWrapper).attrs((props) => ({ }))` font-family: ${(props) => props.theme.fontFamily}; border: none; - display: inline-block; + display: flex; + align-items: center; font-weight: ${(props) => props.theme.socialButton.fontWeight}; text-decoration: ${(props) => props.theme.socialButton.textDecoration}; @@ -42,6 +45,15 @@ const StyledSocialButton = styled(ButtonWrapper).attrs((props) => ({ outline: ${(props) => props.theme.socialButton.outline}; } + ${(props) => + props.$iconOptions && + props.$iconOptions.color && + css` + svg path { + fill: ${props.$iconOptions.color}; + } + `} + ${(props) => !props.isDisabled ? css` @@ -49,20 +61,27 @@ const StyledSocialButton = styled(ButtonWrapper).attrs((props) => ({ box-shadow: ${(props) => props.theme.socialButton.boxShadow}; color: ${(props) => props.theme.socialButton.color}; - :hover, - :active { - cursor: pointer; - box-shadow: ${(props) => props.theme.socialButton.hoverBoxShadow}; - } + ${(props) => + !props.noHover && + css` + :hover, + :active { + cursor: pointer; + box-shadow: ${(props) => + props.theme.socialButton.hoverBoxShadow}; + } - :hover { - background: ${(props) => props.theme.socialButton.hoverBackground}; - } + :hover { + background: ${(props) => + props.theme.socialButton.hoverBackground}; + } - :active { - background: ${(props) => props.theme.socialButton.activeBackground}; - border: none; - } + :active { + background: ${(props) => + props.theme.socialButton.activeBackground}; + border: none; + } + `} ` : css` box-shadow: none; @@ -75,8 +94,14 @@ const StyledSocialButton = styled(ButtonWrapper).attrs((props) => ({ } `}; + .iconWrapper { + display: flex; + pointer-events: none; + } + .social_button_text { - position: absolute; + position: relative; + pointer-events: none; width: ${(props) => props.theme.socialButton.text.width}; height: ${(props) => props.theme.socialButton.text.height}; diff --git a/packages/asc-web-components/themes/base.js b/packages/asc-web-components/themes/base.js index 94348077a8..b4615efbba 100644 --- a/packages/asc-web-components/themes/base.js +++ b/packages/asc-web-components/themes/base.js @@ -213,14 +213,13 @@ const Base = { socialButton: { fontWeight: "600", textDecoration: "none", - margin: "20px 0 0 20px", padding: "0", borderRadius: "2px", - width: "201px", height: "40px", textAlign: "left", stroke: " none", outline: "none", + width: "100%", background: white, disableBackgroundColor: "rgba(0, 0, 0, 0.08)", @@ -236,16 +235,17 @@ const Base = { disableColor: "rgba(0, 0, 0, 0.4)", text: { - width: "142px", + width: "100%", height: "16px", - margin: "12px 9px 12px 10px", - fontWeight: "500", + margin: "0 11px", + fontWeight: "600", fontSize: "14px", - lineHeight: "16px", + lineHeight: "14px", letterSpacing: "0.21875px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", + color: "#757575", }, svg: { diff --git a/packages/asc-web-components/tree-menu/sub-components/tree-node.js b/packages/asc-web-components/tree-menu/sub-components/tree-node.js index e295e8c9a2..f0a476abd1 100644 --- a/packages/asc-web-components/tree-menu/sub-components/tree-node.js +++ b/packages/asc-web-components/tree-menu/sub-components/tree-node.js @@ -322,11 +322,7 @@ const TreeNodeMenu = styled(TreeNode)` span.rc-tree-title:first-child { max-width: 100%; } - .newItem { - position: absolute; - right: 0px; - top: 2px; - } + .rc-tree-node-selected { background: ${(props) => props.theme.treeNode.selected.background}; mix-blend-mode: normal; diff --git a/products/ASC.CRM/Client/package.json b/products/ASC.CRM/Client/package.json index a23f8238d4..ba69f5fce3 100644 --- a/products/ASC.CRM/Client/package.json +++ b/products/ASC.CRM/Client/package.json @@ -1,6 +1,6 @@ { "name": "@appserver/crm", - "version": "0.1.3", + "version": "0.1.8", "private": "true", "homepage": "/products/crm", "title": "ONLYOFFICE", diff --git a/products/ASC.CRM/Client/public/locales/en/ComingSoon.json b/products/ASC.CRM/Client/public/locales/en/ComingSoon.json index 3e2ac83d7e..e4034ce0ac 100644 --- a/products/ASC.CRM/Client/public/locales/en/ComingSoon.json +++ b/products/ASC.CRM/Client/public/locales/en/ComingSoon.json @@ -2,5 +2,6 @@ "ComingSoon": "Coming soon", "ViewWeb": "View web version", "OpenApp": "Open your {{title}} app", - "LearnMore": "Learn more" + "LearnMore": "Learn more", + "ModuleDescription": "Manage your contacts and sales" } diff --git a/products/ASC.CRM/Client/public/locales/ru/ComingSoon.json b/products/ASC.CRM/Client/public/locales/ru/ComingSoon.json index e339d8564d..cbcc442f67 100644 --- a/products/ASC.CRM/Client/public/locales/ru/ComingSoon.json +++ b/products/ASC.CRM/Client/public/locales/ru/ComingSoon.json @@ -2,5 +2,6 @@ "ComingSoon": "Скоро появится", "ViewWeb": "Просмотреть веб-версию", "OpenApp": "Откройте {{title}}", - "LearnMore": "Узнать больше" + "LearnMore": "Узнать больше", + "ModuleDescription": "Управляйте своими контактами и продажами" } diff --git a/products/ASC.CRM/Client/src/i18n.js b/products/ASC.CRM/Client/src/i18n.js index ae1eed771a..7b2ade83d5 100644 --- a/products/ASC.CRM/Client/src/i18n.js +++ b/products/ASC.CRM/Client/src/i18n.js @@ -9,8 +9,6 @@ import { LANGUAGE } from "@appserver/common/constants"; // have a look at the Quick start guide // for passing in lng and translations on init -const languages = ["en", "ru"]; - i18n /* load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales) @@ -32,8 +30,6 @@ i18n */ .init({ lng: localStorage.getItem(LANGUAGE) || "en", - supportedLngs: languages, - whitelist: languages, fallbackLng: "en", load: "languageOnly", //debug: true, diff --git a/products/ASC.CRM/Client/src/pages/Home/index.js b/products/ASC.CRM/Client/src/pages/Home/index.js index 67e1c82031..111f615fc7 100644 --- a/products/ASC.CRM/Client/src/pages/Home/index.js +++ b/products/ASC.CRM/Client/src/pages/Home/index.js @@ -11,9 +11,9 @@ import ExternalLinkIcon from "../../../../../../public/images/external.link.reac import Loaders from "@appserver/common/components/Loaders"; import toastr from "studio/toastr"; import PageLayout from "@appserver/common/components/PageLayout"; -import { useTranslation } from "react-i18next"; +import { withTranslation } from "react-i18next"; import styled from "styled-components"; -import { isMobile, isIOS } from "react-device-detect"; +import { isMobile, isTablet, isIOS } from "react-device-detect"; import { setDocumentTitle } from "../../helpers/utils"; import { inject } from "mobx-react"; @@ -34,14 +34,19 @@ const commonStyles = ` `; const ComingSoonPage = styled.div` - padding: ${isMobile ? "62px 0 0 0" : "0"}; - width: 336px; + padding: ${isTablet ? "106px 0 0 0" : isMobile ? "62px 0 0 0" : "0"}; + width: ${isTablet ? "500px" : "336px"}; margin: 0 auto; .module-logo-icon { float: left; margin-top: 8px; margin-right: 16px; + + svg { + width: ${isTablet ? "192px" : "96px"}; + height: ${isTablet ? "192px" : "96px"}; + } } .module-title { @@ -93,7 +98,10 @@ const StyledDesktopContainer = styled(EmptyScreenContainer)` const ExternalLink = ({ label, href, onClick }) => ( - + ( ); -const Body = ({ modules, match, isLoaded, setCurrentProductId }) => { - const { t } = useTranslation("ComingSoon"); +const Body = ({ modules, match, isLoaded, setCurrentProductId, t, tReady }) => { const { error } = match.params; const { pathname, protocol, hostname } = window.location; const currentModule = modules.find((m) => m.link === pathname); - const { - id, - title, - description, - imageUrl, - link, - originUrl, - helpUrl, - } = currentModule; + const { id, title, imageUrl, link, originUrl, helpUrl } = currentModule; const url = originUrl ? originUrl : link; const webLink = combineUrl( protocol + "//" + hostname, @@ -172,8 +171,8 @@ const Body = ({ modules, match, isLoaded, setCurrentProductId }) => { const moduleDescription = ( - {description}{" "} - {helpUrl && ( + {t("ModuleDescription")}{" "} + {helpUrl && false && ( { ); - return !isLoaded ? ( + return !isLoaded || !tReady ? ( <> - ) : isMobile ? ( + ) : isMobile || isTablet ? ( { src={imageUrl} /> - + {title} {moduleDescription} @@ -245,7 +248,7 @@ const ComingSoonWrapper = inject(({ auth }) => ({ modules: auth.moduleStore.modules, isLoaded: auth.isLoaded, setCurrentProductId: auth.settingsStore.setCurrentProductId, -}))(withRouter(ComingSoon)); +}))(withRouter(withTranslation("ComingSoon")(ComingSoon))); export default (props) => ( diff --git a/products/ASC.CRM/Server/ASC.CRM.csproj b/products/ASC.CRM/Server/ASC.CRM.csproj index 552230915b..482cb429ff 100644 --- a/products/ASC.CRM/Server/ASC.CRM.csproj +++ b/products/ASC.CRM/Server/ASC.CRM.csproj @@ -4,6 +4,11 @@ net5.0 + + + + + diff --git a/products/ASC.CRM/Server/Program.cs b/products/ASC.CRM/Server/Program.cs index 0f6a697061..40cb0088ae 100644 --- a/products/ASC.CRM/Server/Program.cs +++ b/products/ASC.CRM/Server/Program.cs @@ -1,6 +1,13 @@ +using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using ASC.Common.DependencyInjection; +using ASC.Common.Utils; + +using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; @@ -11,40 +18,67 @@ namespace ASC.CRM { public class Program { - public static void Main(string[] args) + public async static Task Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); - }) - .ConfigureAppConfiguration((hostingContext, config) => - { - var buided = config.Build(); - var path = buided["pathToConf"]; - if (!Path.IsPathRooted(path)) - { - path = Path.GetFullPath(Path.Combine(hostingContext.HostingEnvironment.ContentRootPath, path)); - } + var builder = webBuilder.UseStartup(); - config.SetBasePath(path); - config - .AddJsonFile("appsettings.json") - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true) - .AddJsonFile("storage.json") - .AddJsonFile("kafka.json") - .AddJsonFile($"kafka.{hostingContext.HostingEnvironment.EnvironmentName}.json", true) - .AddEnvironmentVariables() - .AddCommandLine(args) - .AddInMemoryCollection(new Dictionary + builder.ConfigureKestrel((hostingContext, serverOptions) => + { + var kestrelConfig = hostingContext.Configuration.GetSection("Kestrel"); + + if (!kestrelConfig.Exists()) return; + + var unixSocket = kestrelConfig.GetValue("ListenUnixSocket"); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + if (!String.IsNullOrWhiteSpace(unixSocket)) + { + unixSocket = String.Format(unixSocket, hostingContext.HostingEnvironment.ApplicationName.Replace("ASC.", "").Replace(".", "")); + + serverOptions.ListenUnixSocket(unixSocket); + } + } + }); + }) + .ConfigureAppConfiguration((hostingContext, config) => { + var buided = config.Build(); + var path = buided["pathToConf"]; + if (!Path.IsPathRooted(path)) + { + path = Path.GetFullPath(CrossPlatform.PathCombine(hostingContext.HostingEnvironment.ContentRootPath, path)); + } + + config.SetBasePath(path); + config + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true) + .AddJsonFile("storage.json") + .AddJsonFile("kafka.json") + .AddJsonFile($"kafka.{hostingContext.HostingEnvironment.EnvironmentName}.json", true) + .AddEnvironmentVariables() + .AddCommandLine(args) + .AddInMemoryCollection(new Dictionary + { {"pathToConf", path} - }); - }); + }); + }) + .ConfigureContainer((context, builder) => + { + builder.Register(context.Configuration, true, false); + });//if (!FilesIntegration.IsRegisteredFileSecurityProvider("crm", "crm_common"))//{// FilesIntegration.RegisterFileSecurityProvider("crm", "crm_common", new FileSecurityProvider());//}////Register prodjects' calendar events//CalendarManager.Instance.RegistryCalendarProvider(userid =>//{// if (WebItemSecurity.IsAvailableForUser(WebItemManager.CRMProductID, userid))// {// return new List { new CRMCalendar(userid) };// }// return new List();//}); } } diff --git a/products/ASC.CRM/Server/Properties/launchSettings.json b/products/ASC.CRM/Server/Properties/launchSettings.json index b93fb0586e..25ff3e6f31 100644 --- a/products/ASC.CRM/Server/Properties/launchSettings.json +++ b/products/ASC.CRM/Server/Properties/launchSettings.json @@ -1,31 +1,29 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:5021", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", + "Kestrel WebServer": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "http://localhost:5021/api/2.0/crm/info", "environmentVariables": { "$STORAGE_ROOT": "../../../Data", "log__dir": "../../../Logs", "log__name": "crm", - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5021" } }, - "ASC.CRM": { - "commandName": "Project", + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "launchUrl": "http://localhost:5021/api/2.0/crm/info", "environmentVariables": { "$STORAGE_ROOT": "../../../Data", "log__dir": "../../../Logs", "log__name": "crm", - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5021" }, - "applicationUrl": "http://localhost:5021" + "distributionName": "Ubuntu-20.04" } } } \ No newline at end of file diff --git a/products/ASC.Calendar/Client/.babelrc b/products/ASC.Calendar/Client/.babelrc new file mode 100644 index 0000000000..b383aed85c --- /dev/null +++ b/products/ASC.Calendar/Client/.babelrc @@ -0,0 +1,7 @@ +{ + "presets": ["@babel/preset-react", "@babel/preset-env"], + "plugins": [ + "@babel/plugin-transform-runtime", + "@babel/plugin-proposal-class-properties" + ] +} diff --git a/products/ASC.Calendar/Client/.env b/products/ASC.Calendar/Client/.env new file mode 100644 index 0000000000..529d10ddce --- /dev/null +++ b/products/ASC.Calendar/Client/.env @@ -0,0 +1,3 @@ +PUBLIC_URL=/products/calendar +WDS_SOCKET_PATH=/products/calendar/sockjs-node +PORT=5017 \ No newline at end of file diff --git a/products/ASC.Calendar/Client/.gitignore b/products/ASC.Calendar/Client/.gitignore new file mode 100644 index 0000000000..d30f40ef44 --- /dev/null +++ b/products/ASC.Calendar/Client/.gitignore @@ -0,0 +1,21 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +/node_modules + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/products/ASC.Calendar/Client/Dockerfile b/products/ASC.Calendar/Client/Dockerfile new file mode 100644 index 0000000000..cf973e8c13 --- /dev/null +++ b/products/ASC.Calendar/Client/Dockerfile @@ -0,0 +1,15 @@ +FROM node:12 +WORKDIR /usr/src/app + +COPY package.json ./ +COPY yarn.lock ./ + +RUN yarn install + +COPY . . + +RUN yarn build + +EXPOSE 5017 + +CMD [ "yarn", "build:start" ] diff --git a/products/ASC.Calendar/Client/analyse.js b/products/ASC.Calendar/Client/analyse.js new file mode 100644 index 0000000000..8c6c219fba --- /dev/null +++ b/products/ASC.Calendar/Client/analyse.js @@ -0,0 +1,17 @@ +// script to enable webpack-bundle-analyzer +process.env.NODE_ENV = "production"; +const webpack = require("webpack"); +const BundleAnalyzerPlugin = require("webpack-bundle-analyzer") + .BundleAnalyzerPlugin; +const webpackConfigProd = require("react-scripts/config/webpack.config")( + "production" +); + +webpackConfigProd.plugins.push(new BundleAnalyzerPlugin()); + +// actually running compilation and waiting for plugin to start explorer +webpack(webpackConfigProd, (err, stats) => { + if (err || stats.hasErrors()) { + console.error(err); + } +}); diff --git a/products/ASC.Calendar/Client/package.json b/products/ASC.Calendar/Client/package.json new file mode 100644 index 0000000000..f9749b4b3f --- /dev/null +++ b/products/ASC.Calendar/Client/package.json @@ -0,0 +1,83 @@ +{ + "name": "@appserver/calendar", + "version": "0.1.3", + "private": "true", + "homepage": "/products/calendar", + "id": "32d24cb5-7ece-4606-9c94-19216ba42086", + "title": "ONLYOFFICE", + "scripts": { + "start": "webpack-cli serve", + "start-prod": "webpack --mode production && serve dist -p 5017", + "build": "webpack --mode production", + "serve": "serve dist -p 5017", + "clean": "rm -rf dist" + }, + "devDependencies": { + "@babel/core": "^7.12.10", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-export-default-from": "^7.12.1", + "@babel/plugin-transform-runtime": "^7.12.1", + "@babel/preset-env": "^7.12.7", + "@babel/preset-react": "^7.12.10", + "@svgr/webpack": "^5.5.0", + "babel-loader": "^8.2.2", + "clean-webpack-plugin": "^3.0.0", + "copy-webpack-plugin": "^7.0.0", + "css-loader": "^3.6.0", + "html-webpack-plugin": "4.5.0", + "json-loader": "^0.5.7", + "serve": "11.3.2", + "source-map-loader": "^1.1.2", + "style-loader": "1.2.1", + "webpack": "5.14.0", + "webpack-cli": "4.5.0", + "webpack-dev-server": "3.11.2", + "workbox-webpack-plugin": "^6.1.1" + }, + "dependencies": { + "@babel/runtime": "^7.12.5", + "attr-accept": "^2.2.2", + "axios": "^0.21.0", + "email-addresses": "^3.1.0", + "i18next": "^19.8.4", + "i18next-http-backend": "^1.1.0", + "mobx": "^6.1.1", + "mobx-react": "^7.1.0", + "moment": "^2.29.1", + "copy-to-clipboard": "^3.2.0", + "fast-deep-equal": "^3.1.3", + "prop-types": "^15.7.2", + "rc-tree": "^2.1.4", + "re-resizable": "^6.9.0", + "react": "^17.0.1", + "react-autosize-textarea": "^7.1.0", + "react-content-loader": "^5.1.4", + "react-custom-scrollbars": "^4.2.1", + "react-device-detect": "^1.14.0", + "react-dom": "^17.0.1", + "react-dropzone": "^11.2.4", + "react-i18next": "^11.7.3", + "react-hammerjs": "^1.0.1", + "react-onclickoutside": "^6.9.0", + "react-player": "^1.15.3", + "react-resize-detector": "^5.2.0", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", + "react-string-format": "^0.1.0", + "react-svg": "^12.0.0", + "react-text-mask": "^5.4.3", + "react-toastify": "^6.1.0", + "react-tooltip": "^4.2.11", + "react-viewer": "^3.2.2", + "react-virtualized-auto-sizer": "^1.0.2", + "react-window": "^1.8.6", + "react-window-infinite-loader": "^1.0.5", + "resize-image": "^0.1.0", + "sass": "^1.29.0", + "sass-loader": "^10.1.0", + "sjcl": "^1.0.8", + "screenfull": "^5.1.0", + "styled-components": "^5.2.1", + "workbox-window": "^6.1.1" + } +} diff --git a/products/ASC.Calendar/Client/public/favicon.ico b/products/ASC.Calendar/Client/public/favicon.ico new file mode 100644 index 0000000000..dcddd3efdf Binary files /dev/null and b/products/ASC.Calendar/Client/public/favicon.ico differ diff --git a/products/ASC.Calendar/Client/public/images/calendar.menu.svg b/products/ASC.Calendar/Client/public/images/calendar.menu.svg new file mode 100644 index 0000000000..5a6d08cd23 --- /dev/null +++ b/products/ASC.Calendar/Client/public/images/calendar.menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/products/ASC.Calendar/Client/public/images/calendar.svg b/products/ASC.Calendar/Client/public/images/calendar.svg new file mode 100644 index 0000000000..17bc3d661a --- /dev/null +++ b/products/ASC.Calendar/Client/public/images/calendar.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/products/ASC.Calendar/Client/public/index.html b/products/ASC.Calendar/Client/public/index.html new file mode 100644 index 0000000000..320f78696c --- /dev/null +++ b/products/ASC.Calendar/Client/public/index.html @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + <%= htmlWebpackPlugin.options.title %> + + + + +
+ +
+
+
+ + + + + + + + +
+
+ + + + + + + + +
+
+
+ + + + + + + + +
+
+ +
+
+ + + + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+ + + + + diff --git a/products/ASC.Calendar/Client/public/locales/en/ComingSoon.json b/products/ASC.Calendar/Client/public/locales/en/ComingSoon.json new file mode 100644 index 0000000000..ad91fbd76f --- /dev/null +++ b/products/ASC.Calendar/Client/public/locales/en/ComingSoon.json @@ -0,0 +1,7 @@ +{ + "ComingSoon": "Coming soon", + "ViewWeb": "View web version", + "OpenApp": "Open your {{title}} app", + "LearnMore": "Learn more", + "ModuleDescription": "Schedule meetings and events" +} diff --git a/products/ASC.Calendar/Client/public/locales/ru/ComingSoon.json b/products/ASC.Calendar/Client/public/locales/ru/ComingSoon.json new file mode 100644 index 0000000000..162fa12258 --- /dev/null +++ b/products/ASC.Calendar/Client/public/locales/ru/ComingSoon.json @@ -0,0 +1,7 @@ +{ + "ComingSoon": "Скоро появится", + "ViewWeb": "Просмотреть веб-версию", + "OpenApp": "Откройте {{title}}", + "LearnMore": "Узнать больше", + "ModuleDescription": "Планируйте встречи и мероприятия" +} diff --git a/products/ASC.Calendar/Client/public/manifest.json b/products/ASC.Calendar/Client/public/manifest.json new file mode 100644 index 0000000000..7f73a7c20d --- /dev/null +++ b/products/ASC.Calendar/Client/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "ASC.Calendar", + "name": "ASC.Calendar", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/products/ASC.Calendar/Client/src/App.js b/products/ASC.Calendar/Client/src/App.js new file mode 100644 index 0000000000..4ed936eff8 --- /dev/null +++ b/products/ASC.Calendar/Client/src/App.js @@ -0,0 +1,7 @@ +//import "./wdyr"; +import React from "react"; +import Shell from "studio/shell"; + +const App = () => ; + +export default App; diff --git a/products/ASC.Calendar/Client/src/App.test.js b/products/ASC.Calendar/Client/src/App.test.js new file mode 100644 index 0000000000..3871ae868d --- /dev/null +++ b/products/ASC.Calendar/Client/src/App.test.js @@ -0,0 +1,15 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import { MemoryRouter } from "react-router-dom"; +import App from "./App"; + +it("renders without crashing", async () => { + const div = document.createElement("div"); + ReactDOM.render( + + + , + div + ); + await new Promise((resolve) => setTimeout(resolve, 1000)); +}); diff --git a/products/ASC.Calendar/Client/src/Calendar.jsx b/products/ASC.Calendar/Client/src/Calendar.jsx new file mode 100644 index 0000000000..e263f51971 --- /dev/null +++ b/products/ASC.Calendar/Client/src/Calendar.jsx @@ -0,0 +1,71 @@ +import React, { useEffect } from "react"; +import { Provider as PeopleProvider, inject, observer } from "mobx-react"; +import { Switch } from "react-router-dom"; +import CalendarStore from "./store/CalendarStore"; +import ErrorBoundary from "@appserver/common/components/ErrorBoundary"; +import toastr from "studio/toastr"; +import PrivateRoute from "@appserver/common/components/PrivateRoute"; +import AppLoader from "@appserver/common/components/AppLoader"; +import { combineUrl, updateTempContent } from "@appserver/common/utils"; +import config from "../package.json"; +import "./custom.scss"; +import i18n from "./i18n"; +import { I18nextProvider } from "react-i18next"; +import Home from "./pages/Home"; +import { AppServerConfig } from "@appserver/common/constants"; + +const { proxyURL } = AppServerConfig; +const homepage = config.homepage; +const PROXY_HOMEPAGE_URL = combineUrl(proxyURL, homepage); + +const Error404 = React.lazy(() => import("studio/Error404")); + +const Error404Route = (props) => ( + }> + + + + +); + +const CalendarContent = (props) => { + const { isLoaded, loadBaseInfo } = props; + + useEffect(() => { + loadBaseInfo() + .catch((err) => toastr.error(err)) + .finally(() => { + //this.props.setIsLoaded(true); + updateTempContent(); + }); + }, []); + + useEffect(() => { + if (isLoaded) updateTempContent(); + }, [isLoaded]); + + return ( + + + + + ); +}; + +const Calendar = inject(({ auth, calendarStore }) => ({ + loadBaseInfo: async () => { + await calendarStore.init(); + auth.setProductVersion(config.version); + }, + isLoaded: auth.isLoaded && calendarStore.isLoaded, +}))(observer(CalendarContent)); + +const calendarStore = new CalendarStore(); + +export default (props) => ( + + + + + +); diff --git a/products/ASC.Calendar/Client/src/bootstrap.js b/products/ASC.Calendar/Client/src/bootstrap.js new file mode 100644 index 0000000000..784d629277 --- /dev/null +++ b/products/ASC.Calendar/Client/src/bootstrap.js @@ -0,0 +1,9 @@ +import App from "./App"; +import React from "react"; +import ReactDOM from "react-dom"; +import config from "../package.json"; +import { registerSW } from "@appserver/common/utils/sw-helper"; + +ReactDOM.render(, document.getElementById("root")); + +registerSW(config.homepage); diff --git a/products/ASC.Calendar/Client/src/custom.scss b/products/ASC.Calendar/Client/src/custom.scss new file mode 100644 index 0000000000..835034402f --- /dev/null +++ b/products/ASC.Calendar/Client/src/custom.scss @@ -0,0 +1,27 @@ +// Override default variables before the import +$font-family-base: "Open Sans", sans-serif; + +html, +body { + height: 100%; +} + +#root { + min-height: 100%; + position: relative; + + .pageLoader { + position: absolute; + left: calc(50% - 20px); + top: 35%; + } +} + +body { + margin: 0; + overflow: hidden; +} + +body.loading * { + cursor: wait !important; +} diff --git a/products/ASC.Calendar/Client/src/helpers/utils.js b/products/ASC.Calendar/Client/src/helpers/utils.js new file mode 100644 index 0000000000..5cc3febb38 --- /dev/null +++ b/products/ASC.Calendar/Client/src/helpers/utils.js @@ -0,0 +1,26 @@ +import authStore from "@appserver/common/store/AuthStore"; +//import store from "../store/store"; + +//const { getCurrentProduct } = commonStore.auth.selectors; + +export const setDocumentTitle = (subTitle = null) => { + // const state = store.getState(); + // const { auth: commonState } = state; + const { isAuthenticated, settingsStore, product: currentModule } = authStore; + const { organizationName } = settingsStore; + + let title; + if (subTitle) { + if (isAuthenticated && currentModule) { + title = subTitle + " - " + currentModule.title; + } else { + title = subTitle + " - " + organizationName; + } + } else if (currentModule && organizationName) { + title = currentModule.title + " - " + organizationName; + } else { + title = organizationName; + } + + document.title = title; +}; diff --git a/products/ASC.Calendar/Client/src/i18n.js b/products/ASC.Calendar/Client/src/i18n.js new file mode 100644 index 0000000000..ae1eed771a --- /dev/null +++ b/products/ASC.Calendar/Client/src/i18n.js @@ -0,0 +1,58 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import Backend from "i18next-http-backend"; +import config from "../package.json"; +import { LANGUAGE } from "@appserver/common/constants"; + +//import LanguageDetector from "i18next-browser-languagedetector"; +// not like to use this? +// have a look at the Quick start guide +// for passing in lng and translations on init + +const languages = ["en", "ru"]; + +i18n + /* + load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales) + learn more: https://github.com/i18next/i18next-http-backend + */ + .use(Backend) + /* + detect user language + learn more: https://github.com/i18next/i18next-browser-languageDetector + */ + //.use(LanguageDetector) + /* + pass the i18n instance to react-i18next. + */ + .use(initReactI18next) + /* + init i18next + for all options read: https://www.i18next.com/overview/configuration-options + */ + .init({ + lng: localStorage.getItem(LANGUAGE) || "en", + supportedLngs: languages, + whitelist: languages, + fallbackLng: "en", + load: "languageOnly", + //debug: true, + + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + format: function (value, format) { + if (format === "lowercase") return value.toLowerCase(); + return value; + }, + }, + + backend: { + loadPath: `${config.homepage}/locales/{{lng}}/{{ns}}.json`, + }, + + react: { + useSuspense: false, + }, + }); + +export default i18n; diff --git a/products/ASC.Calendar/Client/src/index.js b/products/ASC.Calendar/Client/src/index.js new file mode 100644 index 0000000000..50599f7ad8 --- /dev/null +++ b/products/ASC.Calendar/Client/src/index.js @@ -0,0 +1 @@ +import("./bootstrap"); diff --git a/products/ASC.Calendar/Client/src/pages/Home/index.js b/products/ASC.Calendar/Client/src/pages/Home/index.js new file mode 100644 index 0000000000..111f615fc7 --- /dev/null +++ b/products/ASC.Calendar/Client/src/pages/Home/index.js @@ -0,0 +1,257 @@ +import React, { useEffect } from "react"; +import { ReactSVG } from "react-svg"; +import PropTypes from "prop-types"; +import { withRouter } from "react-router"; +import Text from "@appserver/components/text"; +import Link from "@appserver/components/link"; +import Badge from "@appserver/components/badge"; +import Box from "@appserver/components/box"; +import EmptyScreenContainer from "@appserver/components/empty-screen-container"; +import ExternalLinkIcon from "../../../../../../public/images/external.link.react.svg"; +import Loaders from "@appserver/common/components/Loaders"; +import toastr from "studio/toastr"; +import PageLayout from "@appserver/common/components/PageLayout"; +import { withTranslation } from "react-i18next"; +import styled from "styled-components"; +import { isMobile, isTablet, isIOS } from "react-device-detect"; + +import { setDocumentTitle } from "../../helpers/utils"; +import { inject } from "mobx-react"; +import i18n from "../../i18n"; +import { I18nextProvider } from "react-i18next"; +import { combineUrl, deleteCookie } from "@appserver/common/utils"; + +const commonStyles = ` + .link-box { + margin: 8px 0; + .view-web-link { + margin: 8px; + :focus { + outline: 0; + } + } + } +`; + +const ComingSoonPage = styled.div` + padding: ${isTablet ? "106px 0 0 0" : isMobile ? "62px 0 0 0" : "0"}; + width: ${isTablet ? "500px" : "336px"}; + margin: 0 auto; + + .module-logo-icon { + float: left; + margin-top: 8px; + margin-right: 16px; + + svg { + width: ${isTablet ? "192px" : "96px"}; + height: ${isTablet ? "192px" : "96px"}; + } + } + + .module-title { + margin-top: 14px; + margin-bottom: 14px; + } + + .module-info { + margin-bottom: 18px; + .learn-more-link { + white-space: nowrap; + } + } + + .coming-soon-badge { + margin-bottom: 26px; + } + + ${commonStyles} +`; + +const StyledDesktopContainer = styled(EmptyScreenContainer)` + img { + width: 150px; + height: 150px; + } + span:first-of-type { + font-size: 24px; + } + span { + font-size: 14px; + > p { + font-size: 14px; + .learn-more-link { + white-space: nowrap; + } + } + } + ${commonStyles} + + .view-web-link { + font-size: 14px; + } + + .coming-soon-badge > div > p { + font-size: 13px; + } +`; + +const ExternalLink = ({ label, href, onClick }) => ( + + + + {label} + + +); + +const Body = ({ modules, match, isLoaded, setCurrentProductId, t, tReady }) => { + const { error } = match.params; + const { pathname, protocol, hostname } = window.location; + const currentModule = modules.find((m) => m.link === pathname); + const { id, title, imageUrl, link, originUrl, helpUrl } = currentModule; + const url = originUrl ? originUrl : link; + const webLink = combineUrl( + protocol + "//" + hostname, + `${url}?desktop_view=true` + ); + const appLink = isIOS + ? id === "2A923037-8B2D-487b-9A22-5AC0918ACF3F" + ? "message:" + : id === "32D24CB5-7ECE-4606-9C94-19216BA42086" + ? "calshow:" + : false + : false; + + setDocumentTitle(); + + useEffect(() => { + setCurrentProductId(id); + }, [id, setCurrentProductId]); + + useEffect(() => error && toastr.error(error), [error]); + + const appButtons = ( + <> + + { + deleteCookie("desktop_view"); + window.open(webLink, "_self", "", true); + }} + /> + {appLink && ( + + )} + + ); + + const moduleDescription = ( + + {t("ModuleDescription")}{" "} + {helpUrl && false && ( + + {t("LearnMore")}... + + )} + + ); + + return !isLoaded || !tReady ? ( + <> + ) : isMobile || isTablet ? ( + + ( + + )} + src={imageUrl} + /> + + + {title} + + {moduleDescription} + {appButtons} + + + ) : ( + + ); +}; + +const ComingSoon = (props) => { + return ( + + + + + + ); +}; + +ComingSoon.propTypes = { + modules: PropTypes.array, + isLoaded: PropTypes.bool, +}; + +const ComingSoonWrapper = inject(({ auth }) => ({ + modules: auth.moduleStore.modules, + isLoaded: auth.isLoaded, + setCurrentProductId: auth.settingsStore.setCurrentProductId, +}))(withRouter(withTranslation("ComingSoon")(ComingSoon))); + +export default (props) => ( + + + +); diff --git a/products/ASC.Calendar/Client/src/store/CalendarStore.js b/products/ASC.Calendar/Client/src/store/CalendarStore.js new file mode 100644 index 0000000000..7d36a56d4c --- /dev/null +++ b/products/ASC.Calendar/Client/src/store/CalendarStore.js @@ -0,0 +1,39 @@ +import { action, makeObservable, observable } from "mobx"; +import config from "../../package.json"; +import store from "studio/store"; +const { auth: authStore } = store; + +class CalendarStore { + isLoading = false; + isLoaded = false; + isInit = false; + + constructor() { + makeObservable(this, { + isLoading: observable, + isLoaded: observable, + setIsLoading: action, + setIsLoaded: action, + init: action, + }); + } + + init = async () => { + if (this.isInit) return; + this.isInit = true; + + authStore.settingsStore.setModuleInfo(config.homepage, config.id); + + this.setIsLoaded(true); + }; + + setIsLoading = (loading) => { + this.isLoading = loading; + }; + + setIsLoaded = (isLoaded) => { + this.isLoaded = isLoaded; + }; +} + +export default CalendarStore; diff --git a/products/ASC.Calendar/Client/src/wdyr.js b/products/ASC.Calendar/Client/src/wdyr.js new file mode 100644 index 0000000000..19a8d822a5 --- /dev/null +++ b/products/ASC.Calendar/Client/src/wdyr.js @@ -0,0 +1,12 @@ +import React from "react"; + +if (process.env.NODE_ENV === "development") { + const whyDidYouRender = require("@welldone-software/why-did-you-render"); + whyDidYouRender(React, { + trackAllPureComponents: true, + collapseGroups: true, + //trackExtraHooks: [ + // [ReactRedux, 'useSelector'] + //] + }); +} diff --git a/products/ASC.Calendar/Client/webpack.config.js b/products/ASC.Calendar/Client/webpack.config.js new file mode 100644 index 0000000000..397cfb8696 --- /dev/null +++ b/products/ASC.Calendar/Client/webpack.config.js @@ -0,0 +1,198 @@ +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const CopyPlugin = require("copy-webpack-plugin"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const ModuleFederationPlugin = require("webpack").container + .ModuleFederationPlugin; +const TerserPlugin = require("terser-webpack-plugin"); +const { InjectManifest } = require("workbox-webpack-plugin"); +const combineUrl = require("@appserver/common/utils/combineUrl"); +const AppServerConfig = require("@appserver/common/constants/AppServerConfig"); + +const path = require("path"); +const pkg = require("./package.json"); +const deps = pkg.dependencies; +const homepage = pkg.homepage; // combineUrl(AppServerConfig.proxyURL, pkg.homepage); +const title = pkg.title; + +var config = { + mode: "development", + entry: "./src/index", + + devServer: { + publicPath: homepage, + + contentBase: [path.join(__dirname, "dist")], + contentBasePublicPath: homepage, + port: 5017, + historyApiFallback: { + // Paths with dots should still use the history fallback. + // See https://github.com/facebook/create-react-app/issues/387. + disableDotRule: true, + index: homepage, + }, + // proxy: [ + // { + // context: "/api", + // target: "http://localhost:8092", + // }, + // ], + hot: false, + hotOnly: false, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS", + "Access-Control-Allow-Headers": + "X-Requested-With, content-type, Authorization", + }, + }, + + output: { + publicPath: "auto", + chunkFilename: "static/js/[id].[contenthash].js", + //assetModuleFilename: "static/images/[hash][ext][query]", + path: path.resolve(process.cwd(), "dist"), + filename: "static/js/[name].[contenthash].bundle.js", + }, + + resolve: { + extensions: [".jsx", ".js", ".json"], + fallback: { + crypto: false, + }, + }, + + module: { + rules: [ + { + test: /\.(png|jpe?g|gif|ico)$/i, + type: "asset/resource", + generator: { + filename: "static/images/[hash][ext][query]", + }, + }, + { + test: /\.m?js/, + type: "javascript/auto", + resolve: { + fullySpecified: false, + }, + }, + { + test: /\.react.svg$/, + use: [ + { + loader: "@svgr/webpack", + options: { + svgoConfig: { + plugins: [{ removeViewBox: false }], + }, + }, + }, + ], + }, + { test: /\.json$/, loader: "json-loader" }, + { + test: /\.css$/i, + use: ["style-loader", "css-loader"], + }, + { + test: /\.s[ac]ss$/i, + use: [ + // Creates `style` nodes from JS strings + "style-loader", + // Translates CSS into CommonJS + "css-loader", + // Compiles Sass to CSS + "sass-loader", + ], + }, + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: [ + { + loader: "babel-loader", + options: { + presets: ["@babel/preset-react", "@babel/preset-env"], + plugins: [ + "@babel/plugin-transform-runtime", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-export-default-from", + ], + }, + }, + "source-map-loader", + ], + }, + ], + }, + + plugins: [ + new CleanWebpackPlugin(), + new ModuleFederationPlugin({ + name: "calendar", + filename: "remoteEntry.js", + remotes: { + studio: `studio@${combineUrl( + AppServerConfig.proxyURL, + "/remoteEntry.js" + )}`, + }, + exposes: { + "./app": "./src/Calendar.jsx", + }, + shared: { + ...deps, + react: { + singleton: true, + requiredVersion: deps.react, + }, + "react-dom": { + singleton: true, + requiredVersion: deps["react-dom"], + }, + }, + }), + new HtmlWebpackPlugin({ + template: "./public/index.html", + publicPath: homepage, + title: title, + base: `${homepage}/`, + }), + new CopyPlugin({ + patterns: [ + { + from: "public", + globOptions: { + dot: true, + gitignore: true, + ignore: ["**/index.html"], + }, + }, + ], + }), + ], +}; + +module.exports = (env, argv) => { + if (argv.mode === "production") { + config.mode = "production"; + config.optimization = { + splitChunks: { chunks: "all" }, + minimize: true, + minimizer: [new TerserPlugin()], + }; + config.plugins.push( + new InjectManifest({ + mode: "production", //"development", + swSrc: "@appserver/common/utils/sw-template.js", // this is your sw template file + swDest: "sw.js", // this will be created in the build step + exclude: [/\.map$/, /manifest$/, /service-worker\.js$/], + }) + ); + } else { + config.devtool = "cheap-module-source-map"; + } + + return config; +}; diff --git a/products/ASC.Calendar/Server/ASC.Calendar.csproj b/products/ASC.Calendar/Server/ASC.Calendar.csproj new file mode 100644 index 0000000000..cf1455a2e5 --- /dev/null +++ b/products/ASC.Calendar/Server/ASC.Calendar.csproj @@ -0,0 +1,47 @@ + + + + net5.0 + + + + + + + + + + + + + + + True + True + CalendarAddonResource.resx + + + + + + ResXFileCodeGenerator + CalendarAddonResource.Designer.cs + + + CalendarAddonResource.resx + + + CalendarAddonResource.resx + + + CalendarAddonResource.resx + + + CalendarAddonResource.resx + + + CalendarAddonResource.resx + + + + diff --git a/products/ASC.Calendar/Server/Classes/PathProvider.cs b/products/ASC.Calendar/Server/Classes/PathProvider.cs new file mode 100644 index 0000000000..57d9d400ab --- /dev/null +++ b/products/ASC.Calendar/Server/Classes/PathProvider.cs @@ -0,0 +1,53 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2018 + * + * This program is freeware. You can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html). + * In accordance with Section 7(a) of the GNU GPL 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 more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html + * + * You can contact Ascensio System SIA by email at sales@onlyoffice.com + * + * The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display + * Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3. + * + * Pursuant to Section 7 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains + * relevant author attributions when distributing the software. If the display of the logo in its graphic + * form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE" + * in every copy of the program you distribute. + * Pursuant to Section 7 3(e) we decline to grant you any rights under trademark law for use of our trademarks. + * +*/ + + +using System; + +using ASC.Common; +using ASC.Core.Common; + +namespace ASC.Web.Calendar.Classes +{ + [Scope] + public class PathProvider + { + public string BaseVirtualPath { get; private set; } + public string BaseAbsolutePath { get; private set; } + + public PathProvider(BaseCommonLinkUtility commonLinkUtility) + { + BaseVirtualPath = "~/Products/Calendar/"; + try + { + BaseAbsolutePath = commonLinkUtility.ToAbsolute(BaseVirtualPath); + } + catch (Exception) + { + BaseAbsolutePath = BaseVirtualPath; + } + } + } +} \ No newline at end of file diff --git a/products/ASC.Calendar/Server/Configuration/ProductEntryPoint.cs b/products/ASC.Calendar/Server/Configuration/ProductEntryPoint.cs new file mode 100644 index 0000000000..b58a334e05 --- /dev/null +++ b/products/ASC.Calendar/Server/Configuration/ProductEntryPoint.cs @@ -0,0 +1,112 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2018 + * + * This program is freeware. You can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html). + * In accordance with Section 7(a) of the GNU GPL 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 more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html + * + * You can contact Ascensio System SIA by email at sales@onlyoffice.com + * + * The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display + * Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3. + * + * Pursuant to Section 7 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains + * relevant author attributions when distributing the software. If the display of the logo in its graphic + * form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE" + * in every copy of the program you distribute. + * Pursuant to Section 7 3(e) we decline to grant you any rights under trademark law for use of our trademarks. + * +*/ + + +using System; + +using ASC.Calendar.Resources; +using ASC.Common; +using ASC.Core; +using ASC.Web.Calendar.Classes; +using ASC.Web.Core; + +namespace ASC.Calendar.Configuration +{ + [Scope] + public class ProductEntryPoint : Product + { + private ProductContext context; + + public static readonly Guid ID = WebItemManager.CalendarProductID; + + private AuthContext AuthContext { get; } + private UserManager UserManager { get; } + private PathProvider PathProvider { get; } + + public ProductEntryPoint( + AuthContext authContext, + UserManager userManager, + PathProvider pathProvider) + { + AuthContext = authContext; + UserManager = userManager; + PathProvider = pathProvider; + } + + public override Guid ProductID + { + get { return ID; } + } + + public override string Name + { + get { return CalendarAddonResource.AddonName; } + } + + public override string Description + { + get { return CalendarAddonResource.AddonDescription; } + } + + public override string StartURL + { + get { return PathProvider.BaseAbsolutePath; } + } + + public override string HelpURL + { + get { return "https://helpcenter.onlyoffice.com/userguides/calendar.aspx"; } + } + + public override string ProductClassName + { + get { return "calendar"; } + } + + public override bool Visible { get { return true; } } + + public override ProductContext Context + { + get { return context; } + } + + public override string ApiURL => "api/2.0/calendar/info.json"; + + public override void Init() + { + context = new ProductContext + { + //MasterPageFile = String.Concat(PathProvider.BaseVirtualPath, "Masters/BasicTemplate.Master"), + DisabledIconFileName = "product_disabled_logo.png", + IconFileName = "images/calendar.menu.svg", + LargeIconFileName = "images/calendar.svg", + //SubscriptionManager = new ProductSubscriptionManager(), + DefaultSortOrder = 20, + //SpaceUsageStatManager = new ProjectsSpaceUsageStatManager(), + HasComplexHierarchyOfAccessRights = true, + }; + } + } +} \ No newline at end of file diff --git a/products/ASC.Calendar/Server/Controllers/CalendarController.cs b/products/ASC.Calendar/Server/Controllers/CalendarController.cs new file mode 100644 index 0000000000..ae091c0ebe --- /dev/null +++ b/products/ASC.Calendar/Server/Controllers/CalendarController.cs @@ -0,0 +1,54 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2018 + * + * This program is freeware. You can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html). + * In accordance with Section 7(a) of the GNU GPL 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 more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html + * + * You can contact Ascensio System SIA by email at sales@onlyoffice.com + * + * The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display + * Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3. + * + * Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains + * relevant author attributions when distributing the software. If the display of the logo in its graphic + * form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE" + * in every copy of the program you distribute. + * Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks. + * +*/ + +using ASC.Api.Core; +using ASC.Common; +using ASC.Calendar.Configuration; +using ASC.Web.Api.Routing; + +using Microsoft.AspNetCore.Mvc; + +namespace ASC.Calendar.Controllers +{ + [Scope] + [DefaultRoute] + [ApiController] + public class CalendarController : ControllerBase + { + private ProductEntryPoint ProductEntryPoint { get; } + + public CalendarController(ProductEntryPoint productEntryPoint) + { + ProductEntryPoint = productEntryPoint; + } + + [Read("info")] + public Module GetModule() + { + ProductEntryPoint.Init(); + return new Module(ProductEntryPoint); + } + } +} diff --git a/products/ASC.Calendar/Server/Program.cs b/products/ASC.Calendar/Server/Program.cs new file mode 100644 index 0000000000..4a73ada4f7 --- /dev/null +++ b/products/ASC.Calendar/Server/Program.cs @@ -0,0 +1,56 @@ + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +using Autofac.Extensions.DependencyInjection; + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace ASC.Calendar +{ + public class Program + { + public async static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + await host.RunAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .ConfigureAppConfiguration((hostingContext, config) => + { + var buided = config.Build(); + var path = buided["pathToConf"]; + if (!Path.IsPathRooted(path)) + { + path = Path.GetFullPath(Path.Combine(hostingContext.HostingEnvironment.ContentRootPath, path)); + } + + config.SetBasePath(path); + config + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true) + .AddJsonFile("storage.json") + .AddJsonFile("kafka.json") + .AddJsonFile($"kafka.{hostingContext.HostingEnvironment.EnvironmentName}.json", true) + .AddEnvironmentVariables() + .AddCommandLine(args) + .AddInMemoryCollection(new Dictionary + { + {"pathToConf", path} + }); + }); + } +} diff --git a/products/ASC.Calendar/Server/Properties/launchSettings.json b/products/ASC.Calendar/Server/Properties/launchSettings.json new file mode 100644 index 0000000000..484ee9d02d --- /dev/null +++ b/products/ASC.Calendar/Server/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "profiles": { + "Kestrel WebServer": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "http://localhost:5023/api/2.0/calendar/info", + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__dir": "../../../Logs", + "log__name": "calendar", + "ASPNETCORE_URLS": "http://localhost:5023", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WSL 2 : Ubuntu 20.04": { + "commandName": "WSL2", + "launchBrowser": false, + "launchUrl": "http://localhost:5023/api/2.0/calendar/info", + "environmentVariables": { + "$STORAGE_ROOT": "../../../Data", + "log__dir": "../../../Logs", + "log__name": "calendar", + "ASPNETCORE_URLS": "http://localhost:5023", + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "distributionName": "Ubuntu-20.04" + } + } +} \ No newline at end of file diff --git a/products/ASC.Calendar/Server/Resources/CalendarAddonResource.Designer.cs b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.Designer.cs new file mode 100644 index 0000000000..332067c8d4 --- /dev/null +++ b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ASC.Calendar.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class CalendarAddonResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal CalendarAddonResource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ASC.Calendar.Resources.CalendarAddonResource", typeof(CalendarAddonResource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Organize your work schedule. Track tasks and milestones due dates. Get notifications about events.. + /// + internal static string AddonDescription { + get { + return ResourceManager.GetString("AddonDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Calendar. + /// + internal static string AddonName { + get { + return ResourceManager.GetString("AddonName", resourceCulture); + } + } + } +} diff --git a/products/ASC.Calendar/Server/Resources/CalendarAddonResource.de.resx b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.de.resx new file mode 100644 index 0000000000..7519b32402 --- /dev/null +++ b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.de.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Stellen Sie Ihren Arbeitsplan auf. Behalten Sie die Übersicht über die Fristen der Aufgaben und Meilensteine. Erhalten Sie Benachrichtigungen über Ereignisse. + + + Kalender + + \ No newline at end of file diff --git a/products/ASC.Calendar/Server/Resources/CalendarAddonResource.es.resx b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.es.resx new file mode 100644 index 0000000000..c82c23dc99 --- /dev/null +++ b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.es.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Organice su calendario de trabajo. Siga tareas e hitos por sus fechas limite. Reciba notificaciones sobre los eventos. + + + Calendario + + \ No newline at end of file diff --git a/products/ASC.Calendar/Server/Resources/CalendarAddonResource.fr.resx b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.fr.resx new file mode 100644 index 0000000000..7f0bc56a5b --- /dev/null +++ b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.fr.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Organisez votre horaire de travail. Contrôlez les dates limites des tâches et des jalons. Recevez des notifications d'événements. + + + Calendrier + + \ No newline at end of file diff --git a/products/ASC.Calendar/Server/Resources/CalendarAddonResource.it.resx b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.it.resx new file mode 100644 index 0000000000..cc305d2e85 --- /dev/null +++ b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.it.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Organizza il tuo orario di lavoro. Controlla le attività e le milestone. Ricevi promemoria degli eventi. + + + Calendario + + \ No newline at end of file diff --git a/products/ASC.Calendar/Server/Resources/CalendarAddonResource.resx b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.resx new file mode 100644 index 0000000000..3e689a75a8 --- /dev/null +++ b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Organize your work schedule. Track tasks and milestones due dates. Get notifications about events. + + + Calendar + + \ No newline at end of file diff --git a/products/ASC.Calendar/Server/Resources/CalendarAddonResource.ru.resx b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.ru.resx new file mode 100644 index 0000000000..405d59269f --- /dev/null +++ b/products/ASC.Calendar/Server/Resources/CalendarAddonResource.ru.resx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Организуйте своё рабочее расписание. Отслеживайте даты наступления задач и вех. Получайте оповещения о событиях. + + + Календарь + + \ No newline at end of file diff --git a/products/ASC.Calendar/Server/Startup.cs b/products/ASC.Calendar/Server/Startup.cs new file mode 100644 index 0000000000..7dc06000f1 --- /dev/null +++ b/products/ASC.Calendar/Server/Startup.cs @@ -0,0 +1,42 @@ + +using System.Text; + +using ASC.Api.Core; + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace ASC.Calendar +{ + public class Startup : BaseStartup + { + public override string[] LogParams { get => new string[] { "ASC.Calendar" }; } + + public Startup(IConfiguration configuration, IHostEnvironment hostEnvironment) + : base(configuration, hostEnvironment) + { + + } + + public override void ConfigureServices(IServiceCollection services) + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + base.ConfigureServices(services); + } + + public override void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseCors(builder => + builder + .AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod()); + + base.Configure(app, env); + } + } +} diff --git a/products/ASC.Calendar/Server/appsettings.json b/products/ASC.Calendar/Server/appsettings.json new file mode 100644 index 0000000000..9bf702e310 --- /dev/null +++ b/products/ASC.Calendar/Server/appsettings.json @@ -0,0 +1,3 @@ +{ + "pathToConf": "..\\..\\..\\config" +} diff --git a/products/ASC.Files/Client/package.json b/products/ASC.Files/Client/package.json index 2bd45a0442..e0bb11fc77 100644 --- a/products/ASC.Files/Client/package.json +++ b/products/ASC.Files/Client/package.json @@ -1,6 +1,6 @@ { "name": "@appserver/files", - "version": "0.1.3", + "version": "0.1.8", "private": "true", "homepage": "/products/files", "id": "e67be73d-f9ae-4ce1-8fec-1880cb518cb4", diff --git a/products/ASC.Files/Client/public/locales/en/ConflictResolveDialog.json b/products/ASC.Files/Client/public/locales/en/ConflictResolveDialog.json new file mode 100644 index 0000000000..b1a24901c2 --- /dev/null +++ b/products/ASC.Files/Client/public/locales/en/ConflictResolveDialog.json @@ -0,0 +1,14 @@ +{ + "OKButton": "OK", + "CancelButton": "Cancel", + "ConflictResolveTitle": "Overwrite confirmation", + "ConflictResolveDescription": "The file with the name {{file}} already exists in the folder {{folder}}.", + "ConflictResolveDescriptionFiles": "{{filesCount}} documents with the same name already exist in the folder '{{folder}}'.", + "ConflictResolveSelectAction": "Please select the action:", + "OverwriteTitle": "Overwrite with version update", + "OverwriteDescription": "The file will be added to the file with the same name as a version.", + "CreateTitle": "Create file copy", + "CreateDescription": "There will be two different files in the folder.", + "SkipTitle": "Skip", + "SkipDescription": "No file will be copied. The original file will be retained in the destination folder." +} diff --git a/products/ASC.Files/Client/public/locales/en/DeleteDialog.json b/products/ASC.Files/Client/public/locales/en/DeleteDialog.json index aaca2fbfc5..891607a095 100644 --- a/products/ASC.Files/Client/public/locales/en/DeleteDialog.json +++ b/products/ASC.Files/Client/public/locales/en/DeleteDialog.json @@ -1,16 +1,19 @@ { - "OKButton": "OK", - "CancelButton": "Cancel", - "ConfirmationTitle": "Confirmation", - "QuestionDeleteFile": "Are you sure you want to delete this file?", "FilesModule": "Files", "FoldersModule": "Folders", - "DeleteDialogQuestion": "Are you sure you want to delete these elements?", - "DeleteDialogNote": "Note: This action can not be undone.", - "QuestionDeleteFolder": "Are you sure you want to delete this folder?", - "QuestionDeleteElements": "Are you sure you want to delete these elements?", "DeleteOperation": "Deleting", "DeleteSelectedElem": "Selected elements were successfully deleted", - "DeleteFromTrash": "Selected elements were successfully deleted from trash" + "DeleteFromTrash": "Selected elements were successfully deleted from trash", + + "MoveToTrashOneFileTitle": "Move file to Trash?", + "MoveToTrashOneFolderTitle": "Move folder to Trash?", + "MoveToTrashItemsTitle": "Move items to Trash?", + + "MoveToTrashOneFileNote": "If you delete a shared file, other users won’t be able to access it.", + "MoveToTrashOneFolderNote": "If you delete a shared folder, other users won’t be able to access it.", + "MoveToTrashItemsNote": "If you delete shared items, other users won’t be able to access them.", + + "MoveToTrashButton": "Move to Trash", + "CancelButton": "Cancel" } diff --git a/products/ASC.Files/Client/public/locales/en/EmptyTrashDialog.json b/products/ASC.Files/Client/public/locales/en/EmptyTrashDialog.json index 807debcc95..40e65b3873 100644 --- a/products/ASC.Files/Client/public/locales/en/EmptyTrashDialog.json +++ b/products/ASC.Files/Client/public/locales/en/EmptyTrashDialog.json @@ -1,11 +1,10 @@ { - "OKButton": "OK", - "CancelButton": "Cancel", - "ConfirmationTitle": "Confirmation", - - "EmptyTrashDialogQuestion": "Are you sure you want to empty the recycle bin?", - "EmptyTrashDialogMessage": "Note: This action can not be undone. Note: removal from your account can not be undone.", "DeleteOperation": "Deleting", - "SuccessEmptyTrash": "Success empty recycle bin" + "SuccessEmptyTrash": "Success empty recycle bin", + "DeleteForeverTitle": "Delete forever?", + "DeleteForeverNote": "All items from Trash will be deleted forever. You won’t be able to restore them.", + + "DeleteForeverButton": "Delete forever", + "CancelButton": "Cancel" } diff --git a/products/ASC.Files/Client/public/locales/en/Home.json b/products/ASC.Files/Client/public/locales/en/Home.json index e78d7bdad7..ade6127db7 100644 --- a/products/ASC.Files/Client/public/locales/en/Home.json +++ b/products/ASC.Files/Client/public/locales/en/Home.json @@ -112,6 +112,8 @@ "AddFilter": "Add filter", "FolderCreated": "New folder {{itemTitle}} is created", "FileCreated": "New file {{itemTitle}}.{{exst}} is created", + "FileRenamed": "The document '{{oldTitle}}' is renamed to '{{newTitle}}'", + "FolderRenamed": "The folder '{{folderTitle}}' is renamed to '{{newFoldedTitle}}'", "FolderRemoved": "Folder moved to recycle bin", "FileRemoved": "File moved to recycle bin", "MoveItem": "{{title}} moved", diff --git a/products/ASC.Files/Client/public/locales/ru/ConflictResolveDialog.json b/products/ASC.Files/Client/public/locales/ru/ConflictResolveDialog.json new file mode 100644 index 0000000000..40df1c5caf --- /dev/null +++ b/products/ASC.Files/Client/public/locales/ru/ConflictResolveDialog.json @@ -0,0 +1,14 @@ +{ + "OKButton": "ОК", + "CancelButton": "Отмена", + "ConflictResolveTitle": "Подтверждение перезаписи", + "ConflictResolveDescription": "Файл с именем {{file}} уже существует в папке {{folder}}.", + "ConflictResolveDescriptionFiles": "{{filesCount}} документов с одинаковыми именами уже существуют в папке '{{folder}}'.", + "ConflictResolveSelectAction": "Пожалуйста, выберите действие:", + "OverwriteTitle": "Перезаписать с обновлением версии", + "OverwriteDescription": "Файл будет добавлен к файлу с таким же именем как версия.", + "CreateTitle": "Создать копию файла", + "CreateDescription": "В папке будет два разных файла.", + "SkipTitle": "Пропустить", + "SkipDescription": "Файл не будет скопирован. В папке назначения останется исходный файл." +} diff --git a/products/ASC.Files/Client/public/locales/ru/DeleteDialog.json b/products/ASC.Files/Client/public/locales/ru/DeleteDialog.json index 9a8cb89c1f..fe06e7c258 100644 --- a/products/ASC.Files/Client/public/locales/ru/DeleteDialog.json +++ b/products/ASC.Files/Client/public/locales/ru/DeleteDialog.json @@ -1,17 +1,19 @@ { - "OKButton": "ОК", - "CancelButton": "Отмена", - "ConfirmationTitle": "Подтверждение удаления", - "QuestionDeleteFile": "Вы собираетесь удалить этот файл?", "FilesModule": "Файлы", "FoldersModule": "Папки", - "DeleteDialogQuestion": "Вы собираетесь удалить эти элементы?", - "DeleteDialogMessage": "Пожалуйста, обратите внимание, что если вы предоставили кому-то доступ к этим файлам/папкам, они станут недоступны. Вы уверены, что хотите продолжить?", - "DeleteDialogNote": "Замечание: Это действие не может быть отменено.", - "QuestionDeleteFolder": "Вы собираетесь удалить эту папку?", - "QuestionDeleteElements": "Вы собираетесь удалить эти элементы?", "DeleteOperation": "Удаление", "DeleteSelectedElem": "Выбранные элементы успешно удалены", - "DeleteFromTrash": "Выбранные элементы успешно удалены из корзины" + "DeleteFromTrash": "Выбранные элементы успешно удалены из корзины", + + "MoveToTrashOneFileTitle": "Переместить файл в корзину?", + "MoveToTrashOneFolderTitle": "Переместить папку в корзину?", + "MoveToTrashItemsTitle": "Переместить элементы в корзину?", + + "MoveToTrashOneFileNote": "Если вы удалите расшаренный файл, другие пользователи не смогут получить к нему доступ.", + "MoveToTrashOneFolderNote": "Если вы удалите расшаренную папку, другие пользователи не смогут получить к нему доступ.", + "MoveToTrashItemsNote": "Если вы удалите расшаренные элементы, другие пользователи не смогут получить к ним доступ.", + + "MoveToTrashButton": "Переместить в корзину", + "CancelButton": "Отмена" } diff --git a/products/ASC.Files/Client/public/locales/ru/EmptyTrashDialog.json b/products/ASC.Files/Client/public/locales/ru/EmptyTrashDialog.json index 716632bd9d..380118381f 100644 --- a/products/ASC.Files/Client/public/locales/ru/EmptyTrashDialog.json +++ b/products/ASC.Files/Client/public/locales/ru/EmptyTrashDialog.json @@ -1,10 +1,10 @@ { - "OKButton": "ОК", - "CancelButton": "Отмена", - "ConfirmationTitle": "Подтверждение удаления", - - "EmptyTrashDialogQuestion": "Вы уверены, что хотите очистить корзину?", - "EmptyTrashDialogMessage": "Замечание: Это действие не может быть отменено.", "DeleteOperation": "Удаление", - "SuccessEmptyTrash": "Корзина успешно очищена" + "SuccessEmptyTrash": "Корзина успешно очищена", + + "DeleteForeverTitle": "Удалить навсегда?", + "DeleteForeverNote": "Все объекты из корзины будут удалены навсегда. Вы не сможете их восстановить.", + + "DeleteForeverButton": "Удалить навсегда", + "CancelButton": "Отмена" } diff --git a/products/ASC.Files/Client/public/locales/ru/Home.json b/products/ASC.Files/Client/public/locales/ru/Home.json index df899fb0a7..cf1f514df9 100644 --- a/products/ASC.Files/Client/public/locales/ru/Home.json +++ b/products/ASC.Files/Client/public/locales/ru/Home.json @@ -112,6 +112,8 @@ "AddFilter": "Добавить фильтр", "FolderCreated": "Создана новая папка {{itemTitle}}", "FileCreated": "Создан новый файл {{itemTitle}}.{{exst}}", + "FileRenamed": "Документ '{{oldTitle}}' переименован в '{{newTitle}}'", + "FolderRenamed": "Папка '{{folderTitle}}' переименована в '{{newFoldedTitle}}'", "FolderRemoved": "Папка перемещена в корзину", "FileRemoved": "Файл перемещен в корзину", "MoveItem": "{{title}} перемещен", diff --git a/products/ASC.Files/Client/src/Files.jsx b/products/ASC.Files/Client/src/Files.jsx index 06376c70a6..82b3c9db14 100644 --- a/products/ASC.Files/Client/src/Files.jsx +++ b/products/ASC.Files/Client/src/Files.jsx @@ -12,9 +12,9 @@ import "./custom.scss"; import i18n from "./i18n"; import { I18nextProvider } from "react-i18next"; import { regDesktop } from "@appserver/common/desktop"; -import Home from "./components/pages/Home"; -import Settings from "./components/pages/Settings"; -import VersionHistory from "./components/pages/VersionHistory"; +import Home from "./pages/Home"; +import Settings from "./pages/Settings"; +import VersionHistory from "./pages/VersionHistory"; import ErrorBoundary from "@appserver/common/components/ErrorBoundary"; import Panels from "./components/FilesPanels"; import { AppServerConfig } from "@appserver/common/constants"; diff --git a/products/ASC.Files/Client/src/HOCs/withBadges.js b/products/ASC.Files/Client/src/HOCs/withBadges.js new file mode 100644 index 0000000000..badba784b3 --- /dev/null +++ b/products/ASC.Files/Client/src/HOCs/withBadges.js @@ -0,0 +1,269 @@ +import React from "react"; +import { inject, observer } from "mobx-react"; + +import { + ShareAccessRights, + AppServerConfig, +} from "@appserver/common/constants"; +import toastr from "studio/toastr"; +import { combineUrl } from "@appserver/common/utils"; +import { + convertFile, + getFileConversationProgress, +} from "@appserver/common/api/files"; + +import Badges from "../components/Badges"; +import config from "../../package.json"; + +export default function withBadges(WrappedComponent) { + class WithBadges extends React.Component { + constructor(props) { + super(props); + this.state = { showConvertDialog: false }; + } + onClickLock = () => { + const { item, lockFileAction } = this.props; + const { locked, id } = item; + + lockFileAction(id, !locked).catch((err) => toastr.error(err)); + }; + + onClickFavorite = () => { + const { t, item, setFavoriteAction } = this.props; + + setFavoriteAction("remove", item.id) + .then(() => toastr.success(t("RemovedFromFavorites"))) + .catch((err) => toastr.error(err)); + }; + + onShowVersionHistory = () => { + const { + homepage, + isTabletView, + item, + setIsVerHistoryPanel, + fetchFileVersions, + history, + isTrashFolder, + } = this.props; + if (isTrashFolder) return; + + if (!isTabletView) { + fetchFileVersions(item.id + ""); + setIsVerHistoryPanel(true); + } else { + history.push( + combineUrl(AppServerConfig.proxyURL, homepage, `/${item.id}/history`) + ); + } + }; + onBadgeClick = () => { + const { + item, + selectedFolderPathParts, + markAsRead, + setNewFilesPanelVisible, + setNewFilesIds, + updateRootBadge, + updateFileBadge, + } = this.props; + if (item.fileExst) { + markAsRead([], [item.id]) + .then(() => { + updateRootBadge(selectedFolderPathParts[0], 1); + updateFileBadge(item.id); + }) + .catch((err) => toastr.error(err)); + } else { + setNewFilesPanelVisible(true); + const newFolderIds = selectedFolderPathParts; + newFolderIds.push(item.id); + setNewFilesIds(newFolderIds); + } + }; + + setConvertDialogVisible = () => + this.setState({ showConvertDialog: !this.state.showConvertDialog }); + + onConvert = () => { + const { item, t, setSecondaryProgressBarData } = this.props; + setSecondaryProgressBarData({ + icon: "file", + visible: true, + percent: 0, + label: t("Convert"), + alert: false, + }); + this.setState({ showConvertDialog: false }, () => + convertFile(item.id).then((convertRes) => { + if (convertRes && convertRes[0] && convertRes[0].progress !== 100) { + this.getConvertProgress(item.id); + } + }) + ); + }; + + getConvertProgress = (fileId) => { + const { + selectedFolderId, + filter, + setIsLoading, + setSecondaryProgressBarData, + t, + clearSecondaryProgressData, + fetchFiles, + } = this.props; + getFileConversationProgress(fileId).then((res) => { + if (res && res[0] && res[0].progress !== 100) { + setSecondaryProgressBarData({ + icon: "file", + visible: true, + percent: res[0].progress, + label: t("Convert"), + alert: false, + }); + setTimeout(() => this.getConvertProgress(fileId), 1000); + } else { + if (res[0].error) { + setSecondaryProgressBarData({ + visible: true, + alert: true, + }); + toastr.error(res[0].error); + setTimeout(() => clearSecondaryProgressData(), TIMEOUT); + } else { + setSecondaryProgressBarData({ + icon: "file", + visible: true, + percent: 100, + label: t("Convert"), + alert: false, + }); + setTimeout(() => clearSecondaryProgressData(), TIMEOUT); + const newFilter = filter.clone(); + fetchFiles(selectedFolderId, newFilter) + .catch((err) => { + setSecondaryProgressBarData({ + visible: true, + alert: true, + }); + //toastr.error(err); + setTimeout(() => clearSecondaryProgressData(), TIMEOUT); + }) + .finally(() => setIsLoading(false)); + } + } + }); + }; + render() { + const { showConvertDialog } = this.state; + const { + item, + canWebEdit, + isTrashFolder, + canConvert, + onFilesClick, // from withFileAction HOC + } = this.props; + const { fileStatus, access } = item; + + const newItems = item.new || fileStatus === 2; + const showNew = !!newItems; + + const accessToEdit = + access === ShareAccessRights.FullAccess || + access === ShareAccessRights.None; // TODO: fix access type for owner (now - None) + + const badgesComponent = ( + + ); + + return ( + <> + {showConvertDialog && ( + + )} + + + ); + } + } + + return inject( + ( + { + auth, + formatsStore, + treeFoldersStore, + filesActionsStore, + versionHistoryStore, + selectedFolderStore, + dialogsStore, + filesStore, + uploadDataStore, + }, + { item } + ) => { + const { docserviceStore } = formatsStore; + const { isRecycleBinFolder, updateRootBadge } = treeFoldersStore; + const { + lockFileAction, + setFavoriteAction, + markAsRead, + } = filesActionsStore; + const { isTabletView } = auth.settingsStore; + const { setIsVerHistoryPanel, fetchFileVersions } = versionHistoryStore; + const { setNewFilesPanelVisible, setNewFilesIds } = dialogsStore; + const { updateFileBadge, filter, setIsLoading, fetchFiles } = filesStore; + const { secondaryProgressDataStore } = uploadDataStore; + const { + setSecondaryProgressBarData, + clearSecondaryProgressData, + } = secondaryProgressDataStore; + + const canWebEdit = docserviceStore.canWebEdit(item.fileExst); + const canConvert = docserviceStore.canConvert(item.fileExst); + + return { + canWebEdit, + canConvert, + isTrashFolder: isRecycleBinFolder, + lockFileAction, + setFavoriteAction, + homepage: config.homepage, + isTabletView, + setIsVerHistoryPanel, + fetchFileVersions, + selectedFolderPathParts: selectedFolderStore.pathParts, + markAsRead, + setNewFilesPanelVisible, + setNewFilesIds, + updateRootBadge, + updateFileBadge, + setSecondaryProgressBarData, + selectedFolderId: selectedFolderStore.id, + filter, + setIsLoading, + clearSecondaryProgressData, + fetchFiles, + }; + } + )(observer(WithBadges)); +} diff --git a/products/ASC.Files/Client/src/HOCs/withContent.js b/products/ASC.Files/Client/src/HOCs/withContent.js new file mode 100644 index 0000000000..f453b200a0 --- /dev/null +++ b/products/ASC.Files/Client/src/HOCs/withContent.js @@ -0,0 +1,364 @@ +import React from "react"; +import { inject, observer } from "mobx-react"; +import { Trans } from "react-i18next"; +import { isMobile } from "react-device-detect"; + +import toastr from "studio/toastr"; +import { + AppServerConfig, + FileAction, + ShareAccessRights, +} from "@appserver/common/constants"; +import { combineUrl } from "@appserver/common/utils"; + +import config from "../../package.json"; +import EditingWrapperComponent from "../components/EditingWrapperComponent"; +import { getTitleWithoutExst } from "../helpers/files-helpers"; + +export default function withContent(WrappedContent) { + class WithContent extends React.Component { + constructor(props) { + super(props); + let titleWithoutExt = getTitleWithoutExst(props.item); + + if (props.fileActionId === -1) { + titleWithoutExt = this.getDefaultName(props.fileActionExt); + } + + this.state = { + itemTitle: titleWithoutExt, + + //loading: false + }; + } + + componentDidUpdate(prevProps) { + const { fileActionId, fileActionExt } = this.props; + if (fileActionId === -1 && fileActionExt !== prevProps.fileActionExt) { + const itemTitle = this.getDefaultName(fileActionExt); + this.setState({ itemTitle }); + } + // if (fileAction) { + // if (fileActionId !== prevProps.fileActionId) { + // this.setState({ editingId: fileActionId }); + // } + // } + } + + getDefaultName = (format) => { + const { t } = this.props; + + switch (format) { + case "docx": + return t("NewDocument"); + case "xlsx": + return t("NewSpreadsheet"); + case "pptx": + return t("NewPresentation"); + default: + return t("NewFolder"); + } + }; + + completeAction = (id) => { + const { editCompleteAction, item } = this.props; + + const isCancel = + (id.currentTarget && id.currentTarget.dataset.action === "cancel") || + id.keyCode === 27; + editCompleteAction(id, item, isCancel); + }; + + updateItem = () => { + const { + t, + updateFile, + renameFolder, + item, + setIsLoading, + fileActionId, + editCompleteAction, + } = this.props; + + const { itemTitle } = this.state; + const originalTitle = getTitleWithoutExst(item); + + setIsLoading(true); + const isSameTitle = + originalTitle.trim() === itemTitle.trim() || itemTitle.trim() === ""; + if (isSameTitle) { + this.setState({ + itemTitle: originalTitle, + }); + return editCompleteAction(fileActionId, item, isSameTitle); + } + + item.fileExst || item.contentLength + ? updateFile(fileActionId, itemTitle) + .then(() => this.completeAction(fileActionId)) + .then(() => + toastr.success( + t("FileRenamed", { + oldTitle: item.title, + newTitle: itemTitle + item.fileExst, + }) + ) + ) + .catch((err) => toastr.error(err)) + .finally(() => setIsLoading(false)) + : renameFolder(fileActionId, itemTitle) + .then(() => this.completeAction(fileActionId)) + .then(() => + toastr.success( + t("FolderRenamed", { + folderTitle: item.title, + newFoldedTitle: itemTitle, + }) + ) + ) + .catch((err) => toastr.error(err)) + .finally(() => setIsLoading(false)); + }; + + cancelUpdateItem = (e) => { + const { item } = this.props; + + const originalTitle = getTitleWithoutExst(item); + this.setState({ + itemTitle: originalTitle, + }); + + return this.completeAction(e); + }; + + onClickUpdateItem = (e) => { + const { fileActionType } = this.props; + + fileActionType === FileAction.Create + ? this.createItem(e) + : this.updateItem(e); + }; + + createItem = (e) => { + const { + createFile, + item, + setIsLoading, + openDocEditor, + isPrivacy, + isDesktop, + replaceFileStream, + t, + setEncryptionAccess, + createFolder, + } = this.props; + const { itemTitle } = this.state; + + setIsLoading(true); + + const itemId = e.currentTarget.dataset.itemid; + + if (itemTitle.trim() === "") { + toastr.warning(t("CreateWithEmptyTitle")); + return this.completeAction(itemId); + } + + let tab = + !isDesktop && item.fileExst + ? window.open( + combineUrl( + AppServerConfig.proxyURL, + config.homepage, + "/products/files/doceditor" + ), + "_blank" + ) + : null; + + !item.fileExst && !item.contentLength + ? createFolder(item.parentId, itemTitle) + .then(() => this.completeAction(itemId)) + .then(() => + toastr.success( + + New folder {{ itemTitle }} is created + + ) + ) + .catch((e) => toastr.error(e)) + .finally(() => { + return setIsLoading(false); + }) + : createFile(item.parentId, `${itemTitle}.${item.fileExst}`) + .then((file) => { + if (isPrivacy) { + return setEncryptionAccess(file).then((encryptedFile) => { + if (!encryptedFile) return Promise.resolve(); + toastr.info(t("EncryptedFileSaving")); + return replaceFileStream( + file.id, + encryptedFile, + true, + false + ).then(() => + openDocEditor(file.id, file.providerKey, tab, file.webUrl) + ); + }); + } + return openDocEditor(file.id, file.providerKey, tab, file.webUrl); + }) + .then(() => this.completeAction(itemId)) + .then(() => { + const exst = item.fileExst; + return toastr.success( + + New file {{ itemTitle }}.{{ exst }} is created + + ); + }) + .catch((e) => toastr.error(e)) + .finally(() => { + return setIsLoading(false); + }); + }; + + renameTitle = (e) => { + const { t } = this.props; + + let title = e.target.value; + //const chars = '*+:"<>?|/'; TODO: think how to solve problem with interpolation escape values in i18n translate + const regexp = new RegExp('[*+:"<>?|\\\\/]', "gim"); + if (title.match(regexp)) { + toastr.warning(t("ContainsSpecCharacter")); + } + title = title.replace(regexp, "_"); + return this.setState({ itemTitle: title }); + }; + + getStatusByDate = () => { + const { culture, t, item, sectionWidth } = this.props; + const { created, updated, version, fileExst } = item; + + const title = + version > 1 + ? t("TitleModified") + : fileExst + ? t("TitleUploaded") + : t("TitleCreated"); + + const date = fileExst ? updated : created; + const dateLabel = new Date(date).toLocaleString(culture); + const mobile = (sectionWidth && sectionWidth <= 375) || isMobile; + + return mobile ? dateLabel : `${title}: ${dateLabel}`; + }; + + render() { + const { itemTitle } = this.state; + const { + item, + fileActionId, + fileActionExt, + isLoading, + viewer, + t, + isTrashFolder, + onFilesClick, + } = this.props; + const { id, fileExst, updated, createdBy, access, fileStatus } = item; + + const titleWithoutExt = getTitleWithoutExst(item); + + const isEdit = id === fileActionId && fileExst === fileActionExt; + + const updatedDate = updated && this.getStatusByDate(); + + const fileOwner = + createdBy && + ((viewer.id === createdBy.id && t("AuthorMe")) || + createdBy.displayName); + + const accessToEdit = + access === ShareAccessRights.FullAccess || // only badges? + access === ShareAccessRights.None; // TODO: fix access type for owner (now - None) + + const linkStyles = isTrashFolder //|| window.innerWidth <= 1024 + ? { noHover: true } + : { onClick: onFilesClick }; + + const newItems = item.new || fileStatus === 2; + const showNew = !!newItems; + + return isEdit ? ( + + ) : ( + + ); + } + } + + return inject( + ({ filesActionsStore, filesStore, treeFoldersStore, auth }, {}) => { + const { editCompleteAction } = filesActionsStore; + const { + setIsLoading, + openDocEditor, + updateFile, + renameFolder, + createFile, + createFolder, + isLoading, + } = filesStore; + const { isRecycleBinFolder, isPrivacyFolder } = treeFoldersStore; + + const { + type: fileActionType, + extension: fileActionExt, + id: fileActionId, + } = filesStore.fileActionStore; + const { replaceFileStream, setEncryptionAccess } = auth; + const { culture, isDesktopClient } = auth.settingsStore; + + return { + editCompleteAction, + setIsLoading, + isTrashFolder: isRecycleBinFolder, + openDocEditor, + updateFile, + renameFolder, + fileActionId, + editCompleteAction, + fileActionType, + createFile, + isPrivacy: isPrivacyFolder, + isDesktop: isDesktopClient, + replaceFileStream, + setEncryptionAccess, + createFolder, + fileActionExt, + isLoading, + culture, + homepage: config.homepage, + viewer: auth.userStore.user, + }; + } + )(observer(WithContent)); +} diff --git a/products/ASC.Files/Client/src/HOCs/withContextOptions.js b/products/ASC.Files/Client/src/HOCs/withContextOptions.js new file mode 100644 index 0000000000..8ec66f6c33 --- /dev/null +++ b/products/ASC.Files/Client/src/HOCs/withContextOptions.js @@ -0,0 +1,503 @@ +import React from "react"; +import { inject, observer } from "mobx-react"; +import copy from "copy-to-clipboard"; + +import { combineUrl } from "@appserver/common/utils"; +import { FileAction, AppServerConfig } from "@appserver/common/constants"; +import toastr from "studio/toastr"; + +import config from "../../package.json"; + +export default function withContextOptions(WrappedComponent) { + class WithContextOptions extends React.Component { + onOpenLocation = () => { + const { item, openLocationAction } = this.props; + const { id, folderId, fileExst } = item; + + const locationId = !fileExst ? id : folderId; + openLocationAction(locationId, !fileExst); + }; + + onOwnerChange = () => { + const { setChangeOwnerPanelVisible } = this.props; + setChangeOwnerPanelVisible(true); + }; + onMoveAction = () => { + const { setMoveToPanelVisible } = this.props; + setMoveToPanelVisible(true); + }; + onCopyAction = () => { + const { setCopyPanelVisible } = this.props; + setCopyPanelVisible(true); + }; + + showVersionHistory = () => { + const { + item, + isTabletView, + fetchFileVersions, + setIsVerHistoryPanel, + history, + homepage, + isTrashFolder, + } = this.props; + const { id } = item; + if (isTrashFolder) return; + + if (!isTabletView) { + fetchFileVersions(id + ""); + setIsVerHistoryPanel(true); + } else { + history.push( + combineUrl(AppServerConfig.proxyURL, homepage, `/${id}/history`) + ); + } + }; + + finalizeVersion = () => { + const { item, finalizeVersionAction } = this.props; + const { id } = item; + finalizeVersionAction(id).catch((err) => toastr.error(err)); + }; + + onClickFavorite = (e) => { + const { item, setFavoriteAction, t } = this.props; + const { id } = item; + const data = (e.currentTarget && e.currentTarget.dataset) || e; + const { action } = data; + + setFavoriteAction(action, id) + .then(() => + action === "mark" + ? toastr.success(t("MarkedAsFavorite")) + : toastr.success(t("RemovedFromFavorites")) + ) + .catch((err) => toastr.error(err)); + }; + + lockFile = () => { + const { item, lockFileAction } = this.props; + const { id, locked } = item; + lockFileAction(id, !locked).catch((err) => toastr.error(err)); + }; + + onClickLinkForPortal = () => { + const { item, homepage, t } = this.props; + const { fileExst, canOpenPlayer, webUrl } = item; + + const isFile = !!fileExst; + copy( + isFile + ? canOpenPlayer + ? `${window.location.href}&preview=${id}` + : webUrl + : `${window.location.origin + homepage}/filter?folder=${id}` + ); + + toastr.success(t("LinkCopySuccess")); + }; + + onClickLinkEdit = () => { + const { item, openDocEditor } = this.props; + const { id, providerKey } = item; + openDocEditor(id, providerKey); + }; + + onClickDownload = () => { + const { item, downloadAction, t } = this.props; + const { fileExst, contentLength, viewUrl } = item; + const isFile = !!fileExst && contentLength; + isFile + ? window.open(viewUrl, "_blank") + : downloadAction(t("ArchivingData")).catch((err) => toastr.error(err)); + }; + + onClickDownloadAs = () => { + const { setDownloadDialogVisible } = this.props; + setDownloadDialogVisible(true); + }; + + onDuplicate = () => { + const { duplicateAction, t, item } = this.props; + duplicateAction(item, t("CopyOperation")).catch((err) => + toastr.error(err) + ); + }; + + onClickRename = () => { + const { item, setAction } = this.props; + const { id, fileExst } = item; + setAction({ + type: FileAction.Rename, + extension: fileExst, + id, + }); + }; + + onChangeThirdPartyInfo = () => { + const { item, setThirdpartyInfo } = this.props; + const { providerKey } = item; + setThirdpartyInfo(providerKey); + }; + + onMediaFileClick = (fileId) => { + const { item, setMediaViewerData } = this.props; + const itemId = typeof fileId !== "object" ? fileId : item.id; + setMediaViewerData({ visible: true, id: itemId }); + }; + + onClickDelete = () => { + const { + item, + setRemoveItem, + setDeleteThirdPartyDialogVisible, + confirmDelete, + setDeleteDialogVisible, + t, + deleteFileAction, + deleteFolderAction, + isThirdPartyFolder, + } = this.props; + const { id, title, fileExst, contentLength, folderId, parentId } = item; + + if (isThirdPartyFolder) { + const splitItem = id.split("-"); + setRemoveItem({ id: splitItem[splitItem.length - 1], title }); + setDeleteThirdPartyDialogVisible(true); + return; + } + + if (confirmDelete) { + setDeleteDialogVisible(true); + } else { + const translations = { + deleteOperation: t("DeleteOperation"), + }; + + fileExst || contentLength + ? deleteFileAction(id, folderId, translations) + .then(() => toastr.success(t("FileRemoved"))) + .catch((err) => toastr.error(err)) + : deleteFolderAction(id, parentId, translations) + .then(() => toastr.success(t("FolderRemoved"))) + .catch((err) => toastr.error(err)); + } + }; + + onClickShare = () => { + const { onSelectItem, setSharingPanelVisible, item } = this.props; + onSelectItem(item); + setSharingPanelVisible(true); + }; + + getFilesContextOptions = () => { + const { item, t, isThirdPartyFolder } = this.props; + const { access, contextOptions } = item; + const isSharable = access !== 1 && access !== 0; + return contextOptions.map((option) => { + switch (option) { + case "open": + return { + key: option, + label: t("Open"), + icon: "images/catalog.folder.react.svg", + onClick: this.onOpenLocation, + disabled: false, + }; + case "show-version-history": + return { + key: option, + label: t("ShowVersionHistory"), + icon: "images/history.react.svg", + onClick: this.showVersionHistory, + disabled: false, + }; + case "finalize-version": + return { + key: option, + label: t("FinalizeVersion"), + icon: "images/history-finalized.react.svg", + onClick: this.finalizeVersion, + disabled: false, + }; + case "separator0": + case "separator1": + case "separator2": + case "separator3": + return { key: option, isSeparator: true }; + case "open-location": + return { + key: option, + label: t("OpenLocation"), + icon: "images/download-as.react.svg", + onClick: this.onOpenLocation, + disabled: false, + }; + case "mark-as-favorite": + return { + key: option, + label: t("MarkAsFavorite"), + icon: "images/favorites.react.svg", + onClick: this.onClickFavorite, + disabled: false, + "data-action": "mark", + action: "mark", + }; + case "block-unblock-version": + return { + key: option, + label: t("UnblockVersion"), + icon: "images/lock.react.svg", + onClick: this.lockFile, + disabled: false, + }; + case "sharing-settings": + return { + key: option, + label: t("SharingSettings"), + icon: "images/catalog.shared.react.svg", + onClick: this.onClickShare, + disabled: isSharable, + }; + case "send-by-email": + return { + key: option, + label: t("SendByEmail"), + icon: "/static/images/mail.react.svg", + disabled: true, + }; + case "owner-change": + return { + key: option, + label: t("ChangeOwner"), + icon: "images/catalog.user.react.svg", + onClick: this.onOwnerChange, + disabled: false, + }; + case "link-for-portal-users": + return { + key: option, + label: t("LinkForPortalUsers"), + icon: "/static/images/invitation.link.react.svg", + onClick: this.onClickLinkForPortal, + disabled: false, + }; + case "edit": + return { + key: option, + label: t("Edit"), + icon: "/static/images/access.edit.react.svg", + onClick: this.onClickLinkEdit, + disabled: false, + }; + case "preview": + return { + key: option, + label: t("Preview"), + icon: "EyeIcon", + onClick: this.onClickLinkEdit, + disabled: true, + }; + case "view": + return { + key: option, + label: t("View"), + icon: "/static/images/eye.react.svg", + onClick: this.onMediaFileClick, + disabled: false, + }; + case "download": + return { + key: option, + label: t("Download"), + icon: "images/download.react.svg", + onClick: this.onClickDownload, + disabled: false, + }; + case "download-as": + return { + key: option, + label: t("DownloadAs"), + icon: "images/download-as.react.svg", + onClick: this.onClickDownloadAs, + disabled: false, + }; + case "move-to": + return { + key: option, + label: t("MoveTo"), + icon: "images/move.react.svg", + onClick: this.onMoveAction, + disabled: false, + }; + case "restore": + return { + key: option, + label: t("Restore"), + icon: "images/move.react.svg", + onClick: this.onMoveAction, + disabled: false, + }; + case "copy-to": + return { + key: option, + label: t("Copy"), + icon: "/static/images/copy.react.svg", + onClick: this.onCopyAction, + disabled: false, + }; + case "copy": + return { + key: option, + label: t("Duplicate"), + icon: "/static/images/copy.react.svg", + onClick: this.onDuplicate, + disabled: false, + }; + case "rename": + return { + key: option, + label: t("Rename"), + icon: "images/rename.react.svg", + onClick: this.onClickRename, + disabled: false, + }; + case "change-thirdparty-info": + return { + key: option, + label: t("ThirdPartyInfo"), + icon: "/static/images/access.edit.react.svg", + onClick: this.onChangeThirdPartyInfo, + disabled: false, + }; + case "delete": + return { + key: option, + label: isThirdPartyFolder ? t("DeleteThirdParty") : t("Delete"), + icon: "/static/images/catalog.trash.react.svg", + onClick: this.onClickDelete, + disabled: false, + }; + case "remove-from-favorites": + return { + key: option, + label: t("RemoveFromFavorites"), + icon: "images/favorites.react.svg", + onClick: this.onClickFavorite, + disabled: false, + "data-action": "remove", + action: "remove", + }; + default: + break; + } + + return undefined; + }); + }; + render() { + const { actionType, actionId, actionExtension, item } = this.props; + const { id, fileExst, contextOptions } = item; + + const isEdit = + !!actionType && actionId === id && fileExst === actionExtension; + + const contextOptionsProps = + !isEdit && contextOptions && contextOptions.length > 0 + ? { + contextOptions: this.getFilesContextOptions(), + } + : {}; + + return ( + + ); + } + } + + return inject( + ( + { + filesStore, + filesActionsStore, + auth, + versionHistoryStore, + mediaViewerDataStore, + settingsStore, + selectedFolderStore, + dialogsStore, + treeFoldersStore, + }, + { item } + ) => { + const { openDocEditor, fileActionStore } = filesStore; + const { + openLocationAction, + finalizeVersionAction, + setFavoriteAction, + lockFileAction, + downloadAction, + duplicateAction, + setThirdpartyInfo, + deleteFileAction, + deleteFolderAction, + onSelectItem, + } = filesActionsStore; + const { + setChangeOwnerPanelVisible, + setMoveToPanelVisible, + setCopyPanelVisible, + setDownloadDialogVisible, + setRemoveItem, + setDeleteThirdPartyDialogVisible, + setDeleteDialogVisible, + setSharingPanelVisible, + } = dialogsStore; + const { isTabletView } = auth.settingsStore; + const { setIsVerHistoryPanel, fetchFileVersions } = versionHistoryStore; + const { setAction, type, extension, id } = fileActionStore; + const { setMediaViewerData } = mediaViewerDataStore; + const { isRootFolder } = selectedFolderStore; + const { isRecycleBinFolder } = treeFoldersStore; + + const isThirdPartyFolder = item.providerKey && isRootFolder; + + return { + openLocationAction, + setChangeOwnerPanelVisible, + setMoveToPanelVisible, + setCopyPanelVisible, + isTabletView, + setIsVerHistoryPanel, + fetchFileVersions, + homepage: config.homepage, + finalizeVersionAction, + setFavoriteAction, + lockFileAction, + openDocEditor, + downloadAction, + setDownloadDialogVisible, + duplicateAction, + setAction, + setThirdpartyInfo, + setMediaViewerData, + setRemoveItem, + setDeleteThirdPartyDialogVisible, + confirmDelete: settingsStore.confirmDelete, + setDeleteDialogVisible, + deleteFileAction, + deleteFolderAction, + isThirdPartyFolder, + onSelectItem, + setSharingPanelVisible, + actionType: type, + actionId: id, + actionExtension: extension, + isTrashFolder: isRecycleBinFolder, + }; + } + )(observer(WithContextOptions)); +} diff --git a/products/ASC.Files/Client/src/HOCs/withFileActions.js b/products/ASC.Files/Client/src/HOCs/withFileActions.js new file mode 100644 index 0000000000..08f5d8f870 --- /dev/null +++ b/products/ASC.Files/Client/src/HOCs/withFileActions.js @@ -0,0 +1,339 @@ +import React from "react"; +import { inject, observer } from "mobx-react"; +import { ReactSVG } from "react-svg"; + +import IconButton from "@appserver/components/icon-button"; +import Text from "@appserver/components/text"; + +import { EncryptedFileIcon } from "../components/Icons"; + +const svgLoader = () =>
; +export default function withFileActions(WrappedFileItem) { + class WithFileActions extends React.Component { + onContentRowSelect = (checked, file) => { + const { selectRowAction } = this.props; + if (!file) return; + selectRowAction(checked, file); + }; + + onClickShare = () => { + const { onSelectItem, setSharingPanelVisible, item } = this.props; + onSelectItem(item); + setSharingPanelVisible(true); + }; + + rowContextClick = () => { + const { onSelectItem, item } = this.props; + onSelectItem(item); + }; + + getSharedButton = (shared) => { + const { t } = this.props; + const color = shared ? "#657077" : "#a3a9ae"; + return ( + + + {t("Share")} + + ); + }; + + getItemIcon = (isEdit) => { + const { item, isPrivacy } = this.props; + const { icon, fileExst } = item; + return ( + <> + + {isPrivacy && fileExst && } + + ); + }; + + onDropZoneUpload = (files, uploadToFolder) => { + const { + selectedFolderId, + dragging, + setDragging, + startUpload, + } = this.props; + + const folderId = uploadToFolder ? uploadToFolder : selectedFolderId; + dragging && setDragging(false); + startUpload(files, folderId, t); + }; + + onDrop = (items) => { + const { item, selectedFolderId } = this.props; + const { fileExst, id } = item; + + if (!fileExst) { + this.onDropZoneUpload(items, id); + } else { + this.onDropZoneUpload(items, selectedFolderId); + } + }; + + onMouseDown = (e) => { + const { draggable, setTooltipPosition, setStartDrag } = this.props; + if (!draggable) { + return; + } + + if ( + window.innerWidth < 1025 || + e.target.tagName === "rect" || + e.target.tagName === "path" + ) { + return; + } + const mouseButton = e.which + ? e.which !== 1 + : e.button + ? e.button !== 0 + : false; + const label = e.currentTarget.getAttribute("label"); + if (mouseButton || e.currentTarget.tagName !== "DIV" || label) { + return; + } + + setTooltipPosition(e.pageX, e.pageY); + setStartDrag(true); + }; + + onFilesClick = () => { + const { + filter, + parentFolder, + setIsLoading, + fetchFiles, + isImage, + isSound, + isVideo, + canWebEdit, + item, + isTrashFolder, + openDocEditor, + expandedKeys, + addExpandedKeys, + setMediaViewerData, + } = this.props; + const { id, fileExst, viewUrl, providerKey, contentLength } = item; + + if (isTrashFolder) return; + + if (!fileExst && !contentLength) { + setIsLoading(true); + + if (!expandedKeys.includes(parentFolder + "")) { + addExpandedKeys(parentFolder + ""); + } + + fetchFiles(id, filter) + .catch((err) => { + toastr.error(err); + setIsLoading(false); + }) + .finally(() => setIsLoading(false)); + } else { + if (canWebEdit) { + return openDocEditor(id, providerKey); + } + + if (isImage || isSound || isVideo) { + setMediaViewerData({ visible: true, id }); + return; + } + + return window.open(viewUrl, "_blank"); + } + }; + + render() { + const { + item, + isRecycleBin, + draggable, + canShare, + isPrivacy, + actionType, + actionExtension, + actionId, + sectionWidth, + checked, + dragging, + isFolder, + } = this.props; + const { fileExst, access, contentLength, id, shared } = item; + + const isEdit = + !!actionType && actionId === id && fileExst === actionExtension; + + const isDragging = isFolder && access < 2 && !isRecycleBin; + + let className = isDragging ? " droppable" : ""; + if (draggable) className += " draggable not-selectable"; + + let value = fileExst || contentLength ? `file_${id}` : `folder_${id}`; + value += draggable ? "_draggable" : ""; + + const isMobile = sectionWidth < 500; + const displayShareButton = isMobile + ? "26px" + : !canShare + ? "38px" + : "96px"; + + const sharedButton = + !canShare || (isPrivacy && !fileExst) || isEdit || id <= 0 || isMobile + ? null + : this.getSharedButton(shared); + + const checkedProps = isEdit || id <= 0 ? {} : { checked }; + const element = this.getItemIcon(isEdit || id <= 0); + + return ( + + ); + } + } + + return inject( + ( + { + filesActionsStore, + dialogsStore, + treeFoldersStore, + selectedFolderStore, + filesStore, + uploadDataStore, + formatsStore, + mediaViewerDataStore, + }, + { item, t, history } + ) => { + const { selectRowAction, onSelectItem } = filesActionsStore; + const { setSharingPanelVisible } = dialogsStore; + const { + isPrivacyFolder, + isRecycleBinFolder, + expandedKeys, + addExpandedKeys, + } = treeFoldersStore; + const { id: selectedFolderId, isRootFolder } = selectedFolderStore; + const { + dragging, + setDragging, + selection, + setTooltipPosition, + setStartDrag, + fileActionStore, + canShare, + isFileSelected, + filter, + setIsLoading, + fetchFiles, + openDocEditor, + } = filesStore; + const { startUpload } = uploadDataStore; + const { type, extension, id } = fileActionStore; + const { + iconFormatsStore, + mediaViewersFormatsStore, + docserviceStore, + } = formatsStore; + const { setMediaViewerData } = mediaViewerDataStore; + + const selectedItem = selection.find( + (x) => x.id === item.id && x.fileExst === item.fileExst + ); + + const draggable = + !isRecycleBinFolder && selectedItem && selectedItem.id !== id; + + const isFolder = selectedItem + ? false + : item.fileExst //|| item.contentLength + ? false + : true; + + const isImage = iconFormatsStore.isImage(item.fileExst); + const isSound = iconFormatsStore.isSound(item.fileExst); + const isVideo = mediaViewersFormatsStore.isVideo(item.fileExst); + const canWebEdit = docserviceStore.canWebEdit(item.fileExst); + + return { + t, + item, + selectRowAction, + onSelectItem, + setSharingPanelVisible, + isPrivacy: isPrivacyFolder, + selectedFolderId, + dragging, + setDragging, + startUpload, + draggable, + setTooltipPosition, + setStartDrag, + history, + isFolder, + isRootFolder, + canShare, + actionType: type, + actionExtension: extension, + actionId: id, + checked: isFileSelected(item.id, item.parentId), + filter, + parentFolder: selectedFolderStore.parentId, + setIsLoading, + fetchFiles, + isImage, + isSound, + isVideo, + canWebEdit, + isTrashFolder: isRecycleBinFolder, + openDocEditor, + expandedKeys, + addExpandedKeys, + setMediaViewerData, + }; + } + )(observer(WithFileActions)); +} diff --git a/products/ASC.Files/Client/src/HOCs/withLoader.js b/products/ASC.Files/Client/src/HOCs/withLoader.js new file mode 100644 index 0000000000..669617e745 --- /dev/null +++ b/products/ASC.Files/Client/src/HOCs/withLoader.js @@ -0,0 +1,51 @@ +import React, { useEffect, useState } from "react"; +import { observer, inject } from "mobx-react"; +import { isMobile } from "react-device-detect"; + +import Loaders from "@appserver/common/components/Loaders"; + +let loadTimeout = null; +export default function withLoader(WrappedComponent, type) { + const withLoader = (props) => { + const { tReady, firstLoad, isLoaded, isLoading } = props; + const [inLoad, setInLoad] = useState(false); + + const cleanTimer = () => { + loadTimeout && clearTimeout(loadTimeout); + loadTimeout = null; + }; + + useEffect(() => { + if (isLoading) { + cleanTimer(); + loadTimeout = setTimeout(() => { + console.log("inLoad", true); + setInLoad(true); + }, 500); + } else { + cleanTimer(); + console.log("inLoad", false); + setInLoad(false); + } + + return () => { + cleanTimer(); + }; + }, [isLoading]); + + return firstLoad || !isLoaded || (isMobile && inLoad) || !tReady ? ( + + ) : ( + + ); + }; + + return inject(({ auth, filesStore }) => { + const { firstLoad, isLoading } = filesStore; + return { + firstLoad, + isLoaded: auth.isLoaded, + isLoading, + }; + })(observer(withLoader)); +} diff --git a/products/ASC.Files/Client/src/components/Article/Body/ThirdPartyList.js b/products/ASC.Files/Client/src/components/Article/Body/ThirdPartyList.js index 17782ace79..03564bd4ba 100644 --- a/products/ASC.Files/Client/src/components/Article/Body/ThirdPartyList.js +++ b/products/ASC.Files/Client/src/components/Article/Body/ThirdPartyList.js @@ -98,11 +98,12 @@ const PureThirdPartyListContainer = ({ nextCloudConnectItem, webDavConnectItem, setConnectItem, - setThirdPartyDialogVisible, + setConnectDialogVisible, setSelectedNode, setSelectedFolder, getOAuthToken, openConnectWindow, + setThirdPartyDialogVisible, history, }) => { const redirectAction = () => { @@ -125,8 +126,10 @@ const PureThirdPartyListContainer = ({ "Authorization", "height=600, width=1020" ); - openConnectWindow(data.title, authModal).then((modal) => + openConnectWindow(data.title, authModal).then((modal) => { + redirectAction(); getOAuthToken(modal).then((token) => { + authModal.close(); const serviceData = { title: data.title, provider_key: data.title, @@ -134,17 +137,17 @@ const PureThirdPartyListContainer = ({ token, }; setConnectItem(serviceData); - }) - ); + setConnectDialogVisible(true); + }); + }); } else { setConnectItem(data); + setConnectDialogVisible(true); + redirectAction(); } - - onShowConnectPanel(); }; const onShowConnectPanel = () => { - //setThirdPartyDialogVisible((prev) => !prev); TODO: setThirdPartyDialogVisible(true); redirectAction(); }; @@ -214,6 +217,7 @@ const ThirdPartyList = withTranslation("Article")( export default inject( ({ filesStore, + auth, settingsStore, treeFoldersStore, selectedFolderStore, @@ -229,12 +233,16 @@ export default inject( oneDriveConnectItem, nextCloudConnectItem, webDavConnectItem, - getOAuthToken, openConnectWindow, } = settingsStore.thirdPartyStore; - const { setConnectItem, setThirdPartyDialogVisible } = dialogsStore; + const { getOAuthToken } = auth.settingsStore; + const { + setConnectItem, + setConnectDialogVisible, + setThirdPartyDialogVisible, + } = dialogsStore; return { googleConnectItem, boxConnectItem, @@ -247,9 +255,10 @@ export default inject( setSelectedFolder, setSelectedNode, setConnectItem, - setThirdPartyDialogVisible, + setConnectDialogVisible, getOAuthToken, openConnectWindow, + setThirdPartyDialogVisible, }; } )(observer(ThirdPartyList)); diff --git a/products/ASC.Files/Client/src/components/Article/Body/TreeFolders.js b/products/ASC.Files/Client/src/components/Article/Body/TreeFolders.js index 667b725775..040cf2be2c 100644 --- a/products/ASC.Files/Client/src/components/Article/Body/TreeFolders.js +++ b/products/ASC.Files/Client/src/components/Article/Body/TreeFolders.js @@ -364,8 +364,7 @@ class TreeFolders extends React.Component { const treeData = [...this.props.treeFolders]; this.getNewTreeData(treeData, listIds, data.folders, 10); - this.props.needUpdate && this.props.setTreeFolders(treeData); - //this.setState({ treeData }); + /* this.props.needUpdate && */ this.props.setTreeFolders(treeData); }) .catch((err) => toastr.error(err)) .finally(() => { @@ -374,15 +373,16 @@ class TreeFolders extends React.Component { }); }; - onExpand = (data, treeNode) => { + onExpand = (expandedKeys, treeNode) => { if (treeNode.node && !treeNode.node.props.children) { if (treeNode.expanded) { this.onLoadData(treeNode.node, true); } } if (this.props.needUpdate) { - const expandedKeys = data; this.props.setExpandedKeys(expandedKeys); + } else { + this.props.setExpandedPanelKeys(expandedKeys); } }; @@ -431,9 +431,10 @@ class TreeFolders extends React.Component { onSelect, dragging, expandedKeys, + expandedPanelKeys, treeFolders, + data, } = this.props; - //const loadProp = needUpdate ? { loadData: this.onLoadData } : {}; return ( - {this.getItems(treeFolders)} + {this.getItems(data || treeFolders)} ); } @@ -488,6 +488,7 @@ export default inject( isPrivacyFolder, expandedKeys, setExpandedKeys, + setExpandedPanelKeys, } = treeFoldersStore; const { id /* rootFolderType */ } = selectedFolderStore; @@ -510,6 +511,7 @@ export default inject( setTreeFolders, setFilter, setExpandedKeys, + setExpandedPanelKeys, }; } )(observer(TreeFolders)); diff --git a/products/ASC.Files/Client/src/components/Article/Body/TreeSettings.js b/products/ASC.Files/Client/src/components/Article/Body/TreeSettings.js index 2db7a2dbcf..8a0125a8dd 100644 --- a/products/ASC.Files/Client/src/components/Article/Body/TreeSettings.js +++ b/products/ASC.Files/Client/src/components/Article/Body/TreeSettings.js @@ -81,6 +81,7 @@ const PureTreeSettings = ({ history, setIsLoading, t, + isVisitor, }) => { const { setting } = match.params; @@ -168,7 +169,7 @@ const PureTreeSettings = ({ title={t("TreeSettingsAdminSettings")} /> ) : null} - {enableThirdParty ? ( + {enableThirdParty && !isVisitor ? ( 0) { fetchFiles(data[0], newFilter) .catch((err) => toastr.error(err)) - .finally(() => setIsLoading(false)); + .finally(() => { + setIsLoading(false); + }); } else { newFilter.startIndex = 0; const urlFilter = newFilter.toUrlParams(); @@ -77,7 +80,13 @@ class ArticleBodyContent extends React.Component { }; render() { - const { treeFolders, onTreeDrop, selectedTreeNode } = this.props; + const { + treeFolders, + onTreeDrop, + selectedTreeNode, + enableThirdParty, + isVisitor, + } = this.props; return isEmpty(treeFolders) ? ( @@ -91,14 +100,21 @@ class ArticleBodyContent extends React.Component { onTreeDrop={onTreeDrop} /> - + {enableThirdParty && !isVisitor && } ); } } export default inject( - ({ filesStore, treeFoldersStore, selectedFolderStore, dialogsStore }) => { + ({ + auth, + filesStore, + treeFoldersStore, + selectedFolderStore, + dialogsStore, + settingsStore, + }) => { const { fetchFiles, filter, setIsLoading } = filesStore; const { treeFolders, setSelectedNode, setTreeFolders } = treeFoldersStore; const selectedTreeNode = @@ -114,6 +130,8 @@ export default inject( treeFolders, selectedTreeNode, filter, + enableThirdParty: settingsStore.enableThirdParty, + isVisitor: auth.userStore.user.isVisitor, setIsLoading, fetchFiles, diff --git a/products/ASC.Files/Client/src/components/Badges.js b/products/ASC.Files/Client/src/components/Badges.js new file mode 100644 index 0000000000..80466b3893 --- /dev/null +++ b/products/ASC.Files/Client/src/components/Badges.js @@ -0,0 +1,123 @@ +import React from "react"; +import Badge from "@appserver/components/badge"; +import IconButton from "@appserver/components/icon-button"; +import { + StyledFavoriteIcon, + StyledFileActionsConvertEditDocIcon, + StyledFileActionsLockedIcon, +} from "./Icons"; + +const Badges = ({ + newItems, + item, + canWebEdit, + isTrashFolder, + /* canConvert, */ + accessToEdit, + showNew, + onFilesClick, + onClickLock, + onClickFavorite, + onShowVersionHistory, + onBadgeClick, + /*setConvertDialogVisible*/ +}) => { + const { id, locked, fileStatus, versionGroup, title, fileExst } = item; + + return fileExst ? ( +
+ {/* TODO: Uncomment after fix conversation {canConvert && !isTrashFolder && ( + + )} */} + {canWebEdit && !isTrashFolder && accessToEdit && ( + + )} + {locked && accessToEdit && ( + + )} + {fileStatus === 32 && !isTrashFolder && ( + + )} + {fileStatus === 1 && ( + + )} + {versionGroup > 1 && ( + + )} + {showNew && ( + + )} +
+ ) : ( + showNew && ( + + ) + ); +}; + +export default Badges; diff --git a/products/ASC.Files/Client/src/components/pages/Home/Section/Body/EditingWrapperComponent.js b/products/ASC.Files/Client/src/components/EditingWrapperComponent.js similarity index 79% rename from products/ASC.Files/Client/src/components/pages/Home/Section/Body/EditingWrapperComponent.js rename to products/ASC.Files/Client/src/components/EditingWrapperComponent.js index 8570815153..6ac5d79df7 100644 --- a/products/ASC.Files/Client/src/components/pages/Home/Section/Body/EditingWrapperComponent.js +++ b/products/ASC.Files/Client/src/components/EditingWrapperComponent.js @@ -1,7 +1,36 @@ -import React, { memo, useState } from "react"; +import React, { useState } from "react"; import styled from "styled-components"; import Button from "@appserver/components/button"; import TextInput from "@appserver/components/text-input"; +import commonIconsStyles from "@appserver/components/utils/common-icons-style"; + +import CheckIcon from "../../public/images/check.react.svg"; +import CrossIcon from "../../../../../public/images/cross.react.svg"; + +const StyledCheckIcon = styled(CheckIcon)` + ${commonIconsStyles} + path { + fill: #a3a9ae; + } + :hover { + fill: #657077; + } +`; + +const StyledCrossIcon = styled(CrossIcon)` + ${commonIconsStyles} + path { + fill: #a3a9ae; + } + :hover { + fill: #657077; + } +`; + +export const okIcon = ; +export const cancelIcon = ( + +); const EditingWrapper = styled.div` width: 100%; @@ -49,8 +78,6 @@ const EditingWrapperComponent = (props) => { const { itemTitle, itemId, - okIcon, - cancelIcon, renameTitle, onClickUpdateItem, cancelUpdateItem, @@ -125,4 +152,4 @@ const EditingWrapperComponent = (props) => { ); }; -export default memo(EditingWrapperComponent); +export default EditingWrapperComponent; diff --git a/products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/EmptyContainer.js b/products/ASC.Files/Client/src/components/EmptyContainer/EmptyContainer.js similarity index 100% rename from products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/EmptyContainer.js rename to products/ASC.Files/Client/src/components/EmptyContainer/EmptyContainer.js diff --git a/products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/EmptyFilterContainer.js b/products/ASC.Files/Client/src/components/EmptyContainer/EmptyFilterContainer.js similarity index 100% rename from products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/EmptyFilterContainer.js rename to products/ASC.Files/Client/src/components/EmptyContainer/EmptyFilterContainer.js diff --git a/products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/EmptyFolderContainer.js b/products/ASC.Files/Client/src/components/EmptyContainer/EmptyFolderContainer.js similarity index 100% rename from products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/EmptyFolderContainer.js rename to products/ASC.Files/Client/src/components/EmptyContainer/EmptyFolderContainer.js diff --git a/products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/RootFolderContainer.js b/products/ASC.Files/Client/src/components/EmptyContainer/RootFolderContainer.js similarity index 100% rename from products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/RootFolderContainer.js rename to products/ASC.Files/Client/src/components/EmptyContainer/RootFolderContainer.js diff --git a/products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/index.js b/products/ASC.Files/Client/src/components/EmptyContainer/index.js similarity index 100% rename from products/ASC.Files/Client/src/components/pages/Home/Section/Body/EmptyContainer/index.js rename to products/ASC.Files/Client/src/components/EmptyContainer/index.js diff --git a/products/ASC.Files/Client/src/components/FilesPanels/index.js b/products/ASC.Files/Client/src/components/FilesPanels/index.js index 26aabcc7cf..2db0a47bef 100644 --- a/products/ASC.Files/Client/src/components/FilesPanels/index.js +++ b/products/ASC.Files/Client/src/components/FilesPanels/index.js @@ -16,6 +16,7 @@ import { DeleteDialog, DownloadDialog, ThirdPartyDialog, + ConflictResolveDialog, } from "../dialogs"; const Panels = (props) => { @@ -34,6 +35,7 @@ const Panels = (props) => { emptyTrashDialogVisible, thirdPartyDialogVisible, newFilesPanelVisible, + conflictResolveDialogVisible, } = props; return [ @@ -63,6 +65,9 @@ const Panels = (props) => { downloadDialogVisible && , thirdPartyDialogVisible && , newFilesPanelVisible && , + conflictResolveDialogVisible && ( + + ), ]; }; @@ -81,6 +86,7 @@ export default inject( emptyTrashDialogVisible, thirdPartyDialogVisible, newFilesPanelVisible, + conflictResolveDialogVisible, connectItem, //TODO: } = dialogsStore; @@ -103,6 +109,7 @@ export default inject( emptyTrashDialogVisible, thirdPartyDialogVisible, newFilesPanelVisible, + conflictResolveDialogVisible, }; } )(observer(Panels)); diff --git a/products/ASC.Files/Client/src/components/Icons.js b/products/ASC.Files/Client/src/components/Icons.js new file mode 100644 index 0000000000..3e9538e4bf --- /dev/null +++ b/products/ASC.Files/Client/src/components/Icons.js @@ -0,0 +1,36 @@ +import styled from "styled-components"; + +import commonIconsStyles from "@appserver/components/utils/common-icons-style"; + +import FavoriteIcon from "../../public/images/favorite.react.svg"; +import FileActionsConvertEditDocIcon from "../../public/images/file.actions.convert.edit.doc.react.svg"; +import FileActionsLockedIcon from "../../public/images/file.actions.locked.react.svg"; + +export const EncryptedFileIcon = styled.div` + background: url("images/security.svg") no-repeat 0 0 / 16px 16px transparent; + height: 16px; + position: absolute; + width: 16px; + margin-top: 14px; + margin-left: ${(props) => (props.isEdit ? "40px" : "12px")}; +`; + +export const StyledFavoriteIcon = styled(FavoriteIcon)` + ${commonIconsStyles} +`; + +export const StyledFileActionsConvertEditDocIcon = styled( + FileActionsConvertEditDocIcon +)` + ${commonIconsStyles} + path { + fill: #3b72a7; + } +`; + +export const StyledFileActionsLockedIcon = styled(FileActionsLockedIcon)` + ${commonIconsStyles} + path { + fill: #3b72a7; + } +`; diff --git a/products/ASC.Files/Client/src/components/dialogs/ConflictResolveDialog/index.js b/products/ASC.Files/Client/src/components/dialogs/ConflictResolveDialog/index.js new file mode 100644 index 0000000000..bcf55968f7 --- /dev/null +++ b/products/ASC.Files/Client/src/components/dialogs/ConflictResolveDialog/index.js @@ -0,0 +1,184 @@ +import React, { useState } from "react"; +import { withRouter } from "react-router"; +import ModalDialogContainer from "../ModalDialogContainer"; +import ModalDialog from "@appserver/components/modal-dialog"; +import RadioButtonGroup from "@appserver/components/radio-button-group"; +import Button from "@appserver/components/button"; +import Text from "@appserver/components/text"; +import { withTranslation } from "react-i18next"; +import { inject, observer } from "mobx-react"; +import { ConflictResolveType } from "@appserver/common/constants"; + +const ConflictResolveDialog = (props) => { + const { + t, + visible, + setConflictResolveDialogVisible, + conflictResolveDialogData, + items, + itemOperationToFolder, + } = props; + + const { + destFolderId, + folderIds, + fileIds, + deleteAfter, + folderTitle, + isCopy, + translations, + } = conflictResolveDialogData; + + const [resolveType, setResolveType] = useState("overwrite"); + + const onSelectResolveType = (e) => setResolveType(e.target.value); + const onClose = () => setConflictResolveDialogVisible(false); + + const getResolveType = () => { + switch (resolveType) { + case "skip": + return ConflictResolveType.Skip; + case "overwrite": + return ConflictResolveType.Overwrite; + case "create": + return ConflictResolveType.Duplicate; + + default: + return ConflictResolveType.Overwrite; + } + }; + + const onAcceptType = () => { + const conflictResolveType = getResolveType(); + + let newFileIds = fileIds; + if (conflictResolveType === ConflictResolveType.Skip) { + for (let item of items) { + newFileIds = newFileIds.filter((x) => x.id === item.id); + } + } + + if (!folderIds.length && !newFileIds.length) return onClose(); + + const data = { + destFolderId, + folderIds, + fileIds: newFileIds, + conflictResolveType, + deleteAfter, + isCopy, + translations, + }; + + onClose(); + itemOperationToFolder(data); + }; + + const radioOptions = [ + { + label: ( +
+ {t("OverwriteTitle")} + {t("OverwriteDescription")} +
+ ), + value: "overwrite", + }, + { + label: ( +
+ {t("CreateTitle")} + {t("CreateDescription")} +
+ ), + + value: "create", + }, + { + label: ( +
+ {t("SkipTitle")} + {t("SkipDescription")} +
+ ), + value: "skip", + }, + ]; + + const filesCount = items.length; + const singleFile = filesCount === 1; + const file = items[0].title; + + return ( + + + {t("ConflictResolveTitle")} + + + {singleFile + ? t("ConflictResolveDescription", { file, folder: folderTitle }) + : t("ConflictResolveDescriptionFiles", { + filesCount, + folder: folderTitle, + })} + + + {t("ConflictResolveSelectAction")} + + + + +