Merge branch 'hotfix/v1.1.2' of https://github.com/ONLYOFFICE/DocSpace into hotfix/v1.1.2

This commit is contained in:
Valeria Bagisheva 2023-08-29 11:02:00 +03:00
commit 6020c1f74f
36 changed files with 1280 additions and 771 deletions

View File

@ -29,6 +29,12 @@ if ($args[0] -eq "--force") {
Write-Host "FORCE BUILD BASE IMAGES: $Force" -ForegroundColor Blue
$ExistsNetwork= docker network ls --format '{{.Name}}' | findstr "onlyoffice"
if (-not $ExistsNetwork) {
docker network create --driver bridge onlyoffice
}
Write-Host "Run MySQL" -ForegroundColor Green
docker compose -f "$DockerDir\db.yml" up -d

View File

@ -38,6 +38,12 @@ echo "Run MySQL"
arch_name="$(uname -m)"
existsnetwork=$(docker network ls | awk '{print $2;}' | { grep -x onlyoffice || true; });
if [[ -z ${existsnetwork} ]]; then
docker network create --driver bridge onlyoffice
fi
if [ "${arch_name}" = "x86_64" ]; then
echo "CPU Type: x86_64 -> run db.yml"
docker compose -f $dockerDir/db.yml up -d

View File

@ -99,7 +99,6 @@
DOCEDITOR_HOST=${CONTAINER_PREFIX}doceditor
LOGIN_HOST=${CONTAINER_PREFIX}login
HELTHCHECKS_HOST=${CONTAINER_PREFIX}healthchecks
REDIS_HOST=${CONTAINER_PREFIX}redis
# proxy upstream environment #
SERVICE_API_SYSTEM=${API_SYSTEM_HOST}:${SERVICE_PORT}

View File

@ -162,7 +162,9 @@ RUN sed -i 's/127.0.0.1:5010/$service_api_system/' /etc/nginx/conf.d/onlyoffice.
sed -i 's/127.0.0.1:5011/$service_login/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/127.0.0.1:5033/$service_healthchecks/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/$public_root/\/var\/www\/public\//' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/http:\/\/172.*/$document_server;/' /etc/nginx/conf.d/onlyoffice.conf
sed -i 's/http:\/\/172.*/$document_server;/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/\(redis_host =\).*/\1 "$server_redis"/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/\(redis_port =\).*/\1 $server_redis_port/' /etc/nginx/conf.d/onlyoffice.conf
ENTRYPOINT [ "/docker-entrypoint.sh" ]

View File

@ -85,3 +85,15 @@ map "$DOCUMENT_SERVER_URL_EXTERNAL" "$document_server" {
default "$DOCUMENT_SERVER_URL_EXTERNAL";
"" "http://$DOCUMENT_CONTAINER_NAME";
}
map "$REDIS_HOST" "$server_redis" {
volatile;
default "$REDIS_HOST";
"" "$REDIS_CONTAINER_NAME";
}
map "$REDIS_PORT" "$server_redis_port" {
volatile;
default "$REDIS_PORT";
"" "6379";
}

View File

@ -19,7 +19,7 @@ LOG_DIR = os.environ["LOG_DIR"] if environ.get("LOG_DIR") else "/var/log/" + PRO
ROUTER_HOST = os.environ["ROUTER_HOST"] if environ.get("ROUTER_HOST") else "localhost"
SOCKET_HOST = os.environ["SOCKET_HOST"] if environ.get("SOCKET_HOST") else "onlyoffice-socket"
MYSQL_CONTAINER_NAME = os.environ["MYSQL_CONTAINER_NAME"] if environ.get("MYSQL_CONTAINER_NAME") else "onlyoffice-mysql"
MYSQL_CONTAINER_NAME = os.environ["MYSQL_CONTAINER_NAME"] if environ.get("MYSQL_CONTAINER_NAME") else "onlyoffice-mysql-server"
MYSQL_HOST = os.environ["MYSQL_HOST"] if environ.get("MYSQL_HOST") else None
MYSQL_PORT = os.environ["MYSQL_PORT"] if environ.get("MYSQL_PORT") else "3306"
MYSQL_DATABASE = os.environ["MYSQL_DATABASE"] if environ.get("MYSQL_DATABASE") else "onlyoffice"

View File

@ -21,6 +21,7 @@ x-service: &x-service-base
expose:
- ${SERVICE_PORT}
environment:
MYSQL_CONTAINER_NAME: ${MYSQL_CONTAINER_NAME}
MYSQL_HOST: ${MYSQL_HOST}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
@ -37,9 +38,24 @@ x-service: &x-service-base
DOCUMENT_SERVER_JWT_SECRET: ${DOCUMENT_SERVER_JWT_SECRET}
DOCUMENT_SERVER_JWT_HEADER: ${DOCUMENT_SERVER_JWT_HEADER}
DOCUMENT_SERVER_URL_PUBLIC: ${DOCUMENT_SERVER_URL_PUBLIC}
DOCUMENT_SERVER_URL_INTERNAL: ${DOCUMENT_SERVER_URL_INTERNAL}
DOCUMENT_CONTAINER_NAME: ${DOCUMENT_CONTAINER_NAME}
DOCUMENT_SERVER_URL_EXTERNAL: ${DOCUMENT_SERVER_URL_EXTERNAL}
KAFKA_HOST: ${KAFKA_HOST}
ELK_CONTAINER_NAME: ${ELK_CONTAINER_NAME}
ELK_SHEME: ${ELK_SHEME}
ELK_HOST: ${ELK_HOST}
ELK_PORT: ${ELK_PORT}
REDIS_CONTAINER_NAME: ${REDIS_CONTAINER_NAME}
REDIS_HOST: ${REDIS_HOST}
REDIS_PORT: ${REDIS_PORT}
REDIS_USER_NAME: ${REDIS_USER_NAME}
REDIS_PASSWORD: ${REDIS_PASSWORD}
RABBIT_CONTAINER_NAME: ${RABBIT_CONTAINER_NAME}
RABBIT_HOST: ${RABBIT_HOST}
RABBIT_PORT: ${RABBIT_PORT}
RABBIT_VIRTUAL_HOST: ${RABBIT_VIRTUAL_HOST}
RABBIT_USER_NAME: ${RABBIT_USER_NAME}
RABBIT_PASSWORD: ${RABBIT_PASSWORD}
PROXY_HOST: ${PROXY_HOST}
volumes:
- ${ROOT_DIR}/Data:/app/onlyoffice/data
@ -91,7 +107,7 @@ services:
onlyoffice-document-server:
<<: [*x-profiles-extra-services]
image: "${DOCUMENT_SERVER_IMAGE_NAME}"
container_name: ${DOCUMENT_SERVER_HOST}
container_name: ${DOCUMENT_CONTAINER_NAME}
# Strings below enable the JSON Web Token validation.
environment:
- JWT_ENABLED=true
@ -253,7 +269,11 @@ services:
- SERVICE_DOCEDITOR=${SERVICE_DOCEDITOR}
- SERVICE_LOGIN=${SERVICE_LOGIN}
- SERVICE_HELTHCHECKS=${SERVICE_HELTHCHECKS}
- DOCUMENT_SERVER=${DOCUMENT_SERVER_HOST}
- DOCUMENT_CONTAINER_NAME=${DOCUMENT_CONTAINER_NAME}
- DOCUMENT_SERVER_URL_EXTERNAL=${DOCUMENT_SERVER_URL_EXTERNAL}
- REDIS_CONTAINER_NAME=${REDIS_CONTAINER_NAME}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
- SERVICE_PORT=${SERVICE_PORT}
volumes:
- proxy_log:/var/log/nginx
@ -264,6 +284,7 @@ services:
container_name: ${MIGRATION_RUNNER_HOST}
restart: "no"
environment:
MYSQL_CONTAINER_NAME: ${MYSQL_CONTAINER_NAME}
MYSQL_HOST: ${MYSQL_HOST}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}

View File

@ -220,7 +220,9 @@ services:
- SERVICE_HELTHCHECKS=${SERVICE_HELTHCHECKS}
- DOCUMENT_CONTAINER_NAME=${DOCUMENT_CONTAINER_NAME}
- DOCUMENT_SERVER_URL_EXTERNAL=${DOCUMENT_SERVER_URL_EXTERNAL}
- REDIS_CONTAINER_NAME=${REDIS_CONTAINER_NAME}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
- SERVICE_PORT=${SERVICE_PORT}
volumes:
- proxy_log:/var/log/nginx

View File

@ -2,4 +2,3 @@
REDIS_HOST=${REDIS_HOST:-"onlyoffice-redis"}
envsubst '$MAP_HASH_BUCKET_SIZE,$COUNT_WORKER_CONNECTIONS' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
sed -i "s!redis_host = \"127.0.0.1\"!redis_host = \"${REDIS_HOST}\"!g" /etc/nginx/conf.d/onlyoffice.conf

View File

@ -400,6 +400,89 @@ Function TestSqlConnection
Set ConnectionObject = Nothing
End Function
Function OpenRestySetup
On Error Resume Next
Dim objShell, sourcePath, destinationPath, openRestyServicePath, openRestyFolder, objFSO, objFolder
Set objShell = CreateObject("WScript.Shell")
destinationPath = Session.Property("APPDIR")
openRestyServicePath = Session.Property("APPDIR") & "tools\OpenResty.exe"
openRestyFolder = ""
Set objFSO = CreateObject("Scripting.FileSystemObject")
For Each objFolder In objFSO.GetFolder(destinationPath).SubFolders
If Left(objFolder.Name, 9) = "openresty" Then
openRestyFolder = objFolder.Name
End If
Next
Set objFSO = Nothing
sourcePath = Session.Property("APPDIR") & openRestyFolder
' Run XCopy to copy files and folders
objShell.Run "xcopy """ & sourcePath & """ """ & destinationPath & """ /E /I /Y", 0, True
objShell.CurrentDirectory = destinationPath
' Run the RMDIR command to delete the folder
objShell.Run "cmd /c RMDIR /S /Q """ & openRestyFolder & """", 0, True
objShell.Run """" & openRestyServicePath & """ install", 0, True
Set objShell = Nothing
End Function
Function MoveNginxConfigs
On Error Resume Next
Dim objFSO, sourceFolder, targetFolder, nginxFolder
' Define source and target paths
Set objFSO = CreateObject("Scripting.FileSystemObject")
sourceFolder = Session.Property("APPDIR") & "nginx\conf"
targetFolder = "C:\OpenResty\conf"
nginxFolder = Session.Property("APPDIR") & "nginx"
' Check if source folder exists
If objFSO.FolderExists(sourceFolder) Then
' Check if target folder exists, if not, create it
If Not objFSO.FolderExists(targetFolder) Then
objFSO.CreateFolder(targetFolder)
End If
' Copy files and folders from source to target
CopyFolderContents objFSO.GetFolder(sourceFolder), targetFolder, objFSO
' Delete source folder
objFSO.DeleteFolder nginxFolder, True ' "True" parameter for recursive deletion
WScript.Echo "Files and folders moved, and source folder deleted."
Else
WScript.Echo "Source folder does not exist."
End If
Set objFSO = Nothing
End Function
Sub CopyFolderContents(sourceFolder, targetFolder, objFSO)
Dim subFolder, objFile
' Copy files
For Each objFile In sourceFolder.Files
objFSO.CopyFile objFile.Path, targetFolder & "\" & objFile.Name, True
Next
' Recursively copy subfolders
For Each subFolder In sourceFolder.SubFolders
Dim newTargetFolder
newTargetFolder = targetFolder & "\" & subFolder.Name
objFSO.CreateFolder newTargetFolder
CopyFolderContents subFolder, newTargetFolder, objFSO
Next
End Sub
Function EnterpriseConfigure
On Error Resume Next

View File

@ -22,8 +22,8 @@
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Web.Studio.Core.Backup;
public class BackupFileUploadHandler
@ -35,56 +35,101 @@ public class BackupFileUploadHandler
public async Task Invoke(HttpContext context,
PermissionContext permissionContext,
BackupAjaxHandler backupAjaxHandler)
BackupAjaxHandler backupAjaxHandler,
ICache cache,
TenantManager tenantManager,
IConfiguration configuration)
{
FileUploadResult result;
BackupFileUploadResult result = null;
try
{
if (context.Request.Form.Files.Count == 0)
{
result = Error("No files.");
}
{
if (!permissionContext.CheckPermissions(SecutiryConstants.EditPortalSettings))
{
result = Error("Access denied.");
}
var file = context.Request.Form.Files[0];
var filePath = backupAjaxHandler.GetTmpFilePath();
if (File.Exists(filePath))
{
File.Delete(filePath);
}
using (var fileStream = File.Create(filePath))
{
await file.CopyToAsync(fileStream);
throw new ArgumentException("Access denied.");
}
var tenantId = tenantManager.GetCurrentTenant().Id;
var path = backupAjaxHandler.GetTmpFilePath();
if (context.Request.Query["Init"].ToString() == "true")
{
long.TryParse(context.Request.Query["totalSize"], out var size);
if (size <= 0)
{
throw new ArgumentException("Total size must be greater than 0.");
}
result = Success();
var maxSize = tenantManager.GetCurrentTenantQuota().MaxTotalSize;
if (size > maxSize)
{
throw new ArgumentException(BackupResource.LargeBackup);
}
try
{
if (File.Exists(path))
{
File.Delete(path);
}
cache.Insert($"{tenantId} backupTotalSize", size.ToString(), TimeSpan.FromMinutes(10));
int.TryParse(configuration["files:uploader:chunk-size"], out var chunkSize);
chunkSize = chunkSize == 0 ? 10 * 1024 * 1024 : chunkSize;
result = Success(chunkSize);
}
catch
{
throw new ArgumentException("Can't start upload.");
}
}
else
{
long.TryParse(cache.Get<string>($"{tenantId} backupTotalSize"), out var totalSize);
if (totalSize <= 0)
{
throw new ArgumentException("Need init upload.");
}
var file = context.Request.Form.Files[0];
using var stream = file.OpenReadStream();
using var fs = File.Open(path, FileMode.Append);
await stream.CopyToAsync(fs);
if (fs.Length >= totalSize)
{
cache.Remove($"{tenantId} backupTotalSize");
result = Success(endUpload: true);
}
else
{
result = Success();
}
}
}
catch (Exception error)
{
result = Error(error.Message);
}
await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(result));
await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(result, new System.Text.Json.JsonSerializerOptions()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
}));
}
private FileUploadResult Success()
private BackupFileUploadResult Success(int chunk = 0, bool endUpload = false)
{
return new FileUploadResult
return new BackupFileUploadResult
{
Success = true
Success = true,
ChunkSize = chunk,
EndUpload = endUpload
};
}
private FileUploadResult Error(string messageFormat, params object[] args)
private BackupFileUploadResult Error(string messageFormat, params object[] args)
{
return new FileUploadResult
return new BackupFileUploadResult
{
Success = false,
Message = string.Format(messageFormat, args)
@ -92,6 +137,14 @@ public class BackupFileUploadHandler
}
}
internal class BackupFileUploadResult
{
public bool Success { get; set; }
public string Message { get; set; }
public int ChunkSize { get; set; }
public bool EndUpload { get; set; }
}
public static class BackupFileUploadHandlerExtensions
{
public static IApplicationBuilder UseBackupFileUploadHandler(this IApplicationBuilder builder)

View File

@ -77,5 +77,14 @@ namespace ASC.Data.Backup.Core {
return ResourceManager.GetString("ButtonSetPassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Backup is larger than the total size of the portal.
/// </summary>
internal static string LargeBackup {
get {
return ResourceManager.GetString("LargeBackup", resourceCulture);
}
}
}
}

View File

@ -1,5 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
@ -53,10 +112,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="BackupNotFound" xml:space="preserve">
<value>The backup file is invalid. Please, use a file created in ONLYOFFICE v11.5 or later.</value>
@ -64,4 +123,7 @@
<data name="ButtonSetPassword" xml:space="preserve">
<value>Set Password</value>
</data>
<data name="LargeBackup" xml:space="preserve">
<value>Backup is larger than the total size of the portal</value>
</data>
</root>

View File

@ -32,6 +32,8 @@ global using System.Reflection;
global using System.Security.Cryptography;
global using System.ServiceModel;
global using System.Text;
global using System.Text.Json.Serialization;
global using System.Text.RegularExpressions;
global using System.Xml;
global using System.Xml.Linq;
@ -80,7 +82,6 @@ global using ASC.Notify.Recipients;
global using ASC.Security.Cryptography;
global using ASC.Web.Core.PublicResources;
global using ASC.Web.Core.Users;
global using ASC.Web.Core.Utility;
global using ASC.Web.Core.WhiteLabel;
global using ASC.Web.Studio.Core;
global using ASC.Web.Studio.Core.Notify;

View File

@ -109,6 +109,7 @@ public class ChunkZipWriteOperator : IDataWriteOperator
theMemStream.Position = 0;
StoragePath = await _sessionHolder.UploadChunkAsync(_chunkedUploadSession, theMemStream, theMemStream.Length);
_sha.TransformBlock(buffer, 0, bytesRead, buffer, 0);
}
else
{
@ -119,7 +120,6 @@ public class ChunkZipWriteOperator : IDataWriteOperator
await theMemStream.CopyToAsync(_fileStream);
_fileStream.Flush();
}
_sha.TransformBlock(buffer, 0, bytesRead, buffer, 0);
}
}
if (last)

View File

@ -62,25 +62,26 @@ module.exports = function () {
}
const ErrorMessageKey = {
Error: 1,
SsoError: 17,
SsoAuthFailed: 18,
SsoAttributesNotFound: 19,
};
function getPortalAuthErrorUrl(req, errorKey) {
const url = getPortalAuthUrl(req) + "?am=" + errorKey;
const url = getBaseUrl(req) + "/login/error?messageKey=" + errorKey;
logger.debug("getPortalAuthErrorUrl: " + url);
return url;
}
function getPortalErrorUrl(req) {
const url = getBaseUrl(req) + "/500.aspx";
const url = getBaseUrl(req) + "/login/error?messageKey=" + ErrorMessageKey.Error;
logger.debug("getPortal500Url: " + url);
return url;
}
function getPortal404Url(req) {
const url = getBaseUrl(req) + "/404.aspx";
const url = getBaseUrl(req) + "/login/error?messageKey=" + ErrorMessageKey.SsoError;
logger.debug("getPortal404Url: " + url);
return url;
}

View File

@ -42,10 +42,10 @@ global using ASC.Files.Core.EF;
global using ASC.Web.Api.Routing;
global using ASC.Web.Studio.Core.Backup;
global using ASC.Web.Studio.Core.Notify;
global using ASC.Web.Studio.Utility;
global using Microsoft.AspNetCore.Authorization;
global using Autofac;
global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Http.Features;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore.Server.Kestrel.Core;

View File

@ -16,6 +16,7 @@
{
"Period":"00:15:00"
},
"ChunkSize": 20971520
"ChunkSize": 20971520,
"MaxLocalSize": 1048576000
}
}

View File

@ -2,7 +2,7 @@ import saveAs from "file-saver";
export const getCrashReport = (userId, version, language, error) => {
const currentTime = new Date();
const reportTime = currentTime.toTimeString();
const reportTime = currentTime.toUTCString();
const lsObject = JSON.stringify(window.localStorage) || "";
const report = {

View File

@ -96,6 +96,8 @@ const DirectThirdPartyConnection = (props) => {
try {
if (!isDirectConnection()) setState({ isUpdatingInfo: true });
onSelectFolder && onSelectFolder("");
let account;
[account, capabilities] = await Promise.all([
getSettingsThirdParty(),
@ -186,6 +188,7 @@ const DirectThirdPartyConnection = (props) => {
const onConnect = () => {
clearLocalStorage();
onSelectFolder && onSelectFolder("");
const {
provider_key,

View File

@ -8,7 +8,6 @@ import { TenantStatus } from "@docspace/common/constants";
import { startRestore } from "@docspace/common/api/portal";
import { combineUrl } from "@docspace/common/utils";
import toastr from "@docspace/components/toast/toastr";
import { request } from "@docspace/common/api/client";
const ButtonContainer = (props) => {
const {
@ -28,29 +27,11 @@ const ButtonContainer = (props) => {
setTenantStatus,
isFormReady,
getStorageParams,
uploadLocalFile,
} = props;
const [isUploadingLocalFile, setIsUploadingLocalFile] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const localFileUploading = async () => {
try {
const checkedFile = await request({
baseURL: combineUrl(window.DocSpaceConfig?.proxy?.url, config.homepage),
method: "post",
url: `/backupFileUpload.ashx`,
responseType: "text",
data: restoreResource,
});
return checkedFile;
} catch (e) {
toastr.error(e);
setIsUploadingLocalFile(false);
return null;
}
};
const onRestoreClick = async () => {
if (isCheckedThirdPartyStorage) {
const requiredFieldsFilled = isFormReady();
@ -74,16 +55,16 @@ const ButtonContainer = (props) => {
}
if (isCheckedLocalFile) {
const isUploadedFile = await localFileUploading();
const uploadedFile = await uploadLocalFile();
if (!isUploadedFile) {
if (!uploadedFile) {
toastr.error(t("BackupCreatedError"));
setIsLoading(false);
return;
}
if (isUploadedFile?.Message) {
toastr.error(isUploadedFile.Message);
setIsUploadingLocalFile(false);
if (!uploadedFile.data.EndUpload) {
toastr.error(uploadedFile.data.Message ?? t("BackupCreatedError"));
setIsLoading(false);
return;
}
@ -107,19 +88,17 @@ const ButtonContainer = (props) => {
} catch (e) {
toastr.error(e);
setIsUploadingLocalFile(false);
setIsLoading(false);
}
};
const isButtonDisabled =
isLoading ||
isUploadingLocalFile ||
!isMaxProgress ||
!isConfirmed ||
!isEnableRestore ||
!restoreResource;
const isLoadingButton = isUploadingLocalFile || isLoading;
const isLoadingButton = isLoading;
return (
<>
@ -154,11 +133,13 @@ export default inject(({ auth, backup }) => {
isFormReady,
getStorageParams,
restoreResource,
uploadLocalFile,
} = backup;
const { isRestoreAndAutoBackupAvailable } = currentQuotaStore;
const isMaxProgress = downloadingProgress === 100;
return {
uploadLocalFile,
isMaxProgress,
setTenantStatus,
isEnableRestore: isRestoreAndAutoBackupAvailable,

View File

@ -5,10 +5,7 @@ import FileInput from "@docspace/components/file-input";
const LocalFile = ({ setRestoreResource, isEnableRestore, t }) => {
const onClickInput = (file) => {
let data = new FormData();
data.append("file", file);
setRestoreResource(data);
setRestoreResource(file);
};
return (

View File

@ -72,7 +72,7 @@ const DeveloperToolsWrapper = (props) => {
const currentTab = data.findIndex((item) => path.includes(item.id));
if (currentTab !== -1) setCurrentTab(currentTab);
await loadBaseInfo();
//await loadBaseInfo();
setIsLoading(true);
};

View File

@ -16,15 +16,8 @@ import SSOLoader from "./sub-components/ssoLoader";
import SMTPSettings from "./SMTPSettings";
const IntegrationWrapper = (props) => {
const {
t,
tReady,
history,
loadBaseInfo,
enablePlugins,
toDefault,
isSSOAvailable,
} = props;
const { t, tReady, history, enablePlugins, toDefault, isSSOAvailable } =
props;
const [currentTab, setCurrentTab] = useState(0);
const [isLoading, setIsLoading] = useState(false);
@ -67,7 +60,6 @@ const IntegrationWrapper = (props) => {
const currentTab = data.findIndex((item) => path.includes(item.id));
if (currentTab !== -1) setCurrentTab(currentTab);
await loadBaseInfo();
setIsLoading(true);
};
@ -91,16 +83,12 @@ const IntegrationWrapper = (props) => {
return <Submenu data={data} startSelect={currentTab} onSelect={onSelect} />;
};
export default inject(({ setup, auth, ssoStore }) => {
const { initSettings } = setup;
export default inject(({ auth, ssoStore }) => {
const { load: toDefault } = ssoStore;
const { enablePlugins } = auth.settingsStore;
const { isSSOAvailable } = auth.currentQuotaStore;
return {
loadBaseInfo: async () => {
await initSettings();
},
enablePlugins,
toDefault,
isSSOAvailable,

View File

@ -17,7 +17,7 @@ import { resetSessionStorage } from "../../utils";
import { isMobile } from "react-device-detect";
const SecurityWrapper = (props) => {
const { t, history, loadBaseInfo } = props;
const { t, history, loadBaseInfo, resetIsInit } = props;
const [currentTab, setCurrentTab] = useState(0);
const [isLoading, setIsLoading] = useState(false);
@ -46,6 +46,7 @@ const SecurityWrapper = (props) => {
useEffect(() => {
return () => {
resetIsInit();
resetSessionStorage();
};
}, []);
@ -88,12 +89,13 @@ const SecurityWrapper = (props) => {
};
export default inject(({ setup }) => {
const { initSettings } = setup;
const { initSettings, resetIsInit } = setup;
return {
loadBaseInfo: async () => {
await initSettings();
},
resetIsInit,
};
})(
withTranslation(["Settings", "Common"])(withRouter(observer(SecurityWrapper)))

View File

@ -7,7 +7,9 @@ import {
} from "../pages/PortalSettings/utils";
import toastr from "@docspace/components/toast/toastr";
import { AutoBackupPeriod } from "@docspace/common/constants";
//import api from "@docspace/common/api";
import { combineUrl } from "@docspace/common/utils";
import config from "PACKAGE_FILE";
import { uploadBackup } from "@docspace/common/api/files";
const { EveryDayType, EveryWeekType } = AutoBackupPeriod;
@ -617,6 +619,73 @@ class BackupStore {
setRestoreResource = (value) => {
this.restoreResource = value;
};
setChunkUploadSize = (chunkUploadSize) => {
this.chunkUploadSize = chunkUploadSize;
};
uploadFileChunks = async (requestsDataArray, url) => {
const length = requestsDataArray.length;
let res;
for (let index = 0; index < length; index++) {
res = await uploadBackup(
combineUrl(window.DocSpaceConfig?.proxy?.url, config.homepage, url),
requestsDataArray[index]
);
if (!res) return false;
if (res.data.Message || !res.data.Success) return res;
}
return res;
};
uploadLocalFile = async () => {
try {
const url = "/backupFileUpload.ashx";
const res = await uploadBackup(
combineUrl(
window.DocSpaceConfig?.proxy?.url,
config.homepage,
`${url}?init=true&totalSize=${this.restoreResource.size}`
)
);
if (!res) return false;
if (res.data.Message || !res.data.Success) return res;
const chunkUploadSize = res.data.ChunkSize;
const chunks = Math.ceil(
this.restoreResource.size / chunkUploadSize,
chunkUploadSize
);
const requestsDataArray = [];
let chunk = 0;
while (chunk < chunks) {
const offset = chunk * chunkUploadSize;
const formData = new FormData();
formData.append(
"file",
this.restoreResource.slice(offset, offset + chunkUploadSize)
);
requestsDataArray.push(formData);
chunk++;
}
return await this.uploadFileChunks(requestsDataArray, url);
} catch (e) {
toastr.error(e);
return null;
}
};
}
export default BackupStore;

View File

@ -99,6 +99,10 @@ class SettingsSetupStore {
this.isLoadingDownloadReport = state;
};
resetIsInit = () => {
this.isInit = false;
};
setIsInit = (isInit) => {
this.isInit = isInit;
};

View File

@ -560,6 +560,10 @@ export function uploadFile(url, data) {
return axios.post(url, data);
}
export function uploadBackup(url, data) {
return axios.post(url, data);
}
export function downloadFiles(fileIds, folderIds) {
const data = { fileIds, folderIds };
return request({ method: "put", url: "/files/fileops/bulkdownload", data });

View File

@ -2,6 +2,7 @@ import ShareGoogleReactSvgUrl from "PUBLIC_DIR/images/share.google.react.svg?url
import ShareFacebookReactSvgUrl from "PUBLIC_DIR/images/share.facebook.react.svg?url";
import ShareTwitterReactSvgUrl from "PUBLIC_DIR/images/share.twitter.react.svg?url";
import ShareLinkedinReactSvgUrl from "PUBLIC_DIR/images/share.linkedin.react.svg?url";
import ShareMicrosoftReactSvgUrl from "PUBLIC_DIR/images/share.microsoft.react.svg?url";
export const LANGUAGE = "asc_language";
export const COOKIE_EXPIRATION_YEAR = 31536000000;
@ -247,6 +248,10 @@ export const providersData = Object.freeze({
label: "linkedin",
icon: ShareLinkedinReactSvgUrl,
},
microsoft: {
label: "microsoft",
icon: ShareMicrosoftReactSvgUrl,
},
});
export const LoaderStyle = {
title: "",

View File

@ -283,6 +283,8 @@ export function getProviderTranslation(provider, t, linked = false) {
return t("Common:SignInWithTwitter");
case "linkedin":
return t("Common:SignInWithLinkedIn");
case "microsoft":
return t("Common:SignInWithMicrosoft");
case "sso":
return t("Common:SignInWithSso");
}

View File

@ -94,12 +94,18 @@ internal class OneDriveStorage
public async Task<bool> CheckAccessAsync()
{
var request = await OnedriveClient
.Drive
.Request()
.GetAsync();
return request != null;
try
{
var request = await OnedriveClient
.Drive
.Request()
.GetAsync();
return request != null;
}
catch
{
return false;
}
}

View File

@ -325,7 +325,7 @@ public class UserController : PeopleControllerBase
}
}
if (inDto.IsUser)
if (inDto.IsUser.GetValueOrDefault(false))
{
_messageService.Send(MessageAction.GuestCreated, _messageTarget.Create(user.Id), user.DisplayUserName(false, _displayUserSettingsHelper));
}
@ -1354,20 +1354,23 @@ public class UserController : PeopleControllerBase
// change user type
var canBeGuestFlag = !user.IsOwner(Tenant) && !_userManager.IsDocSpaceAdmin(user) && user.GetListAdminModules(_webItemSecurity, _webItemManager).Count == 0 && !user.IsMe(_authContext);
if (inDto.IsUser && !_userManager.IsUser(user) && canBeGuestFlag)
if (inDto.IsUser.HasValue)
{
await _countUserChecker.CheckAppend();
await _userManager.AddUserIntoGroup(user.Id, Constants.GroupUser.ID);
_webItemSecurityCache.ClearCache(Tenant.Id);
}
var isUser = inDto.IsUser.Value;
if (isUser && !_userManager.IsUser(user) && canBeGuestFlag)
{
await _countUserChecker.CheckAppend();
await _userManager.AddUserIntoGroup(user.Id, Constants.GroupUser.ID);
_webItemSecurityCache.ClearCache(Tenant.Id);
}
if (!self && !inDto.IsUser && _userManager.IsUser(user))
{
await _countPaidUserChecker.CheckAppend();
await _userManager.RemoveUserFromGroup(user.Id, Constants.GroupUser.ID);
_webItemSecurityCache.ClearCache(Tenant.Id);
if (!self && !isUser && _userManager.IsUser(user))
{
await _countPaidUserChecker.CheckAppend();
await _userManager.RemoveUserFromGroup(user.Id, Constants.GroupUser.ID);
_webItemSecurityCache.ClearCache(Tenant.Id);
}
}
await _userManager.UpdateUserInfoWithSyncCardDavAsync(user);
_messageService.Send(MessageAction.UserUpdated, _messageTarget.Create(user.Id), user.DisplayUserName(false, _displayUserSettingsHelper), user.Id);

View File

@ -36,7 +36,7 @@ public class MemberRequestDto
/// <summary>Specifies if this is a guest or a user</summary>
/// <type>System.Boolean, System</type>
public bool IsUser { get; set; }
public bool? IsUser { get; set; }
/// <summary>Email</summary>
/// <type>System.String, System</type>

View File

@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0H9.50383V9.50383H0V0Z" fill="#F25022"/>
<path d="M10.4961 0H20V9.50383H10.4961V0Z" fill="#7FBA00"/>
<path d="M0 10.4961H9.50383V19.9999H0V10.4961Z" fill="#00A4EF"/>
<path d="M10.4961 10.4961H20V19.9999H10.4961V10.4961Z" fill="#FFB900"/>
</svg>

After

Width:  |  Height:  |  Size: 353 B

View File

@ -239,6 +239,7 @@
"SignInWithFacebook": "Sign in with Facebook",
"SignInWithGoogle": "Sign in with Google",
"SignInWithLinkedIn": "Sign in with LinkedIn",
"SignInWithMicrosoft": "Sign in with Microsoft",
"SignInWithSso": "Sign in with SSO",
"SignInWithTwitter": "Sign in with Twitter",
"Size": "Size",

1481
yarn.lock

File diff suppressed because it is too large Load Diff