Merge branch 'develop' into feature/mobile-sdk

# Conflicts:
#	packages/client/src/pages/PortalSettings/categories/developer-tools/JavascriptSDK/index.js
This commit is contained in:
Aleksandr Lushkin 2023-09-08 11:41:45 +02:00
commit 88b06c50aa
245 changed files with 11624 additions and 3519 deletions

View File

@ -219,11 +219,7 @@ public class Consumer : IDictionary<string, string>
private void Set(string name, string value)
{
if (!CanSet)
{
throw new NotSupportedException("Key for read only. Key " + name);
}
if (!ManagedKeys.Contains(name))
{
if (_additional.ContainsKey(name))
@ -238,6 +234,11 @@ public class Consumer : IDictionary<string, string>
return;
}
if (!CanSet)
{
throw new NotSupportedException("Key for read only. Key " + name);
}
var tenant = CoreBaseSettings.Standalone
? Tenant.DefaultTenant
: TenantManager.GetCurrentTenant().Id;

View File

@ -120,4 +120,7 @@
<data name="ButtonSetPassword" xml:space="preserve">
<value>비밀번호 설정</value>
</data>
<data name="BackupNotFound" xml:space="preserve">
<value>백업 파일이 유효하지 않습니다. ONLYOFFICE v11.5 이상에서 생성된 파일을 사용해주세요.</value>
</data>
</root>

View File

@ -120,4 +120,7 @@
<data name="ButtonSetPassword" xml:space="preserve">
<value>Definir palavra-passe</value>
</data>
<data name="BackupNotFound" xml:space="preserve">
<value>O ficheiro da cópia de segurança é inválido. Por favor, utilize um ficheiro criado no ONLYOFFICE v11.5 ou noutra versão mais recente.</value>
</data>
</root>

View File

@ -120,4 +120,7 @@
<data name="ButtonSetPassword" xml:space="preserve">
<value>Nastaviť heslo</value>
</data>
<data name="BackupNotFound" xml:space="preserve">
<value>Záložný súbor je neplatný. Použite prosím súbor vytvorený vo verzii ONLYOFFICE v11.5 alebo novšej verzii.</value>
</data>
</root>

View File

@ -1,237 +0,0 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using ConfigurationManager = System.Configuration.ConfigurationManager;
namespace ASC.Data.Backup;
[Scope]
public class DbBackupProvider : IBackupProvider
{
public string Name => "databases";
private readonly List<string> _processedTables = new List<string>();
private readonly DbHelper _dbHelper;
private readonly TempStream _tempStream;
public DbBackupProvider(DbHelper dbHelper, TempStream tempStream)
{
_dbHelper = dbHelper;
_tempStream = tempStream;
}
public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
public async Task<IEnumerable<XElement>> GetElements(int tenant, string[] configs, IDataWriteOperator writer)
{
_processedTables.Clear();
var xml = new List<XElement>();
var connectionKeys = new Dictionary<string, string>();
foreach (var connectionString in GetConnectionStrings(configs))
{
//do not save the base, having the same provider and connection string is not to duplicate
//data, but also expose the ref attribute of repetitive bases for the correct recovery
var node = new XElement(connectionString.Name);
xml.Add(node);
var connectionKey = connectionString.ProviderName + connectionString.ConnectionString;
if (connectionKeys.TryGetValue(connectionKey, out var value))
{
node.Add(new XAttribute("ref", value));
}
else
{
connectionKeys.Add(connectionKey, connectionString.Name);
node.Add(await BackupDatabase(tenant, connectionString, writer));
}
}
return xml.AsEnumerable();
}
public async Task LoadFromAsync(IEnumerable<XElement> elements, int tenant, string[] configs, IDataReadOperator reader)
{
_processedTables.Clear();
foreach (var connectionString in GetConnectionStrings(configs))
{
await RestoreDatabaseAsync(connectionString, elements, reader);
}
}
public IEnumerable<ConnectionStringSettings> GetConnectionStrings(string[] configs)
{
/* if (configs.Length == 0)
{
configs = new string[] { AppDomain.CurrentDomain.SetupInformation.ConfigurationFile };
}
var connectionStrings = new List<ConnectionStringSettings>();
foreach (var config in configs)
{
connectionStrings.AddRange(GetConnectionStrings(GetConfiguration(config)));
}
return connectionStrings.GroupBy(cs => cs.Name).Select(g => g.First());*/
return null;
}
public IEnumerable<ConnectionStringSettings> GetConnectionStrings(Configuration cfg)
{
var connectionStrings = new List<ConnectionStringSettings>();
foreach (ConnectionStringSettings connectionString in cfg.ConnectionStrings.ConnectionStrings)
{
if (connectionString.Name == "LocalSqlServer" || connectionString.Name == "readonly")
{
continue;
}
connectionStrings.Add(connectionString);
if (connectionString.ConnectionString.Contains("|DataDirectory|"))
{
connectionString.ConnectionString = connectionString.ConnectionString.Replace("|DataDirectory|", Path.GetDirectoryName(cfg.FilePath) + '\\');
}
}
return connectionStrings;
}
private void OnProgressChanged(string status, int progress)
{
ProgressChanged?.Invoke(this, new ProgressChangedEventArgs(status, progress));
}
private Configuration GetConfiguration(string config)
{
if (config.Contains(Path.DirectorySeparatorChar) && !Uri.IsWellFormedUriString(config, UriKind.Relative))
{
var map = new ExeConfigurationFileMap
{
ExeConfigFilename = string.Equals(Path.GetExtension(config), ".config", StringComparison.OrdinalIgnoreCase) ? config : CrossPlatform.PathCombine(config, "Web.config")
};
return ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
}
return ConfigurationManager.OpenExeConfiguration(config);
}
private async Task<List<XElement>> BackupDatabase(int tenant, ConnectionStringSettings connectionString, IDataWriteOperator writer)
{
var xml = new List<XElement>();
var errors = 0;
var timeout = TimeSpan.FromSeconds(1);
var tables = _dbHelper.GetTables();
for (var i = 0; i < tables.Count; i++)
{
var table = tables[i];
OnProgressChanged(table, (int)(i / (double)tables.Count * 100));
if (_processedTables.Contains(table, StringComparer.InvariantCultureIgnoreCase))
{
continue;
}
xml.Add(new XElement(table));
DataTable dataTable;
while (true)
{
try
{
dataTable = _dbHelper.GetTable(table, tenant);
break;
}
catch
{
errors++;
if (20 < errors)
{
throw;
}
Thread.Sleep(timeout);
}
}
foreach (DataColumn c in dataTable.Columns)
{
if (c.DataType == typeof(DateTime))
{
c.DateTimeMode = DataSetDateTime.Unspecified;
}
}
await using (var file = _tempStream.Create())
{
dataTable.WriteXml(file, XmlWriteMode.WriteSchema);
await writer.WriteEntryAsync($"{Name}\\{connectionString.Name}\\{table}".ToLower(), file);
}
_processedTables.Add(table);
}
return xml;
}
private async Task RestoreDatabaseAsync(ConnectionStringSettings connectionString, IEnumerable<XElement> elements, IDataReadOperator reader)
{
var dbName = connectionString.Name;
var dbElement = elements.SingleOrDefault(e => string.Equals(e.Name.LocalName, connectionString.Name, StringComparison.OrdinalIgnoreCase));
if (dbElement != null && dbElement.Attribute("ref") != null)
{
dbName = dbElement.Attribute("ref").Value;
dbElement = elements.Single(e => string.Equals(e.Name.LocalName, dbElement.Attribute("ref").Value, StringComparison.OrdinalIgnoreCase));
}
if (dbElement == null)
{
return;
}
var tables = _dbHelper.GetTables();
for (var i = 0; i < tables.Count; i++)
{
var table = tables[i];
OnProgressChanged(table, (int)(i / (double)tables.Count * 100));
if (_processedTables.Contains(table, StringComparer.InvariantCultureIgnoreCase))
{
continue;
}
if (dbElement.Element(table) != null)
{
await using (var stream = reader.GetEntry($"{Name}\\{dbName}\\{table}".ToLower()))
{
var data = new DataTable();
data.ReadXml(stream);
await _dbHelper.SetTableAsync(data);
}
_processedTables.Add(table);
}
}
}
}

View File

@ -1,310 +0,0 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Backup;
[Scope]
public class DbHelper : IDisposable
{
private readonly DbProviderFactory _factory;
private readonly DbConnection _connect;
private readonly DbCommandBuilder _builder;
private readonly DataTable _columns;
private readonly bool _mysql;
private readonly ILogger<DbHelper> _logger;
private readonly TenantDbContext _tenantDbContext;
private readonly CoreDbContext _coreDbContext;
private readonly IDictionary<string, string> _whereExceptions
= new Dictionary<string, string>();
public DbHelper(
ILogger<DbHelper> logger,
ConnectionStringSettings connectionString,
IDbContextFactory<TenantDbContext> tenantDbContext,
IDbContextFactory<CoreDbContext> coreDbContext)
{
_logger = logger;
_tenantDbContext = tenantDbContext.CreateDbContext();
_coreDbContext = coreDbContext.CreateDbContext();
var file = connectionString.ElementInformation.Source;
if ("web.connections.config".Equals(Path.GetFileName(file), StringComparison.InvariantCultureIgnoreCase))
{
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 + "']");
_factory = (DbProviderFactory)Activator.CreateInstance(Type.GetType(provider.Attribute("type").Value, true));
_builder = _factory.CreateCommandBuilder();
_connect = _factory.CreateConnection();
_connect.ConnectionString = connectionString.ConnectionString;
_connect.Open();
_mysql = connectionString.ProviderName.Contains("mysql", StringComparison.OrdinalIgnoreCase);
if (_mysql)
{
CreateCommand("set @@session.sql_mode = concat(@@session.sql_mode, ',NO_AUTO_VALUE_ON_ZERO')").ExecuteNonQuery();
}
_columns = _connect.GetSchema("Columns");
_whereExceptions["calendar_calendar_item"] = " where calendar_id in (select id from calendar_calendars where tenant = {0}) ";
_whereExceptions["calendar_calendar_user"] = " where calendar_id in (select id from calendar_calendars where tenant = {0}) ";
_whereExceptions["calendar_event_item"] = " inner join calendar_events on calendar_event_item.event_id = calendar_events.id where calendar_events.tenant = {0} ";
_whereExceptions["calendar_event_user"] = " inner join calendar_events on calendar_event_user.event_id = calendar_events.id where calendar_events.tenant = {0} ";
_whereExceptions["crm_entity_contact"] = " inner join crm_contact on crm_entity_contact.contact_id = crm_contact.id where crm_contact.tenant_id = {0} ";
_whereExceptions["crm_entity_tag"] = " inner join crm_tag on crm_entity_tag.tag_id = crm_tag.id where crm_tag.tenant_id = {0} ";
_whereExceptions["files_folder_tree"] = " inner join files_folder on folder_id = id where tenant_id = {0} ";
_whereExceptions["forum_answer_variant"] = " where answer_id in (select id from forum_answer where tenantid = {0})";
_whereExceptions["forum_topic_tag"] = " where topic_id in (select id from forum_topic where tenantid = {0})";
_whereExceptions["forum_variant"] = " where question_id in (select id from forum_question where tenantid = {0})";
_whereExceptions["projects_project_participant"] = " inner join projects_projects on projects_project_participant.project_id = projects_projects.id where projects_projects.tenant_id = {0} ";
_whereExceptions["projects_following_project_participant"] = " inner join projects_projects on projects_following_project_participant.project_id = projects_projects.id where projects_projects.tenant_id = {0} ";
_whereExceptions["projects_project_tag"] = " inner join projects_projects on projects_project_tag.project_id = projects_projects.id where projects_projects.tenant_id = {0} ";
_whereExceptions["tenants_tenants"] = " where id = {0}";
_whereExceptions["core_acl"] = " where tenant = {0} or tenant = -1";
_whereExceptions["core_subscription"] = " where tenant = {0} or tenant = -1";
_whereExceptions["core_subscriptionmethod"] = " where tenant = {0} or tenant = -1";
}
public List<string> GetTables()
{
var allowTables = new List<string>
{
"blogs_",
"bookmarking_",
"calendar_",
"core_",
"crm_",
"events_",
"files_",
"forum_",
"photo_",
"projects_",
"tenants_",
"webstudio_",
"wiki_",
};
var disallowTables = new List<string>
{
"core_settings",
"webstudio_uservisit",
"webstudio_useractivity",
"tenants_forbiden",
};
IEnumerable<string> tables;
if (_mysql)
{
tables = ExecuteList(CreateCommand("show tables"));
}
else
{
tables = _connect
.GetSchema("Tables")
.Select(@"TABLE_TYPE <> 'SYSTEM_TABLE'")
.Select(row => (string)row["TABLE_NAME"]);
}
return tables
.Where(t => allowTables.Any(a => t.StartsWith(a)) && !disallowTables.Any(d => t.StartsWith(d)))
.ToList();
}
public DataTable GetTable(string table, int tenant)
{
try
{
var dataTable = new DataTable(table);
var adapter = _factory.CreateDataAdapter();
adapter.SelectCommand = CreateCommand("select " + Quote(table) + ".* from " + Quote(table) + GetWhere(table, tenant));
_logger.Debug(adapter.SelectCommand.CommandText);
adapter.Fill(dataTable);
return dataTable;
}
catch (Exception error)
{
_logger.ErrorTableString(table, error);
throw;
}
}
public async Task SetTableAsync(DataTable table)
{
await using var tx = _connect.BeginTransaction();
try
{
if ("tenants_tenants".Equals(table.TableName, StringComparison.InvariantCultureIgnoreCase))
{
// remove last tenant
var tenant = await Queries.LastTenantAsync(_tenantDbContext);
if (tenant != null)
{
_tenantDbContext.Tenants.Remove(tenant);
await _tenantDbContext.SaveChangesAsync();
}
/* var tenantid = CreateCommand("select id from tenants_tenants order by id desc limit 1").ExecuteScalar();
CreateCommand("delete from tenants_tenants where id = " + tenantid).ExecuteNonQuery();*/
if (table.Columns.Contains("mappeddomain"))
{
foreach (var r in table.Rows.Cast<DataRow>())
{
r[table.Columns["mappeddomain"]] = null;
if (table.Columns.Contains("id"))
{
var tariff = await Queries.TariffAsync(_coreDbContext, tenant.Id);
tariff.TenantId = (int)r[table.Columns["id"]];
tariff.CreateOn = DateTime.Now;
// CreateCommand("update tenants_tariff set tenant = " + r[table.Columns["id"]] + " where tenant = " + tenantid).ExecuteNonQuery();
_coreDbContext.Entry(tariff).State = EntityState.Modified;
await _coreDbContext.SaveChangesAsync();
}
}
}
}
var sql = new StringBuilder("replace into " + Quote(table.TableName) + "(");
var tableColumns = GetColumnsFrom(table.TableName)
.Intersect(table.Columns.Cast<DataColumn>().Select(c => c.ColumnName), StringComparer.InvariantCultureIgnoreCase)
.ToList();
tableColumns.ForEach(column => sql.Append($"{Quote(column)}, "));
sql.Replace(", ", ") values (", sql.Length - 2, 2);
var insert = _connect.CreateCommand();
tableColumns.ForEach(column =>
{
sql.Append($"@{column}, ");
var p = insert.CreateParameter();
p.ParameterName = "@" + column;
insert.Parameters.Add(p);
});
sql.Replace(", ", ")", sql.Length - 2, 2);
insert.CommandText = sql.ToString();
foreach (var r in table.Rows.Cast<DataRow>())
{
foreach (var c in tableColumns)
{
((IDbDataParameter)insert.Parameters["@" + c]).Value = r[c];
}
insert.ExecuteNonQuery();
}
tx.Commit();
}
catch (Exception e)
{
_logger.ErrorTable(table, e);
}
}
public void Dispose()
{
_builder.Dispose();
_connect.Dispose();
}
public DbCommand CreateCommand(string sql)
{
var command = _connect.CreateCommand();
command.CommandText = sql;
return command;
}
public List<string> ExecuteList(DbCommand command)
{
var list = new List<string>();
using (var result = command.ExecuteReader())
{
while (result.Read())
{
list.Add(result.GetString(0));
}
}
return list;
}
private string Quote(string identifier)
{
return identifier;
}
private IEnumerable<string> GetColumnsFrom(string table)
{
if (_mysql)
{
return ExecuteList(CreateCommand("show columns from " + Quote(table)));
}
else
{
return _columns.Select($"TABLE_NAME = '{table}'")
.Select(r => r["COLUMN_NAME"].ToString());
}
}
private string GetWhere(string tableName, int tenant)
{
if (tenant == -1)
{
return string.Empty;
}
if (_whereExceptions.TryGetValue(tableName.ToLower(), out var exc))
{
return string.Format(exc, tenant);
}
var tenantColumn = GetColumnsFrom(tableName).FirstOrDefault(c => c.StartsWith("tenant", StringComparison.OrdinalIgnoreCase));
return tenantColumn != null ?
" where " + Quote(tenantColumn) + " = " + tenant :
" where 1 = 0";
}
}
static file class Queries
{
public static readonly Func<TenantDbContext, Task<DbTenant>> LastTenantAsync =
Microsoft.EntityFrameworkCore.EF.CompileAsyncQuery(
(TenantDbContext ctx) =>
ctx.Tenants.LastOrDefault());
public static readonly Func<CoreDbContext, int, Task<DbTariff>> TariffAsync =
Microsoft.EntityFrameworkCore.EF.CompileAsyncQuery(
(CoreDbContext ctx, int tenantId) =>
ctx.Tariffs.FirstOrDefault(t => t.TenantId == tenantId));
}

View File

@ -38,20 +38,18 @@ global using System.Text.Json.Serialization;
global using System.Text.RegularExpressions;
global using System.Xml;
global using System.Xml.Linq;
global using System.Xml.XPath;
global using ASC.Api.Utils;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.Log;
global using ASC.Common.Threading;
global using ASC.Common.Threading;
global using ASC.Common.Utils;
global using ASC.Core;
global using ASC.Core.Billing;
global using ASC.Core.ChunkedUploader;
global using ASC.Core.Common.Configuration;
global using ASC.Core.Common.EF;
global using ASC.Core.Common.EF.Context;
global using ASC.Core.Common.EF.Model;
global using ASC.Core.Tenants;
global using ASC.Core.Users;
@ -72,7 +70,8 @@ global using ASC.Data.Backup.Utils;
global using ASC.Data.Storage;
global using ASC.Data.Storage.Configuration;
global using ASC.Data.Storage.DiscStorage;
global using ASC.Data.Storage.ZipOperators;
global using ASC.Data.Storage.S3;
global using ASC.Data.Storage.DataOperators;
global using ASC.EventBus.Events;
global using ASC.Files.Core;
global using ASC.MessagingSystem.Core;

View File

@ -222,7 +222,7 @@ public class BackupWorker
}
}
internal static string GetBackupHash(string path)
internal static string GetBackupHashSHA(string path)
{
using (var sha256 = SHA256.Create())
using (var fileStream = File.OpenRead(path))
@ -231,6 +231,43 @@ public class BackupWorker
var hash = sha256.ComputeHash(fileStream);
return BitConverter.ToString(hash).Replace("-", string.Empty);
}
}
internal static string GetBackupHashMD5(string path, long chunkSize)
{
using (var md5 = MD5.Create())
using (var fileStream = File.OpenRead(path))
{var multipartSplitCount = 0;
var splitCount = fileStream.Length / chunkSize;
var mod = (int)(fileStream.Length - chunkSize * splitCount);
IEnumerable<byte> concatHash = new byte[] { };
for (var i = 0; i < splitCount; i++)
{
var offset = i == 0 ? 0 : chunkSize * i;
var chunk = GetChunk(fileStream, offset, (int)chunkSize);
var hash = md5.ComputeHash(chunk);
concatHash = concatHash.Concat(hash);
multipartSplitCount++;
}
if (mod != 0)
{
var chunk = GetChunk(fileStream, chunkSize * splitCount, mod);
var hash = md5.ComputeHash(chunk);
concatHash = concatHash.Concat(hash);
multipartSplitCount++;
}
var multipartHash = BitConverter.ToString(md5.ComputeHash(concatHash.ToArray())).Replace("-", string.Empty);
return multipartHash + "-" + multipartSplitCount;
}
}
private static byte[] GetChunk(Stream sourceStream, long offset, int count)
{
var buffer = new byte[count];
sourceStream.Position = offset;
sourceStream.Read(buffer, 0, count);
return buffer;
}
private BackupProgress ToBackupProgress(BaseBackupProgressItem progressItem)

View File

@ -31,9 +31,7 @@ namespace ASC.Data.Backup.Services;
public class BackupProgressItem : BaseBackupProgressItem
{
public Dictionary<string, string> StorageParams { get; set; }
public string TempFolder { get; set; }
private const string ArchiveFormat = "tar.gz";
public string TempFolder { get; set; }
private bool _isScheduled;
private Guid _userId;
@ -97,16 +95,21 @@ public class BackupProgressItem : BaseBackupProgressItem
_tempStream = scope.ServiceProvider.GetService<TempStream>();
var dateTime = _coreBaseSettings.Standalone ? DateTime.Now : DateTime.UtcNow;
var backupName = string.Format("{0}_{1:yyyy-MM-dd_HH-mm-ss}.{2}", (await _tenantManager.GetTenantAsync(TenantId)).Alias, dateTime, ArchiveFormat);
var tempFile = CrossPlatform.PathCombine(TempFolder, backupName);
var storagePath = tempFile;
string hash;
var tempFile = "";
var storagePath = "";
try
{
var backupStorage = await _backupStorageFactory.GetBackupStorageAsync(_storageType, TenantId, StorageParams);
var writer = await ZipWriteOperatorFactory.GetWriteOperatorAsync(_tempStream, _storageBasePath, backupName, TempFolder, _userId, backupStorage as IGetterWriteOperator);
var getter = backupStorage as IGetterWriteOperator;
var backupName = string.Format("{0}_{1:yyyy-MM-dd_HH-mm-ss}.{2}", (await _tenantManager.GetTenantAsync(TenantId)).Alias, dateTime, await getter.GetBackupExtensionAsync(_storageBasePath));
tempFile = CrossPlatform.PathCombine(TempFolder, backupName);
storagePath = tempFile;
var writer = await DataOperatorFactory.GetWriteOperatorAsync(_tempStream, _storageBasePath, backupName, TempFolder, _userId, getter);
_backupPortalTask.Init(TenantId, tempFile, _limit, writer);
@ -121,7 +124,7 @@ public class BackupProgressItem : BaseBackupProgressItem
if (writer.NeedUpload)
{
storagePath = await backupStorage.UploadAsync(_storageBasePath, tempFile, _userId);
hash = BackupWorker.GetBackupHash(tempFile);
hash = BackupWorker.GetBackupHashSHA(tempFile);
}
else
{

View File

@ -47,8 +47,8 @@
* 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.
*
*/
*/
namespace ASC.Data.Backup.Services;
[Transient(Additional = typeof(RestoreProgressItemExtention))]
@ -82,7 +82,7 @@ public class RestoreProgressItem : BaseBackupProgressItem
_notifyHelper = notifyHelper;
_coreBaseSettings = coreBaseSettings;
BackupProgressItemEnum = BackupProgressItemEnum.Restore;
BackupProgressItemEnum = BackupProgressItemEnum.Restore;
}
public BackupStorageType StorageType { get; set; }
@ -106,9 +106,7 @@ public class RestoreProgressItem : BaseBackupProgressItem
protected override async Task DoJob()
{
Tenant tenant = null;
var tempFile = PathHelper.GetTempFileName(TempFolder);
var tempFile = "";
try
{
await using var scope = _serviceScopeProvider.CreateAsyncScope();
@ -127,16 +125,21 @@ public class RestoreProgressItem : BaseBackupProgressItem
var storage = await _backupStorageFactory.GetBackupStorageAsync(StorageType, TenantId, StorageParams);
await storage.DownloadAsync(StoragePath, tempFile);
tempFile = await storage.DownloadAsync(StoragePath, TempFolder);
if (!_coreBaseSettings.Standalone)
{
var backupHash = BackupWorker.GetBackupHash(tempFile);
var record = await _backupRepository.GetBackupRecordAsync(backupHash, TenantId);
var shaHash = BackupWorker.GetBackupHashSHA(tempFile);
var record = await _backupRepository.GetBackupRecordAsync(shaHash, TenantId);
if (record == null)
{
throw new Exception(BackupResource.BackupNotFound);
{
var md5Hash = BackupWorker.GetBackupHashMD5(tempFile, S3Storage.ChunkSize);
record = await _backupRepository.GetBackupRecordAsync(md5Hash, TenantId);
if (record == null)
{
throw new Exception(BackupResource.BackupNotFound);
}
}
}

View File

@ -73,11 +73,13 @@ public class ConsumerBackupStorage : IBackupStorage, IGetterWriteOperator
return storagePath;
}
public async Task DownloadAsync(string storagePath, string targetLocalPath)
public async Task<string> DownloadAsync(string storagePath, string targetLocalPath)
{
var tempPath = Path.Combine(targetLocalPath, Path.GetFileName(storagePath));
await using var source = await _store.GetReadStreamAsync(Domain, storagePath);
await using var destination = File.OpenWrite(targetLocalPath);
await using var destination = File.OpenWrite(tempPath);
await source.CopyToAsync(destination);
return tempPath;
}
public async Task DeleteAsync(string storagePath)
@ -119,6 +121,11 @@ public class ConsumerBackupStorage : IBackupStorage, IGetterWriteOperator
TempPath = title,
UploadId = await _store.InitiateChunkedUploadAsync(Domain, title)
};
return _store.CreateDataWriteOperator(session, _sessionHolder);
return _store.CreateDataWriteOperator(session, _sessionHolder, true);
}
public Task<string> GetBackupExtensionAsync(string storageBasePath)
{
return Task.FromResult(_store.GetBackupExtension(true));
}
}

View File

@ -85,18 +85,16 @@ public class DocumentsBackupStorage : IBackupStorage, IGetterWriteOperator
return await Upload(folderId, localPath);
}
public async Task DownloadAsync(string fileId, string targetLocalPath)
public async Task<string> DownloadAsync(string fileId, string targetLocalPath)
{
await _tenantManager.SetCurrentTenantAsync(_tenantId);
if (int.TryParse(fileId, out var fId))
{
await DownloadDaoAsync(fId, targetLocalPath);
return;
return await DownloadDaoAsync(fId, targetLocalPath);
}
await DownloadDaoAsync(fileId, targetLocalPath);
return await DownloadDaoAsync(fileId, targetLocalPath);
}
public async Task DeleteAsync(string fileId)
@ -166,7 +164,7 @@ public class DocumentsBackupStorage : IBackupStorage, IGetterWriteOperator
return file.Id;
}
private async Task DownloadDaoAsync<T>(T fileId, string targetLocalPath)
private async Task<string> DownloadDaoAsync<T>(T fileId, string targetLocalPath)
{
await _tenantManager.SetCurrentTenantAsync(_tenantId);
var fileDao = await GetFileDaoAsync<T>();
@ -177,8 +175,10 @@ public class DocumentsBackupStorage : IBackupStorage, IGetterWriteOperator
}
await using var source = await fileDao.GetFileStreamAsync(file);
await using var destination = File.OpenWrite(targetLocalPath);
var destPath = Path.Combine(targetLocalPath, file.Title);
await using var destination = File.OpenWrite(destPath);
await source.CopyToAsync(destination);
return destPath;
}
private async Task DeleteDaoAsync<T>(T fileId)
@ -192,7 +192,6 @@ public class DocumentsBackupStorage : IBackupStorage, IGetterWriteOperator
var fileDao = await GetFileDaoAsync<T>();
try
{
var file = await fileDao.GetFileAsync(fileId);
return file != null && file.RootFolderType != FolderType.TRASH;
@ -229,6 +228,21 @@ public class DocumentsBackupStorage : IBackupStorage, IGetterWriteOperator
}
}
public async Task<string> GetBackupExtensionAsync(string storageBasePath)
{
await _tenantManager.SetCurrentTenantAsync(_tenantId);
if (int.TryParse(storageBasePath, out var fId))
{
var folderDao = GetFolderDao<int>();
return await folderDao.GetBackupExtensionAsync(fId);
}
else
{
var folderDao = GetFolderDao<string>();
return await folderDao.GetBackupExtensionAsync(storageBasePath);
}
}
private async Task<CommonChunkedUploadSession> InitUploadChunkAsync<T>(T folderId, string title)
{
var folderDao = GetFolderDao<T>();

View File

@ -32,5 +32,5 @@ public interface IBackupStorage
Task<string> GetPublicLinkAsync(string storagePath);
Task<string> UploadAsync(string storageBasePath, string localPath, Guid userId);
Task DeleteAsync(string storagePath);
Task DownloadAsync(string storagePath, string targetLocalPath);
Task<string> DownloadAsync(string storagePath, string targetLocalPath);
}

View File

@ -45,10 +45,11 @@ public class LocalBackupStorage : IBackupStorage, IGetterWriteOperator
return Task.FromResult(storagePath);
}
public Task DownloadAsync(string storagePath, string targetLocalPath)
public Task<string> DownloadAsync(string storagePath, string targetLocalPath)
{
var tempPath = Path.Combine(storagePath, Path.GetFileName(targetLocalPath));
File.Copy(storagePath, targetLocalPath, true);
return Task.CompletedTask;
return Task.FromResult(tempPath);
}
public Task DeleteAsync(string storagePath)
@ -71,4 +72,9 @@ public class LocalBackupStorage : IBackupStorage, IGetterWriteOperator
{
return Task.FromResult<IDataWriteOperator>(null);
}
public Task<string> GetBackupExtensionAsync(string storageBasePath)
{
return Task.FromResult("tar.gz");
}
}

View File

@ -710,17 +710,13 @@ public class BackupPortalTask : PortalTaskBase
foreach (var file in group)
{
var storage = await StorageFactory.GetStorageAsync(TenantId, group.Key);
var file1 = file;
Stream fileStream = null;
await ActionInvoker.Try(async state =>
try
{
var f = (BackupFileInfo)state;
fileStream = await storage.GetReadStreamAsync(f.Domain, f.Path);
}, file, 5, error => _logger.WarningCanNotBackupFile(file1.Module, file1.Path, error));
if(fileStream != null)
await writer.WriteEntryAsync(file.GetZipKey(), file.Domain, file.Path, storage);
}
catch(Exception error)
{
await writer.WriteEntryAsync(file1.GetZipKey(), fileStream);
fileStream.Dispose();
_logger.WarningCanNotBackupFile(file.Module, file.Path, error);
}
SetCurrentStepProgress((int)(++filesProcessed * 100 / (double)filesCount));
}

View File

@ -94,7 +94,7 @@ public class DeletePortalTask : PortalTaskBase
var domains = StorageFactoryConfig.GetDomainList(module);
foreach (var domain in domains)
{
await ActionInvoker.Try(async state => await storage.DeleteFilesAsync((string)state, "\\", "*.*", true), domain, 5,
await ActionInvoker.TryAsync(async state => await storage.DeleteFilesAsync((string)state, "\\", "*.*", true), domain, 5,
onFailure: error => _logger.WarningCanNotDeleteFilesForDomain(domain, error));
}
await storage.DeleteFilesAsync("\\", "*.*", true);

View File

@ -90,7 +90,7 @@ public class RestorePortalTask : PortalTaskBase
_options.DebugBeginRestoreData();
using (var dataReader = new ZipReadOperator(BackupFilePath))
using (var dataReader = DataOperatorFactory.GetReadOperator(BackupFilePath))
{
await using (var entry = dataReader.GetEntry(KeyHelper.GetDumpKey()))
{
@ -421,7 +421,7 @@ public class RestorePortalTask : PortalTaskBase
foreach (var domain in domains)
{
await ActionInvoker.Try(
await ActionInvoker.TryAsync(
async state =>
{
if (await storage.IsDirectoryAsync((string)state))

View File

@ -96,7 +96,7 @@ public class TransferPortalTask : PortalTaskBase
//save db data to temporary file
var backupTask = _serviceProvider.GetService<BackupPortalTask>();
backupTask.Init(TenantId, backupFilePath, Limit, ZipWriteOperatorFactory.GetDefaultWriteOperator(_tempStream, backupFilePath));
backupTask.Init(TenantId, backupFilePath, Limit, DataOperatorFactory.GetDefaultWriteOperator(_tempStream, backupFilePath));
backupTask.ProcessStorage = false;
backupTask.ProgressChanged += (sender, args) => SetCurrentStepProgress(args.Progress);
foreach (var moduleName in _ignoredModules)

View File

@ -207,11 +207,16 @@ public abstract class BaseStorage : IDataStore
public virtual IDataWriteOperator CreateDataWriteOperator(
CommonChunkedUploadSession chunkedUploadSession,
CommonChunkedUploadSessionHolder sessionHolder)
CommonChunkedUploadSessionHolder sessionHolder, bool isConsumerStorage = false)
{
return new ChunkZipWriteOperator(_tempStream, chunkedUploadSession, sessionHolder);
}
public virtual string GetBackupExtension(bool isConsumerStorage = false)
{
return "tar.gz";
}
#endregion
public abstract Task DeleteAsync(string domain, string path);

View File

@ -32,8 +32,9 @@ public class CommonChunkedUploadSessionHolder
public static readonly TimeSpan SlidingExpiration = TimeSpan.FromHours(12);
private readonly TempPath _tempPath;
private readonly string _domain;
public readonly string Domain;
public long MaxChunkUploadSize;
public string TempDomain;
public const string StoragePath = "sessions";
private readonly object _locker = new object();
@ -46,24 +47,24 @@ public class CommonChunkedUploadSessionHolder
{
_tempPath = tempPath;
DataStore = dataStore;
_domain = domain;
Domain = domain;
MaxChunkUploadSize = maxChunkUploadSize;
}
public async Task StoreAsync(CommonChunkedUploadSession s)
{
await using var stream = s.Serialize();
await DataStore.SavePrivateAsync(_domain, GetPathWithId(s.Id), stream, s.Expired);
await DataStore.SavePrivateAsync(Domain, GetPathWithId(s.Id), stream, s.Expired);
}
public async Task RemoveAsync(CommonChunkedUploadSession s)
{
await DataStore.DeleteAsync(_domain, GetPathWithId(s.Id));
await DataStore.DeleteAsync(Domain, GetPathWithId(s.Id));
}
public async Task<Stream> GetStreamAsync(string sessionId)
{
return await DataStore.GetReadStreamAsync(_domain, GetPathWithId(sessionId));
return await DataStore.GetReadStreamAsync(Domain, GetPathWithId(sessionId));
}
public async ValueTask InitAsync(CommonChunkedUploadSession chunkedUploadSession)
@ -75,7 +76,7 @@ public class CommonChunkedUploadSessionHolder
}
var tempPath = Guid.NewGuid().ToString();
var uploadId = await DataStore.InitiateChunkedUploadAsync(_domain, tempPath);
var uploadId = await DataStore.InitiateChunkedUploadAsync(Domain, tempPath);
chunkedUploadSession.TempPath = tempPath;
chunkedUploadSession.UploadId = uploadId;
@ -87,13 +88,13 @@ public class CommonChunkedUploadSessionHolder
var uploadId = uploadSession.UploadId;
var eTags = uploadSession.GetItemOrDefault<Dictionary<int, string>>("ETag");
await DataStore.FinalizeChunkedUploadAsync(_domain, tempPath, uploadId, eTags);
await DataStore.FinalizeChunkedUploadAsync(Domain, tempPath, uploadId, eTags);
return Path.GetFileName(tempPath);
}
public async Task MoveAsync(CommonChunkedUploadSession chunkedUploadSession, string newPath, bool quotaCheckFileSize = true)
{
await DataStore.MoveAsync(_domain, chunkedUploadSession.TempPath, string.Empty, newPath, quotaCheckFileSize);
await DataStore.MoveAsync(Domain, chunkedUploadSession.TempPath, string.Empty, newPath, quotaCheckFileSize);
}
public async Task AbortAsync(CommonChunkedUploadSession uploadSession)
@ -103,7 +104,7 @@ public class CommonChunkedUploadSessionHolder
var tempPath = uploadSession.TempPath;
var uploadId = uploadSession.UploadId;
await DataStore.AbortChunkedUploadAsync(_domain, tempPath, uploadId);
await DataStore.AbortChunkedUploadAsync(Domain, tempPath, uploadId);
}
else if (!string.IsNullOrEmpty(uploadSession.ChunksBuffer))
{
@ -125,7 +126,7 @@ public class CommonChunkedUploadSessionHolder
uploadSession.BytesUploaded += length;
}
var eTag = await DataStore.UploadChunkAsync(_domain, tempPath, uploadId, stream, MaxChunkUploadSize, chunkNumber, length);
var eTag = await DataStore.UploadChunkAsync(Domain, tempPath, uploadId, stream, MaxChunkUploadSize, chunkNumber, length);
lock (_locker)
{

View File

@ -1,30 +1,30 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Backup;
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.DataOperators;
public static class ActionInvoker
{
@ -47,7 +47,7 @@ public static class ActionInvoker
Action<Exception> onAttemptFailure = null,
int sleepMs = 1000,
bool isSleepExponential = true)
{
{
ArgumentNullException.ThrowIfNull(action);
var countAttempts = 0;
@ -77,7 +77,18 @@ public static class ActionInvoker
}
}
public static async Task Try(
public static async Task TryAsync(
Func<Task> action,
int maxAttempts,
Action<Exception> onFailure = null,
Action<Exception> onAttemptFailure = null,
int sleepMs = 1000,
bool isSleepExponential = true)
{
await TryAsync(state => action(), null, maxAttempts, onFailure, onAttemptFailure, sleepMs, isSleepExponential);
}
public static async Task TryAsync(
Func<object, Task> action,
object state,
int maxAttempts,
@ -85,7 +96,7 @@ public static class ActionInvoker
Action<Exception> onAttemptFailure = null,
int sleepMs = 1000,
bool isSleepExponential = true)
{
{
ArgumentNullException.ThrowIfNull(action);
var countAttempts = 0;

View File

@ -24,9 +24,9 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.ZipOperators;
namespace ASC.Data.Storage.DataOperators;
public static class ZipWriteOperatorFactory
public static class DataOperatorFactory
{
public static async Task<IDataWriteOperator> GetWriteOperatorAsync(TempStream tempStream, string storageBasePath, string title, string tempFolder, Guid userId, IGetterWriteOperator getter)
{
@ -39,5 +39,17 @@ public static class ZipWriteOperatorFactory
{
return new ZipWriteOperator(tempStream, backupFilePath);
}
public static IDataReadOperator GetReadOperator(string targetFile)
{
if (targetFile.EndsWith("tar.gz"))
{
return new ZipReadOperator(targetFile);
}
else
{
return new TarReadOperator(targetFile);
}
}
}

View File

@ -24,11 +24,12 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.ZipOperators;
namespace ASC.Data.Storage.DataOperators;
public interface IDataWriteOperator : IAsyncDisposable
{
Task WriteEntryAsync(string key, Stream stream);
Task WriteEntryAsync(string tarKey, Stream stream);
Task WriteEntryAsync(string tarKey, string domain, string path, IDataStore store);
bool NeedUpload { get; }
string Hash { get; }
string StoragePath { get; }

View File

@ -24,9 +24,10 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.ZipOperators;
namespace ASC.Data.Storage.DataOperators;
public interface IGetterWriteOperator
{
Task<IDataWriteOperator> GetWriteOperatorAsync(string storageBasePath, string title, Guid userId);
Task<string> GetBackupExtensionAsync(string storageBasePath);
}

View File

@ -0,0 +1,58 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.DataOperators;
public abstract class BaseReadOperator: IDataReadOperator
{
internal string _tmpdir;
public Stream GetEntry(string key)
{
var filePath = Path.Combine(_tmpdir, key);
return File.Exists(filePath) ? File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read) : null;
}
public IEnumerable<string> GetEntries(string key)
{
var path = Path.Combine(_tmpdir, key);
var files = Directory.EnumerateFiles(path);
return files;
}
public IEnumerable<string> GetDirectories(string key)
{
var path = Path.Combine(_tmpdir, key);
var files = Directory.EnumerateDirectories(path);
return files;
}
public void Dispose()
{
if (Directory.Exists(_tmpdir))
{
Directory.Delete(_tmpdir, true);
}
}
}

View File

@ -1,4 +1,4 @@
// (c) Copyright Ascensio System SIA 2010-2022
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
@ -24,30 +24,19 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Backup;
public interface IBackupProvider
namespace ASC.Data.Storage.DataOperators;
public class TarReadOperator: BaseReadOperator
{
string Name { get; }
event EventHandler<ProgressChangedEventArgs> ProgressChanged;
Task<IEnumerable<XElement>> GetElements(int tenant, string[] configs, IDataWriteOperator writer);
Task LoadFromAsync(IEnumerable<XElement> elements, int tenant, string[] configs, IDataReadOperator reader);
}
public class ProgressChangedEventArgs : EventArgs
{
public string Status { get; private set; }
public double Progress { get; private set; }
public bool Completed { get; private set; }
public ProgressChangedEventArgs(string status, double progress)
: this(status, progress, false) { }
public ProgressChangedEventArgs(string status, double progress, bool completed)
public TarReadOperator(string targetFile)
{
Status = status;
Progress = progress;
Completed = completed;
_tmpdir = Path.Combine(Path.GetDirectoryName(targetFile), Path.GetFileNameWithoutExtension(targetFile).Replace('>', '_').Replace(':', '_').Replace('?', '_'));
using (var stream = File.OpenRead(targetFile))
using (var tarOutputStream = TarArchive.CreateInputTarArchive(stream, Encoding.UTF8))
{
tarOutputStream.ExtractContents(_tmpdir);
}
File.Delete(targetFile);
}
}

View File

@ -0,0 +1,43 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.DataOperators;
public class ZipReadOperator : BaseReadOperator
{
public ZipReadOperator(string targetFile)
{
_tmpdir = Path.Combine(Path.GetDirectoryName(targetFile), Path.GetFileNameWithoutExtension(targetFile).Replace('>', '_').Replace(':', '_').Replace('?', '_'));
using (var stream = File.OpenRead(targetFile))
using (var reader = new GZipInputStream(stream))
using (var tarOutputStream = TarArchive.CreateInputTarArchive(reader, Encoding.UTF8))
{
tarOutputStream.ExtractContents(_tmpdir);
}
File.Delete(targetFile);
}
}

View File

@ -24,7 +24,7 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.ZipOperators;
namespace ASC.Data.Storage.DataOperators;
public class ChunkZipWriteOperator : IDataWriteOperator
{
@ -63,7 +63,21 @@ public class ChunkZipWriteOperator : IDataWriteOperator
_sha = SHA256.Create();
}
public async Task WriteEntryAsync(string key, Stream stream)
public async Task WriteEntryAsync(string tarKey, string domain, string path, IDataStore store)
{
Stream fileStream = null;
await ActionInvoker.TryAsync(async () =>
{
fileStream = await store.GetReadStreamAsync(domain, path);
}, 5, error => throw error);
if (fileStream != null)
{
await WriteEntryAsync(tarKey, fileStream);
fileStream.Dispose();
}
}
public async Task WriteEntryAsync(string tarKey, Stream stream)
{
if (_fileStream == null)
{
@ -73,7 +87,7 @@ public class ChunkZipWriteOperator : IDataWriteOperator
await using (var buffered = _tempStream.GetBuffered(stream))
{
var entry = TarEntry.CreateTarEntry(key);
var entry = TarEntry.CreateTarEntry(tarKey);
entry.Size = buffered.Length;
await _tarOutputStream.PutNextEntryAsync(entry, default);
buffered.Position = 0;

View File

@ -0,0 +1,97 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.DataOperators;
public class S3TarWriteOperator : IDataWriteOperator
{
private readonly CommonChunkedUploadSession _chunkedUploadSession;
private readonly CommonChunkedUploadSessionHolder _sessionHolder;
private readonly S3Storage _store;
private readonly string _domain;
private readonly string _key;
public string Hash { get; private set; }
public string StoragePath { get; private set; }
public bool NeedUpload => false;
public S3TarWriteOperator(CommonChunkedUploadSession chunkedUploadSession, CommonChunkedUploadSessionHolder sessionHolder)
{
_chunkedUploadSession = chunkedUploadSession;
_sessionHolder = sessionHolder;
_store = _sessionHolder.DataStore as S3Storage;
_key = _chunkedUploadSession.TempPath;
_domain = _sessionHolder.TempDomain;
}
public async Task WriteEntryAsync(string tarKey, string domain, string path, IDataStore store)
{
if (store is S3Storage)
{
var s3Store = store as S3Storage;
var fullPath = s3Store.MakePath(domain, path);
await _store.ConcatFileAsync(fullPath, tarKey, _domain, _key);
}
else
{
Stream fileStream = null;
await ActionInvoker.TryAsync(async () =>
{
fileStream = await store.GetReadStreamAsync(domain, path);
}, 5, error => throw error);
if (fileStream != null)
{
await WriteEntryAsync(tarKey, fileStream);
fileStream.Dispose();
}
}
}
public async Task WriteEntryAsync(string tarKey, Stream stream)
{
await _store.ConcatFileStreamAsync(stream, tarKey, _domain, _key);
}
public async ValueTask DisposeAsync()
{
await _store.AddEndAsync(_domain ,_key);
await _store.RemoveFirstBlockAsync(_domain ,_key);
var contentLength = await _store.GetFileSizeAsync(_domain, _key);
Hash = (await _store.GetFileEtagAsync(_domain, _key)).Trim('\"');
(var uploadId, var eTags, var partNumber) = await _store.InitiateConcatAsync(_domain, _key, lastInit: true);
_chunkedUploadSession.BytesUploaded = contentLength;
_chunkedUploadSession.BytesTotal = contentLength;
_chunkedUploadSession.UploadId = uploadId;
_chunkedUploadSession.Items["ETag"] = eTags.ToDictionary(e => e.PartNumber, e => e.ETag);
_chunkedUploadSession.Items["ChunksUploaded"] = (partNumber - 1).ToString();
StoragePath = await _sessionHolder.FinalizeAsync(_chunkedUploadSession);
}
}

View File

@ -24,7 +24,7 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.ZipOperators;
namespace ASC.Data.Storage.DataOperators;
public class S3ZipWriteOperator : IDataWriteOperator
{
@ -67,7 +67,21 @@ public class S3ZipWriteOperator : IDataWriteOperator
_sha = SHA256.Create();
}
public async Task WriteEntryAsync(string key, Stream stream)
public async Task WriteEntryAsync(string tarKey, string domain, string path, IDataStore store)
{
Stream fileStream = null;
await ActionInvoker.TryAsync(async () =>
{
fileStream = await store.GetReadStreamAsync(domain, path);
}, 5, error => throw error);
if (fileStream != null)
{
await WriteEntryAsync(tarKey, fileStream);
fileStream.Dispose();
}
}
public async Task WriteEntryAsync(string tarKey, Stream stream)
{
if (_fileStream == null)
{
@ -77,7 +91,7 @@ public class S3ZipWriteOperator : IDataWriteOperator
await using (var buffered = _tempStream.GetBuffered(stream))
{
var entry = TarEntry.CreateTarEntry(key);
var entry = TarEntry.CreateTarEntry(tarKey);
entry.Size = buffered.Length;
await _tarOutputStream.PutNextEntryAsync(entry, default);
buffered.Position = 0;

View File

@ -24,7 +24,7 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Storage.ZipOperators;
namespace ASC.Data.Storage.DataOperators;
public class ZipWriteOperator : IDataWriteOperator
@ -54,11 +54,25 @@ public class ZipWriteOperator : IDataWriteOperator
_tarOutputStream = new TarOutputStream(_gZipOutputStream, Encoding.UTF8);
}
public async Task WriteEntryAsync(string key, Stream stream)
public async Task WriteEntryAsync(string tarKey, string domain, string path, IDataStore store)
{
Stream fileStream = null;
await ActionInvoker.TryAsync(async () =>
{
fileStream = await store.GetReadStreamAsync(domain, path);
}, 5, error => throw error);
if (fileStream != null)
{
await WriteEntryAsync(tarKey, fileStream);
fileStream.Dispose();
}
}
public async Task WriteEntryAsync(string tarKey, Stream stream)
{
await using (var buffered = _tempStream.GetBuffered(stream))
{
var entry = TarEntry.CreateTarEntry(key);
var entry = TarEntry.CreateTarEntry(tarKey);
entry.Size = buffered.Length;
await _tarOutputStream.PutNextEntryAsync(entry, default);
buffered.Position = 0;
@ -73,50 +87,3 @@ public class ZipWriteOperator : IDataWriteOperator
await _tarOutputStream.DisposeAsync();
}
}
public class ZipReadOperator : IDataReadOperator
{
private readonly string tmpdir;
public ZipReadOperator(string targetFile)
{
tmpdir = Path.Combine(Path.GetDirectoryName(targetFile), Path.GetFileNameWithoutExtension(targetFile).Replace('>', '_').Replace(':', '_').Replace('?', '_'));
using (var stream = File.OpenRead(targetFile))
using (var reader = new GZipInputStream(stream))
using (var tarOutputStream = TarArchive.CreateInputTarArchive(reader, Encoding.UTF8))
{
tarOutputStream.ExtractContents(tmpdir);
}
File.Delete(targetFile);
}
public Stream GetEntry(string key)
{
var filePath = Path.Combine(tmpdir, key);
return File.Exists(filePath) ? File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read) : null;
}
public IEnumerable<string> GetEntries(string key)
{
var path = Path.Combine(tmpdir, key);
var files = Directory.EnumerateFiles(path);
return files;
}
public IEnumerable<string> GetDirectories(string key)
{
var path = Path.Combine(tmpdir, key);
var files = Directory.EnumerateDirectories(path);
return files;
}
public void Dispose()
{
if (Directory.Exists(tmpdir))
{
Directory.Delete(tmpdir, true);
}
}
}

View File

@ -30,7 +30,7 @@ global using System.Net;
global using System.Net.Http.Headers;
global using System.Runtime.Serialization;
global using System.Security.Cryptography;
global using System.ServiceModel;
global using System.ServiceModel;
global using System.Text;
global using System.Text.Json;
global using System.Text.Json.Serialization;
@ -39,7 +39,10 @@ global using System.Web;
global using Amazon;
global using Amazon.CloudFront;
global using Amazon.CloudFront.Model;
global using Amazon.Extensions.S3.Encryption;
global using Amazon.Extensions.S3.Encryption.Primitives;
global using Amazon.S3;
global using Amazon.S3.Internal;
global using Amazon.S3.Model;
global using Amazon.S3.Transfer;
global using Amazon.Util;
@ -66,7 +69,8 @@ global using ASC.Data.Storage.GoogleCloud;
global using ASC.Data.Storage.Log;
global using ASC.Data.Storage.RackspaceCloud;
global using ASC.Data.Storage.S3;
global using ASC.Data.Storage.ZipOperators;
global using ASC.Data.Storage.Tar;
global using ASC.Data.Storage.DataOperators;
global using ASC.EventBus.Events;
global using ASC.Notify.Messages;
global using ASC.Protos.Migration;

View File

@ -33,7 +33,9 @@ public interface IDataStore
{
IDataWriteOperator CreateDataWriteOperator(
CommonChunkedUploadSession chunkedUploadSession,
CommonChunkedUploadSessionHolder sessionHolder);
CommonChunkedUploadSessionHolder sessionHolder,
bool isConsumerStorage = false);
string GetBackupExtension(bool isConsumerStorage = false);
IQuotaController QuotaController { get; set; }

View File

@ -24,16 +24,13 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Amazon.Extensions.S3.Encryption;
using Amazon.Extensions.S3.Encryption.Primitives;
using Amazon.S3.Internal;
namespace ASC.Data.Storage.S3;
[Scope]
public class S3Storage : BaseStorage
{
public override bool IsSupportCdnUri => true;
public static long ChunkSize { get; } = 50 * 1024 * 1024;
public override bool IsSupportChunking => true;
private readonly List<string> _domains = new List<string>();
@ -60,7 +57,7 @@ public class S3Storage : BaseStorage
private EncryptionMethod _encryptionMethod = EncryptionMethod.None;
private string _encryptionKey;
private readonly IConfiguration _configuration;
private readonly CoreBaseSettings _coreBaseSettings;
public S3Storage(
TempStream tempStream,
@ -71,12 +68,12 @@ public class S3Storage : BaseStorage
ILoggerProvider factory,
ILogger<S3Storage> options,
IHttpClientFactory clientFactory,
IConfiguration configuration,
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper,
QuotaSocketManager quotaSocketManager)
QuotaSocketManager quotaSocketManager,
CoreBaseSettings coreBaseSettings)
: base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, factory, options, clientFactory, tenantQuotaFeatureStatHelper, quotaSocketManager)
{
_configuration = configuration;
_coreBaseSettings = coreBaseSettings;
}
public Uri GetUriInternal(string path)
@ -461,9 +458,28 @@ public class S3Storage : BaseStorage
}
public override IDataWriteOperator CreateDataWriteOperator(CommonChunkedUploadSession chunkedUploadSession,
CommonChunkedUploadSessionHolder sessionHolder)
CommonChunkedUploadSessionHolder sessionHolder, bool isConsumerStorage = false)
{
return new S3ZipWriteOperator(_tempStream, chunkedUploadSession, sessionHolder);
if (_coreBaseSettings.Standalone || isConsumerStorage)
{
return new S3ZipWriteOperator(_tempStream, chunkedUploadSession, sessionHolder);
}
else
{
return new S3TarWriteOperator(chunkedUploadSession, sessionHolder);
}
}
public override string GetBackupExtension(bool isConsumerStorage = false)
{
if (_coreBaseSettings.Standalone || isConsumerStorage)
{
return "tar.gz";
}
else
{
return "tar";
}
}
#endregion
@ -570,9 +586,9 @@ public class S3Storage : BaseStorage
if (string.IsNullOrEmpty(QuotaController.ExcludePattern) ||
!Path.GetFileName(s3Object.Key).StartsWith(QuotaController.ExcludePattern))
{
await QuotaUsedDeleteAsync(domain, s3Object.Size);
}
}
await QuotaUsedDeleteAsync(domain, s3Object.Size);
}
}
}
}
@ -1052,7 +1068,7 @@ public class S3Storage : BaseStorage
_cdnKeyPairId = props["cdn_keyPairId"];
_cdnPrivateKeyPath = props["cdn_privateKeyPath"];
CdnDistributionDomain = props["cdn_distributionDomain"];
}
}
}
props.TryGetValue("subdir", out _subDir);
@ -1218,7 +1234,7 @@ public class S3Storage : BaseStorage
return s30Objects;
}
private string MakePath(string domain, string path)
public string MakePath(string domain, string path)
{
string result;
@ -1304,7 +1320,7 @@ public class S3Storage : BaseStorage
var uploadId = initResponse.UploadId;
var partSize = 500 * 1024 * 1024L;//500 megabytes
var partSize = ChunkSize;
var uploadTasks = new List<Task<CopyPartResponse>>();
@ -1369,6 +1385,260 @@ public class S3Storage : BaseStorage
}
}
public async Task ConcatFileStreamAsync(Stream stream, string tarKey, string destinationDomain, string destinationKey)
{
(var uploadId, var eTags, var partNumber) = await InitiateConcatAsync(destinationDomain, destinationKey);
using var s3 = GetClient();
var destinationPath = MakePath(destinationDomain, destinationKey);
var blockSize = 512;
long prevFileSize = 0;
try
{
var objResult = await s3.GetObjectMetadataAsync(_bucket, destinationPath);
prevFileSize = objResult.ContentLength;
}
catch { }
var header = BuilderHeaders.CreateHeader(tarKey, stream.Length);
var ms = new MemoryStream();
if (prevFileSize % blockSize != 0)
{
var endBlock = new byte[blockSize - prevFileSize % blockSize];
ms.Write(endBlock);
}
ms.Write(header);
stream.Position = 0;
stream.CopyTo(ms);
stream.Dispose();
stream = ms;
stream.Position = 0;
prevFileSize = stream.Length;
var uploadRequest = new UploadPartRequest
{
BucketName = _bucket,
Key = destinationPath,
UploadId = uploadId,
PartNumber = partNumber,
InputStream = stream
};
eTags.Add(new PartETag(partNumber, (await s3.UploadPartAsync(uploadRequest)).ETag));
var completeRequest = new CompleteMultipartUploadRequest
{
BucketName = _bucket,
Key = destinationPath,
UploadId = uploadId,
PartETags = eTags
};
await s3.CompleteMultipartUploadAsync(completeRequest);
}
public async Task ConcatFileAsync(string pathFile, string tarKey, string destinationDomain, string destinationKey)
{
(var uploadId, var eTags, var partNumber) = await InitiateConcatAsync(destinationDomain, destinationKey);
using var s3 = GetClient();
var destinationPath = MakePath(destinationDomain, destinationKey);
var blockSize = 512;
long prevFileSize = 0;
try
{
var objResult = await s3.GetObjectMetadataAsync(_bucket, destinationPath);
prevFileSize = objResult.ContentLength;
}
catch{}
var objFile = await s3.GetObjectMetadataAsync(_bucket, pathFile);
var header = BuilderHeaders.CreateHeader(tarKey, objFile.ContentLength);
using var stream = new MemoryStream();
if (prevFileSize % blockSize != 0)
{
var endBlock = new byte[blockSize - prevFileSize % blockSize];
stream.Write(endBlock);
}
stream.Write(header);
stream.Position = 0;
prevFileSize = objFile.ContentLength;
var uploadRequest = new UploadPartRequest
{
BucketName = _bucket,
Key = destinationPath,
UploadId = uploadId,
PartNumber = partNumber,
InputStream = stream
};
eTags.Add(new PartETag(partNumber, (await s3.UploadPartAsync(uploadRequest)).ETag));
var completeRequest = new CompleteMultipartUploadRequest
{
BucketName = _bucket,
Key = destinationPath,
UploadId = uploadId,
PartETags = eTags
};
var completeUploadResponse = await s3.CompleteMultipartUploadAsync(completeRequest);
/*******/
(uploadId, eTags, partNumber) = await InitiateConcatAsync(destinationDomain, destinationKey);
var copyRequest = new CopyPartRequest
{
DestinationBucket = _bucket,
DestinationKey = destinationPath,
SourceBucket = _bucket,
SourceKey = pathFile,
UploadId = uploadId,
PartNumber = partNumber
};
eTags.Add(new PartETag(partNumber, (await s3.CopyPartAsync(copyRequest)).ETag));
completeRequest = new CompleteMultipartUploadRequest
{
BucketName = _bucket,
Key = destinationPath,
UploadId = uploadId,
PartETags = eTags
};
completeUploadResponse = await s3.CompleteMultipartUploadAsync(completeRequest);
}
public async Task AddEndAsync(string domain, string key)
{
using var s3 = GetClient();
var path = MakePath(domain, key);
var blockSize = 512;
(var uploadId, var eTags, var partNumber) = await InitiateConcatAsync(domain, key);
var obj = await s3.GetObjectMetadataAsync(_bucket, path);
var buffer = new byte[blockSize - obj.ContentLength % blockSize + blockSize * 2];
var stream = new MemoryStream();
stream.Write(buffer);
stream.Position = 0;
var uploadRequest = new UploadPartRequest
{
BucketName = _bucket,
Key = path,
UploadId = uploadId,
PartNumber = partNumber,
InputStream = stream
};
eTags.Add(new PartETag(partNumber, (await s3.UploadPartAsync(uploadRequest)).ETag));
var completeRequest = new CompleteMultipartUploadRequest
{
BucketName = _bucket,
Key = path,
UploadId = uploadId,
PartETags = eTags
};
await s3.CompleteMultipartUploadAsync(completeRequest);
}
public async Task RemoveFirstBlockAsync(string domain, string key)
{
using var s3 = GetClient();
var path = MakePath(domain, key);
(var uploadId, var eTags, var partNumber) = await InitiateConcatAsync(domain, key, true, true);
var completeRequest = new CompleteMultipartUploadRequest
{
BucketName = _bucket,
Key = path,
UploadId = uploadId,
PartETags = eTags
};
await s3.CompleteMultipartUploadAsync(completeRequest);
}
public async Task<(string uploadId, List<PartETag> eTags, int partNumber)> InitiateConcatAsync(string domain, string key, bool removeFirstBlock = false, bool lastInit = false)
{
using var s3 = GetClient();
key = MakePath(domain, key);
var initiateRequest = new InitiateMultipartUploadRequest
{
BucketName = _bucket,
Key = key
};
var initResponse = await s3.InitiateMultipartUploadAsync(initiateRequest);
var eTags = new List<PartETag>();
try
{
var mb5 = 5 * 1024 * 1024;
long bytePosition = removeFirstBlock ? mb5 : 0;
var obj = await s3.GetObjectMetadataAsync(_bucket, key);
var objectSize = obj.ContentLength;
var partSize = ChunkSize;
var partNumber = 1;
for (var i = 1; bytePosition < objectSize; i++)
{
var copyRequest = new CopyPartRequest
{
DestinationBucket = _bucket,
DestinationKey = key,
SourceBucket = _bucket,
SourceKey = key,
UploadId = initResponse.UploadId,
FirstByte = bytePosition,
LastByte = bytePosition + partSize - 1 >= objectSize ? objectSize - 1 : bytePosition + partSize - 1,
PartNumber = i
};
partNumber = i + 1;
bytePosition += partSize;
var x = objectSize - bytePosition;
if (!lastInit && x < mb5 && x > 0)
{
copyRequest.LastByte = objectSize - 1;
bytePosition += partSize;
}
eTags.Add(new PartETag(i, (await s3.CopyPartAsync(copyRequest)).ETag));
}
return (initResponse.UploadId, eTags, partNumber);
}
catch
{
using var stream = new MemoryStream();
var buffer = new byte[5 * 1024 * 1024];
stream.Write(buffer);
stream.Position = 0;
var uploadRequest = new UploadPartRequest
{
BucketName = _bucket,
Key = key,
UploadId = initResponse.UploadId,
PartNumber = 1,
InputStream = stream
};
eTags.Add(new PartETag(1, (await s3.UploadPartAsync(uploadRequest)).ETag));
return (initResponse.UploadId, eTags, 2);
}
}
private IAmazonCloudFront GetCloudFrontClient()
{
var cfg = new AmazonCloudFrontConfig { MaxErrorRetry = 3 };

View File

@ -24,12 +24,27 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Data.Backup.Core.Log;
public static partial class DbHelperLogger
{
[LoggerMessage(Level = LogLevel.Error, Message = "Table {table}")]
public static partial void ErrorTableString(this ILogger<DbHelper> logger, string table, Exception exception);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
[LoggerMessage(Level = LogLevel.Error, Message = "Table {table}")]
public static partial void ErrorTable(this ILogger<DbHelper> logger, DataTable table, Exception exception);
namespace ASC.Data.Storage.Tar;
public static class BuilderHeaders
{
public static byte[] CreateHeader(string name, long size)
{
var blockBuffer = new byte[512];
var tarHeader = new TarHeader()
{
Name = name,
Size = size
};
tarHeader.WriteHeader(blockBuffer, null);
return blockBuffer;
}
}

View File

@ -24,8 +24,6 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using AutoMapper;
using static System.Formats.Asn1.AsnWriter;
namespace ASC.FederatedLogin.LoginProviders;
@ -39,7 +37,6 @@ public class ZoomLoginProvider : BaseLoginProvider<ZoomLoginProvider>
public override string ClientSecret => this["zoomClientSecret"];
public override string CodeUrl => "https://zoom.us/oauth/authorize";
public override string Scopes => "";
public string ApiRedirectUri => this["zoomApiRedirectUrl"];
public const string ApiUrl = "https://api.zoom.us/v2";
private const string UserProfileUrl = $"{ApiUrl}/users/me";

View File

@ -251,4 +251,9 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterUnsubsribeDocSpace" xml:space="preserve">
<value>이 이메일은 자동으로 생성된 이메일로 답장하실 필요가 없습니다.&lt;br /&gt;고객님께서 &lt;a href="{0}" target="_blank"&gt; DocSpace&lt;/a&gt; 의 등록된 사용자이기 때문에 이 이메일이 발송되었습니다.
&lt;br /&gt; DocSpace 알림 이메일을 수신하지 않으려면 &lt;a href="{1}" target="_blank"&gt;수신 거부&lt;/a&gt;
&lt;br /&gt;를 클릭하세요</value>
</data>
</root>

View File

@ -251,4 +251,9 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterUnsubsribeDocSpace" xml:space="preserve">
<value>Este email foi gerado automaticamente e não precisa de responder ao mesmo.&lt;br /&gt;Recebeu este email porque é um utilizador registado do &lt;a href="{0}" target="_blank"&gt; DocSpace&lt;/a&gt;.
&lt;br /&gt; Clique aqui para cancelar a subscrição aos emails de notificação do DocSpace: &lt;a href="{1}" target="_blank"&gt; Cancelar assinatura&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -192,4 +192,9 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterUnsubsribeDocSpace" xml:space="preserve">
<value>Tento e-mail je generovaný automaticky a nemusíte na to odpovedať. &lt;br /&gt;Tento e-mail dostanete pretože ste registrovaným používateľom&lt;a href="{0}" target="_blank"&gt; DocSpace&lt;/a&gt;
&lt;br /&gt; Ak sa chcete odhlásiť z odberu notifikačných e-mailov DocSpace, kliknite sem: &lt;a href="{1}" target="_blank"&gt;Odhlásiť sa&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -193,8 +193,7 @@
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterUnsubsribeDocSpace" xml:space="preserve">
<value>Цей електронний лист сформовано автоматично, і вам не потрібно відповідати на нього.
&lt;br /&gt;Ви отримуєте цей електронний лист, оскільки ви є зареєстрованим користувачем&lt;a href="{0}" target="_blank"&gt; DocSpace&lt;/a&gt;
<value>Цей електронний лист сформовано автоматично, і вам не потрібно відповідати на нього.&lt;br /&gt;Ви отримуєте цей електронний лист, оскільки ви є зареєстрованим користувачем&lt;a href="{0}" target="_blank"&gt; DocSpace&lt;/a&gt;
&lt;br /&gt;Якщо ви більше не бажаєте отримувати ці електронні листи, натисніть на це посилання: &lt;a href="{1}" target="_blank"&gt;Скасувати підписку&lt;/a&gt;
&lt;br /&gt;</value>
</data>

View File

@ -193,8 +193,7 @@
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterUnsubsribeDocSpace" xml:space="preserve">
<value>Email này được tạo tự động và bạn không cần trả lời nó.
&lt;br /&gt;Bạn nhận được email này vì bạn là người dùng đã đăng ký của &lt;a href="{0}" target="_blank"&gt; DocSpace&lt;/a&gt;
<value>Email này được tạo tự động và bạn không cần trả lời nó.&lt;br /&gt;Bạn nhận được email này vì bạn là người dùng đã đăng ký của &lt;a href="{0}" target="_blank"&gt; DocSpace&lt;/a&gt;
&lt;br /&gt; Nhấp vào đây để hủy đăng ký nhận email thông báo của DocSpace: &lt;a href="{1}" target="_blank"&gt;Hủy đăng ký&lt;/a&gt;
&lt;br /&gt;</value>
</data>

View File

@ -39,7 +39,6 @@ global using ASC.Core.Common.EF.Context;
global using ASC.Core.Common.Hosting;
global using ASC.Core.Tenants;
global using ASC.Core.Users;
global using ASC.Data.Backup;
global using ASC.Data.Backup.EF.Context;
global using ASC.Data.Backup.Exceptions;
global using ASC.Data.Backup.Extensions;
@ -47,11 +46,11 @@ global using ASC.Data.Backup.Tasks;
global using ASC.Data.Backup.Tasks.Data;
global using ASC.Data.Backup.Tasks.Modules;
global using ASC.Data.Storage;
global using ASC.Data.Storage.ZipOperators;
global using ASC.Data.Storage.DataOperators;
global using ASC.EventBus.Abstractions;
global using ASC.EventBus.Events;
global using ASC.EventBus.Extensions.Logger;
global using ASC.Feed.Context;
global using ASC.Feed.Context;
global using ASC.Files.Core.EF;
global using ASC.MessagingSystem.EF.Context;
global using ASC.Migration.PersonalToDocspace;

View File

@ -378,7 +378,7 @@ public class MigrationCreator
{
var storage = await _storageFactory.GetStorageAsync(_fromTenantId, group.Key);
var file1 = file;
await ActionInvoker.Try(async state =>
await ActionInvoker.TryAsync(async state =>
{
var f = (BackupFileInfo)state;
using var fileStream = await storage.GetReadStreamAsync(f.Domain, f.Path);

View File

@ -1285,7 +1285,7 @@ namespace ASC.AuditTrail {
}
/// <summary>
/// Looks up a localized string similar to Products: [{0}]. Groups [{1}]. Access Opened.
/// Looks up a localized string similar to Products: [{0}]. Groups [{1}]. Access Opened.
/// </summary>
public static string ProductAccessOpenedForGroups {
get {
@ -1294,7 +1294,7 @@ namespace ASC.AuditTrail {
}
/// <summary>
/// Looks up a localized string similar to Products: [{0}]. Users [{1}]. Access Opened.
/// Looks up a localized string similar to Products: [{0}]. Users [{1}]. Access Opened.
/// </summary>
public static string ProductAccessOpenedForUsers {
get {

View File

@ -718,4 +718,10 @@
<data name="TagsDeleted" xml:space="preserve">
<value>Teqlər silindi: {0}</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Şəhər</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Ölkə</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="StartStorageEncryption" xml:space="preserve">
<value>Стартирайте криптирането на хранилището</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Град</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Държава</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="TwoFactorAuthenticationSettingsEnabledByTfaApp" xml:space="preserve">
<value>Nastavení dvoufázového ověřování aktualizováno: Ověřovací aplikace byla povolena</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Město</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Země</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="TagsDeleted" xml:space="preserve">
<value>Tags gelöscht: {0}</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Stadt</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Land</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="UserUpdatedAvatarThumbnails" xml:space="preserve">
<value>Χρήστες [{0}]. Έγινε ενημέρωση μικρογραφιών άβαταρ</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Πόλη</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Χώρα</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="RoomRemoveUser" xml:space="preserve">
<value>Usuario eliminado: {0}</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Ciudad</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>País</value>
</data>
</root>

View File

@ -718,4 +718,7 @@
<data name="UserUpdatedEmail" xml:space="preserve">
<value>Sähköposti päivitetty</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Kaupunki</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="TagsDeleted" xml:space="preserve">
<value>Étiquettes supprimées : {0}</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Ville</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Pays</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="TagsDeleted" xml:space="preserve">
<value>Tag eliminati: {0}</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Città</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Paese</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="UserUpdatedAvatarThumbnails" xml:space="preserve">
<value>ユーザー[{0}]。アバターサムネイルが更新されました</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>都市</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>国</value>
</data>
</root>

View File

@ -171,4 +171,613 @@
<data name="TagsDeleted" xml:space="preserve">
<value>태그가 삭제되었습니다: {0}</value>
</data>
<data name="GreetingSettingsUpdated" xml:space="preserve">
<value>환영 페이지 설정이 업데이트되었습니다</value>
</data>
<data name="UsersDeleted" xml:space="preserve">
<value>사용자가 삭제되었습니다: {0}</value>
</data>
<data name="UsersUpdatedType" xml:space="preserve">
<value>사용자 [{0}]. 유형이 업데이트되었습니다</value>
</data>
<data name="UsersUpdatedStatus" xml:space="preserve">
<value>사용자 [{0}]. 상태가 업데이트되었습니다</value>
</data>
<data name="UserSentPasswordInstructions" xml:space="preserve">
<value>사용자 [{0}]. 비밀번호 변경 지침이 전송되었습니다</value>
</data>
<data name="UserSentEmailInstructions" xml:space="preserve">
<value>사용자 [{0}]. 이메일 변경 지침이 전송되었습니다</value>
</data>
<data name="UserSentActivationInstructions" xml:space="preserve">
<value>사용자 [{0}]. 활성화 지침이 전송되었습니다</value>
</data>
<data name="UserUpdatedMobileNumber" xml:space="preserve">
<value>사용자 [{0}]. 휴대폰 번호가 업데이트되었습니다: {1}</value>
</data>
<data name="UserFileUpdated" xml:space="preserve">
<value>사용자 [{0}]. 파일이 업데이트되었습니다: {1}</value>
</data>
<data name="UserUpdatedAvatarThumbnails" xml:space="preserve">
<value>사용자 [{0}]. 아바타 썸네일이 업데이트되었습니다</value>
</data>
<data name="UserDeletedAvatar" xml:space="preserve">
<value>사용자 [{0}]. 아바타가 삭제되었습니다</value>
</data>
<data name="UserAddedAvatar" xml:space="preserve">
<value>사용자 [{0}]. 아바타가 추가되었습니다</value>
</data>
<data name="UsersSentActivationInstructions" xml:space="preserve">
<value>사용자 [{0}]. 활성화 지침이 전송되었습니다</value>
</data>
<data name="UsersModule" xml:space="preserve">
<value>사용자</value>
</data>
<data name="UserUpdated" xml:space="preserve">
<value>사용자가 업데이트되었습니다: {0}</value>
</data>
<data name="UserImported" xml:space="preserve">
<value>사용자가 가져오기되었습니다: {0}</value>
</data>
<data name="UserDeleted" xml:space="preserve">
<value>사용자가 삭제되었습니다: {0}</value>
</data>
<data name="UserDataRemoving" xml:space="preserve">
<value>{0} 에 대한 사용자 데이터가 삭제되었습니다</value>
</data>
<data name="UserCreated" xml:space="preserve">
<value>사용자가 생성되었습니다: {0}</value>
</data>
<data name="UserCreatedViaInvite" xml:space="preserve">
<value>초대를 통해 사용자가 생성되었습니다: {0}</value>
</data>
<data name="UserActivated" xml:space="preserve">
<value>사용자가 활성화되었습니다: {0}</value>
</data>
<data name="UserCol" xml:space="preserve">
<value>사용자</value>
</data>
<data name="DocumentsUploadingFormatsSettingsUpdated" xml:space="preserve">
<value>업로딩 형식 설정이 업데이트되었습니다</value>
</data>
<data name="UploadActionType" xml:space="preserve">
<value>업로드</value>
</data>
<data name="UpdateAccessActionType" xml:space="preserve">
<value>액세스가 업데이트되었습니다</value>
</data>
<data name="UpdateActionType" xml:space="preserve">
<value>업데이트</value>
</data>
<data name="UnlinkActionType" xml:space="preserve">
<value>연결 해제</value>
</data>
<data name="UnknownAccount" xml:space="preserve">
<value>알 수 없는 계정</value>
</data>
<data name="UnfollowActionType" xml:space="preserve">
<value>팔로우 해제</value>
</data>
<data name="TwoFactorAuthenticationSettingsDisabled" xml:space="preserve">
<value>이중 인증 설정이 업데이트되었습니다: 이중 인증이 비활성화되었습니다</value>
</data>
<data name="TwoFactorAuthenticationSettingsEnabledBySms" xml:space="preserve">
<value>이중 인증 설정이 업데이트되었습니다: SMS 확인이 활성화되었습니다</value>
</data>
<data name="TwoFactorAuthenticationSettingsEnabledByTfaApp" xml:space="preserve">
<value>이중 인증 설정이 업데이트되었습니다: 인증이 활성화되었습니다</value>
</data>
<data name="TwoFactorAuthenticationSettingsUpdated" xml:space="preserve">
<value>이중 인증 설정이 업데이트되었습니다</value>
</data>
<data name="TrustedMailDomainSettingsUpdated" xml:space="preserve">
<value>신뢰할 수 있는 메일 도메인 설정이 업데이트되었습니다</value>
</data>
<data name="TrashEmptied" xml:space="preserve">
<value>휴지통이 비워졌습니다</value>
</data>
<data name="TimeZoneSettingsUpdated" xml:space="preserve">
<value>시간대 설정이 업데이트되었습니다</value>
</data>
<data name="TimeTrackingModule" xml:space="preserve">
<value>시간 추적 중</value>
</data>
<data name="DocumentsThirdPartySettingsUpdated" xml:space="preserve">
<value>제3자 설정이 업데이트되었습니다</value>
</data>
<data name="AuthorizationKeysSetting" xml:space="preserve">
<value>제3자 인증 키가 업데이트되었습니다</value>
</data>
<data name="ThirdPartyUpdated" xml:space="preserve">
<value>제3자가 업데이트했습니다: {0}. 제공자: {1}</value>
</data>
<data name="ThirdPartyDeleted" xml:space="preserve">
<value>제3자가 삭제했습니다: {0}. 제공자: {1}</value>
</data>
<data name="ThirdPartyCreated" xml:space="preserve">
<value>제3자가 생성했습니다: {0}. 제공자: {1}</value>
</data>
<data name="TeamTemplateChanged" xml:space="preserve">
<value>팀 템플릿이 변경되었습니다</value>
</data>
<data name="TargetIdCol" xml:space="preserve">
<value>타깃 ID</value>
</data>
<data name="SystemAccount" xml:space="preserve">
<value>시스템 계정</value>
</data>
<data name="LoginSuccessViaSSO" xml:space="preserve">
<value>SSO를 통한 로그인에 성공했습니다</value>
</data>
<data name="LoginSuccessSocialApp" xml:space="preserve">
<value>소셜 애플리케이션을 통한 로그인에 성공했습니다</value>
</data>
<data name="LoginSuccesViaTfaApp" xml:space="preserve">
<value>인증 애플리케이션을 통한 로그인에 성공했습니다</value>
</data>
<data name="LoginSuccessViaApiSms" xml:space="preserve">
<value>API &amp; SMS 코드를 통한 로그인에 성공했습니다</value>
</data>
<data name="LoginSuccessViaApiTfa" xml:space="preserve">
<value>API &amp; 인증 애플리케이션을 통한 로그인에 성공했습니다</value>
</data>
<data name="LoginSuccessViaApi" xml:space="preserve">
<value>API를 통한 로그인에 성공했습니다</value>
</data>
<data name="DocumentsStoreForcesave" xml:space="preserve">
<value>강제 저장 설정이 업데이트되었습니다</value>
</data>
<data name="StartStorageEncryption" xml:space="preserve">
<value>스토리지 암호화 시작</value>
</data>
<data name="StartStorageDecryption" xml:space="preserve">
<value>스토리지 복호화 시작</value>
</data>
<data name="StartTransferSetting" xml:space="preserve">
<value>포털 리전 마이그레이션 시작</value>
</data>
<data name="StartBackupSetting" xml:space="preserve">
<value>백업 시작</value>
</data>
<data name="SSOEnabled" xml:space="preserve">
<value>SSO가 활성화되었습니다</value>
</data>
<data name="SSODisabled" xml:space="preserve">
<value>SSO가 비활성화되었습니다</value>
</data>
<data name="FullTextSearchSetting" xml:space="preserve">
<value>Sphinx 설정이 업데이트되었습니다</value>
</data>
<data name="UserUnlinkedSocialAccount" xml:space="preserve">
<value>소셜 계정의 연결이 해제되었습니다. 제공자: {0}</value>
</data>
<data name="UserLinkedSocialAccount" xml:space="preserve">
<value>소셜 계정이 연결되었습니다. 제공자: {0}</value>
</data>
<data name="SettingsProduct" xml:space="preserve">
<value>설정</value>
</data>
<data name="DocumentsSettingsModule" xml:space="preserve">
<value>설정</value>
</data>
<data name="FileChangeOwner" xml:space="preserve">
<value>[{0}] 소유자가 [{1}]님으로 설정되었습니다</value>
</data>
<data name="SessionStarted" xml:space="preserve">
<value>세션이 시작되었습니다</value>
</data>
<data name="SessionCompleted" xml:space="preserve">
<value>세션이 완료되었습니다</value>
</data>
<data name="FileSendAccessLink" xml:space="preserve">
<value>[{0}] 파일로 공유 링크가 발송되었습니다. 수신자: [{1}]</value>
</data>
<data name="UserSentDeleteInstructions" xml:space="preserve">
<value>프로필 삭제 지침이 전송되었습니다</value>
</data>
<data name="SendActionType" xml:space="preserve">
<value>전송</value>
</data>
<data name="ReassignsActionType" xml:space="preserve">
<value>재할당</value>
</data>
<data name="ProductAccessOpenedForUsers" xml:space="preserve">
<value>프로덕트: [{0}]. 사용자 [{1}]. 액세스가 열렸습니다</value>
</data>
<data name="ProductAccessOpenedForGroups" xml:space="preserve">
<value>프로덕트: [{0}]. 그룹 [{1}]. 액세스가 열렸습니다</value>
</data>
<data name="ProductsListUpdated" xml:space="preserve">
<value>프로덕트 목록이 업데이트되었습니다</value>
</data>
<data name="ProductDeletedAdministrator" xml:space="preserve">
<value>프로덕트 [{0}]. 관리자가 삭제되었습니다: {1}</value>
</data>
<data name="ProductAddedAdministrator" xml:space="preserve">
<value>프로덕트 [{0}]. 관리자가 추가되었습니다: {1}</value>
</data>
<data name="ProductAccessRestricted" xml:space="preserve">
<value>프로덕트 [{0}]. 액세스가 제한되었습니다</value>
</data>
<data name="ProductAccessOpened" xml:space="preserve">
<value>프로덕트 [{0}]. 액세스가 열렸습니다</value>
</data>
<data name="ProductsModule" xml:space="preserve">
<value>프로덕트</value>
</data>
<data name="ProductCol" xml:space="preserve">
<value>프로덕트</value>
</data>
<data name="PrivacyRoomEnable" xml:space="preserve">
<value>포털 룸이 활성화되었습니다</value>
</data>
<data name="PrivacyRoomDisable" xml:space="preserve">
<value>포털 룸이 비활성화되었습니다</value>
</data>
<data name="PortalDeleted" xml:space="preserve">
<value>포털이 삭제되었습니다</value>
</data>
<data name="PortalDeactivated" xml:space="preserve">
<value>포털이 비활성화되었습니다</value>
</data>
<data name="PortalAccessSettingsUpdated" xml:space="preserve">
<value>포털 액세스 설정이 업데이트되었습니다</value>
</data>
<data name="PlatformCol" xml:space="preserve">
<value>플랫폼</value>
</data>
<data name="PeopleProduct" xml:space="preserve">
<value>사람</value>
</data>
<data name="UserUpdatedPassword" xml:space="preserve">
<value>비밀번호가 업데이트되었습니다</value>
</data>
<data name="PasswordStrengthSettingsUpdated" xml:space="preserve">
<value>비밀번호 강도 설정이 업데이트되었습니다</value>
</data>
<data name="PageCol" xml:space="preserve">
<value>페이지</value>
</data>
<data name="OwnerChanged" xml:space="preserve">
<value>소유자가 변경되었습니다: {0}</value>
</data>
<data name="OwnerSentPortalDeleteInstructions" xml:space="preserve">
<value>소유자 [{0}]. 포털 삭제 지침이 전송되었습니다</value>
</data>
<data name="OwnerSentPortalDeactivationInstructions" xml:space="preserve">
<value>소유자 [{0}]. 포털 비활성화 지침이 전송되었습니다</value>
</data>
<data name="OwnerSentChangeOwnerInstructions" xml:space="preserve">
<value>소유자 [{0}]. 소유자 변경 지침이 전송되었습니다</value>
</data>
<data name="DocumentsOverwritingSettingsUpdated" xml:space="preserve">
<value>덮어쓰기 설정이 업데이트되었습니다</value>
</data>
<data name="OthersProduct" xml:space="preserve">
<value>기타</value>
</data>
<data name="UserTfaGenerateCodes" xml:space="preserve">
<value>새로운 백업 코드가 생성되었습니다</value>
</data>
<data name="MoveActionType" xml:space="preserve">
<value>이동</value>
</data>
<data name="ModuleCol" xml:space="preserve">
<value>모듈</value>
</data>
<data name="MailServiceSettingsUpdated" xml:space="preserve">
<value>메일 서비스 설정이 업데이트되었습니다</value>
</data>
<data name="Logout" xml:space="preserve">
<value>로그아웃</value>
</data>
<data name="LoginFailViaSSO" xml:space="preserve">
<value>SSO를 통한 로그인에 실패했습니다</value>
</data>
<data name="LoginSuccessSocialAccount" xml:space="preserve">
<value>소셜 계정을 통한 로그인에 성공했습니다</value>
</data>
<data name="LoginSuccessViaSms" xml:space="preserve">
<value>SMS를 통한 로그인에 성공했습니다</value>
</data>
<data name="LoginFailViaSms" xml:space="preserve">
<value>SMS를 통한 로그인에 실패했습니다</value>
</data>
<data name="LoginFailViaTfaApp" xml:space="preserve">
<value>인증 애플리케이션을 통한 로그인에 실패했습니다</value>
</data>
<data name="LoginSuccessViaSocialAccount" xml:space="preserve">
<value>API &amp; 소셜 미디어를 통한 로그인에 성공했습니다</value>
</data>
<data name="LoginFailViaApiSocialAccount" xml:space="preserve">
<value>API &amp; 소셜 미디어를 통한 로그인에 실패했습니다</value>
</data>
<data name="LoginSuccess" xml:space="preserve">
<value>로그인에 성공했습니다</value>
</data>
<data name="LoginHistoryReportDownloaded" xml:space="preserve">
<value>로그인 기록 보고서가 다운로드되었습니다</value>
</data>
<data name="LoginHistoryReportName" xml:space="preserve">
<value>로그인 기록 보고서 ({0}-{1})</value>
</data>
<data name="LoginFailBruteForce" xml:space="preserve">
<value>로그인 실패. 너무 많이 시도했습니다</value>
</data>
<data name="LoginFailRecaptcha" xml:space="preserve">
<value>로그인 실패. Recaptcha가 유효하지 않습니다</value>
</data>
<data name="LoginFailDisabledProfile" xml:space="preserve">
<value>로그인 실패. 프로필이 비활성화되었습니다</value>
</data>
<data name="LoginFailInvalidCombination" xml:space="preserve">
<value>로그인 실패. 사용자명-비밀번호 조합이 유효하지 않습니다</value>
</data>
<data name="LoginFailSocialAccountNotFound" xml:space="preserve">
<value>로그인 실패. 연결된 소셜 계정을 찾지 못했습니다</value>
</data>
<data name="LoginFail" xml:space="preserve">
<value>로그인 실패</value>
</data>
<data name="LinkActionType" xml:space="preserve">
<value>연결</value>
</data>
<data name="LicenseKeyUploaded" xml:space="preserve">
<value>라이선스 키가 업로드되었습니다</value>
</data>
<data name="UserUpdatedLanguage" xml:space="preserve">
<value>언어가 업데이트되었습니다</value>
</data>
<data name="LanguageSettingsUpdated" xml:space="preserve">
<value>언어 설정이 업데이트되었습니다</value>
</data>
<data name="LoginFailIpSecurity" xml:space="preserve">
<value>IP 보안: 로그인에 실패했습니다</value>
</data>
<data name="IpCol" xml:space="preserve">
<value>IP</value>
</data>
<data name="SentInviteInstructions" xml:space="preserve">
<value>초대 지침이 전송되었습니다: {0}</value>
</data>
<data name="ImportActionType" xml:space="preserve">
<value>가져오기</value>
</data>
<data name="GuestImported" xml:space="preserve">
<value>게스트가 가져오기되었습니다: {0}</value>
</data>
<data name="GuestCreated" xml:space="preserve">
<value>게스트가 생성되었습니다: {0}</value>
</data>
<data name="GuestCreatedViaInvite" xml:space="preserve">
<value>초대를 통해 게스트가 생성되었습니다: {0}</value>
</data>
<data name="GuestActivated" xml:space="preserve">
<value>게스트가 활성화되었습니다: {0}</value>
</data>
<data name="GuestAccount" xml:space="preserve">
<value>게스트 계정</value>
</data>
<data name="GroupsModule" xml:space="preserve">
<value>그룹</value>
</data>
<data name="GroupUpdated" xml:space="preserve">
<value>그룹이 업데이트되었습니다: {0}</value>
</data>
<data name="GroupDeleted" xml:space="preserve">
<value>그룹이 삭제되었습니다: {0}</value>
</data>
<data name="GroupCreated" xml:space="preserve">
<value>그룹이 생성되었습니다: {0}</value>
</data>
<data name="GeneralModule" xml:space="preserve">
<value>일반</value>
</data>
<data name="DocumentsForcesave" xml:space="preserve">
<value>강제 저장 설정이 업데이트되었습니다</value>
</data>
<data name="FollowActionType" xml:space="preserve">
<value>팔로우</value>
</data>
<data name="FolderMovedWithOverwriting" xml:space="preserve">
<value>폴더 [{0}]. {1} 폴더로 덮어쓰기로 이동되었습니다</value>
</data>
<data name="FolderMovedToTrash" xml:space="preserve">
<value>폴더 [{0}]. 휴지통 폴더로 이동되었습니다</value>
</data>
<data name="FolderMoved" xml:space="preserve">
<value>폴더 [{0}]. 폴더로 이동되었습니다: {1}</value>
</data>
<data name="FileImported" xml:space="preserve">
<value>폴더 [{0}]. 파일이 가져오기되었습니다: {1}. 제공자: {2}</value>
</data>
<data name="FolderCopiedWithOverwriting" xml:space="preserve">
<value>폴더 [{0}]. {1} 폴더로 덮어쓰기로 복사되었습니다</value>
</data>
<data name="FolderCopied" xml:space="preserve">
<value>폴더 [{0}]. 폴더로 복사되었습니다: {1}</value>
</data>
<data name="FolderUpdatedAccess" xml:space="preserve">
<value>폴더 [{0}]. 액세스가 업데이트되었습니다</value>
</data>
<data name="FoldersModule" xml:space="preserve">
<value>폴더</value>
</data>
<data name="FolderRenamed" xml:space="preserve">
<value>파일의 이름이 변경되었습니다: {0}</value>
</data>
<data name="FolderDeleted" xml:space="preserve">
<value>폴더가 삭제되었습니다: {0}</value>
</data>
<data name="FolderCreated" xml:space="preserve">
<value>폴더가 생성되었습니다: {0}</value>
</data>
<data name="FileRestoreVersion" xml:space="preserve">
<value>파일 [{0}]. 리비전: {1}. 버전이 복원되었습니다</value>
</data>
<data name="FileDeletedVersion" xml:space="preserve">
<value>파일 [{0}]. 리비전: {1}. 버전이 삭제되었습니다</value>
</data>
<data name="FileCreatedVersion" xml:space="preserve">
<value>파일 [{0}]. 리비전: {1}. 버전이 생성되었습니다</value>
</data>
<data name="FileUpdatedRevisionComment" xml:space="preserve">
<value>파일 [{0}]. 리비전 {1}. 코멘트가 업데이트되었습니다</value>
</data>
<data name="FileMovedWithOverwriting" xml:space="preserve">
<value>파일 [{0}]. "{1}" 폴더에서 "{2}" 폴더로 덮어쓰기로 이동되었습니다</value>
</data>
<data name="FileMovedToTrash" xml:space="preserve">
<value>파일 [{0}]. 휴지통 폴더로 이동되었습니다</value>
</data>
<data name="FileMoved" xml:space="preserve">
<value>파일 [{0}]. "{1}" 폴더에서: "{2}" 폴더로 이동되었습니다</value>
</data>
<data name="FileDownloadedAs" xml:space="preserve">
<value>파일 [{0}]. {1}로 다운로드되었습니다</value>
</data>
<data name="FileCopiedWithOverwriting" xml:space="preserve">
<value>파일 [{0}]. "{1}" 폴더에서 "{2}" 폴더로 덮어쓰기로 복사되었습니다</value>
</data>
<data name="FileCopied" xml:space="preserve">
<value>파일 [{0}]. "{1}" 폴더에서: "{2}" 폴더로 복사되었습니다</value>
</data>
<data name="FileUpdatedAccess" xml:space="preserve">
<value>파일 [{0}]. 액세스가 업데이트되었습니다</value>
</data>
<data name="FilesModule" xml:space="preserve">
<value>파일</value>
</data>
<data name="FileUploaded" xml:space="preserve">
<value>폴더가 업데이트되었습니다: {0}</value>
</data>
<data name="FileUpdated" xml:space="preserve">
<value>파일이 업데이트되었습니다: {0}</value>
</data>
<data name="FileUnlocked" xml:space="preserve">
<value>파일의 잠금이 해제되었습니다: {0}</value>
</data>
<data name="FileRenamed" xml:space="preserve">
<value>파일의 이름이 변경되었습니다: {0}</value>
</data>
<data name="FileLocked" xml:space="preserve">
<value>파일이 잠겼습니다: {0}</value>
</data>
<data name="FileDownloaded" xml:space="preserve">
<value>파일이 다운로드되었습니다: {0}</value>
</data>
<data name="FileDeleted" xml:space="preserve">
<value>파일이 삭제되었습니다: {0}</value>
</data>
<data name="FileCreated" xml:space="preserve">
<value>파일이 생성되었습니다: {0}</value>
</data>
<data name="FileConverted" xml:space="preserve">
<value>파일이 내부 형식으로 변환되었습니다: {0}</value>
</data>
<data name="LoginFailViaApiSms" xml:space="preserve">
<value>API &amp; SMS 코드를 통한 로그인에 실패했습니다</value>
</data>
<data name="LoginFailViaApiTfa" xml:space="preserve">
<value>API &amp; 인증 애플리케이션을 통한 로그인에 실패했습니다</value>
</data>
<data name="LoginFailViaApi" xml:space="preserve">
<value>API를 통한 로그인에 실패했습니다</value>
</data>
<data name="ExportActionType" xml:space="preserve">
<value>내보내기</value>
</data>
<data name="UserUpdatedEmail" xml:space="preserve">
<value>이메일이 업데이트되었습니다</value>
</data>
<data name="DownloadActionType" xml:space="preserve">
<value>다운로드</value>
</data>
<data name="DocumentsProduct" xml:space="preserve">
<value>문서</value>
</data>
<data name="DocumentServiceLocationSetting" xml:space="preserve">
<value>문서 서비스 위치가 업데이트되었습니다</value>
</data>
<data name="FilesDocumentSigned" xml:space="preserve">
<value>{1} 문서가 {0}를 통해 서명되었습니다</value>
</data>
<data name="FilesRequestSign" xml:space="preserve">
<value>{1} 문서가 {0}을 통해 서명하도록 전송되었습니다</value>
</data>
<data name="DnsSettingsUpdated" xml:space="preserve">
<value>DNS 설정이 업데이트되었습니다</value>
</data>
<data name="DetachActionType" xml:space="preserve">
<value>분리</value>
</data>
<data name="ActionTypeCol" xml:space="preserve">
<value>작업 유형</value>
</data>
<data name="ActionIdCol" xml:space="preserve">
<value>작업 ID</value>
</data>
<data name="ActionCol" xml:space="preserve">
<value>작업</value>
</data>
<data name="AdministratorAdded" xml:space="preserve">
<value>관리자가 추가되었습니다: {0}</value>
</data>
<data name="AdministratorDeleted" xml:space="preserve">
<value>관리자가 삭제되었습니다: {0}</value>
</data>
<data name="AdministratorMessageSettingsUpdated" xml:space="preserve">
<value>관리자 메시지 설정이 업데이트되었습니다</value>
</data>
<data name="AdministratorOpenedFullAccess" xml:space="preserve">
<value>관리자 [{0}]. 전체 액세스가 열렸습니다</value>
</data>
<data name="AttachActionType" xml:space="preserve">
<value>연결</value>
</data>
<data name="AuditSettingsUpdated" xml:space="preserve">
<value>감사 수명 설정이 업데이트되었습니다</value>
</data>
<data name="AuditTrailReportName" xml:space="preserve">
<value>감사 추적 보고서 ({0}-{1})</value>
</data>
<data name="AuditTrailReportDownloaded" xml:space="preserve">
<value>감사 추적 보고서가 다운로드되었습니다</value>
</data>
<data name="UserTfaDisconnected" xml:space="preserve">
<value>인증 애플리케이션의 연결이 해제되었습니다: {0}</value>
</data>
<data name="BrowserCol" xml:space="preserve">
<value>브라우저</value>
</data>
<data name="ColorThemeChanged" xml:space="preserve">
<value>색상 테마가 변경되었습니다</value>
</data>
<data name="CookieSettingsUpdated" xml:space="preserve">
<value>쿠키 설정이 업데이트되었습니다</value>
</data>
<data name="CopyActionType" xml:space="preserve">
<value>복사</value>
</data>
<data name="CreateActionType" xml:space="preserve">
<value>생성</value>
</data>
<data name="CustomNavigationSettingsUpdated" xml:space="preserve">
<value>커스텀 네비게이션 설정이 업데이트되었습니다</value>
</data>
<data name="UserDataReassigns" xml:space="preserve">
<value>데이터 재할당되었습니다: {0} 사용자에서 {1} 사용자에게</value>
</data>
<data name="DateCol" xml:space="preserve">
<value>날짜</value>
</data>
<data name="DefaultStartPageSettingsUpdated" xml:space="preserve">
<value>기본 시작 페이지 설정이 업데이트되었습니다</value>
</data>
<data name="DeleteActionType" xml:space="preserve">
<value>삭제</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>도시</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="UserUpdatedEmail" xml:space="preserve">
<value>E-pasta adrese ir atjaunināta</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Pilsēta</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Valsts</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="UserUpdatedEmail" xml:space="preserve">
<value>E-mail Bijgewerkt</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Plaats</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Land</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="UserUpdatedEmail" xml:space="preserve">
<value>Zaktualizowano adres e-mail</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Miejscowość</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Kraj</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="RoomUpdateAccessForUser" xml:space="preserve">
<value>{0} nomeado: {1}</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Cidade</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>País</value>
</data>
</root>

View File

@ -171,4 +171,613 @@
<data name="TagsDeleted" xml:space="preserve">
<value>Tag eliminada: {0}</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Cidade</value>
</data>
<data name="ActionCol" xml:space="preserve">
<value>Ação</value>
</data>
<data name="ActionIdCol" xml:space="preserve">
<value>ID da Ação</value>
</data>
<data name="ActionTypeCol" xml:space="preserve">
<value>Tipo de Ação</value>
</data>
<data name="AdministratorAdded" xml:space="preserve">
<value>Administrador Adicionado: {0}</value>
</data>
<data name="AdministratorDeleted" xml:space="preserve">
<value>Administrador Eliminado: {0}</value>
</data>
<data name="AdministratorMessageSettingsUpdated" xml:space="preserve">
<value>As Definições de Mensagens de Administrador foram Atualizadas</value>
</data>
<data name="AdministratorOpenedFullAccess" xml:space="preserve">
<value>Administradores [{0}]. Acesso Completo Aberto</value>
</data>
<data name="AttachActionType" xml:space="preserve">
<value>Anexar</value>
</data>
<data name="AuditSettingsUpdated" xml:space="preserve">
<value>As Configurações de Retenção de Registos da Auditoria Foram Atualizadas</value>
</data>
<data name="AuditTrailReportDownloaded" xml:space="preserve">
<value>Relatório de Auditoria do Rastro Descarregado</value>
</data>
<data name="AuditTrailReportName" xml:space="preserve">
<value>Relatório de Auditoria do Rastro ({0}-{1})</value>
</data>
<data name="AuthorizationKeysSetting" xml:space="preserve">
<value>Chaves para autorização de terceiros atualizadas</value>
</data>
<data name="BrowserCol" xml:space="preserve">
<value>Navegador</value>
</data>
<data name="ColorThemeChanged" xml:space="preserve">
<value>O Tema de Cores foi Alterado</value>
</data>
<data name="CookieSettingsUpdated" xml:space="preserve">
<value>As Definições de Cookies foram Alteradas</value>
</data>
<data name="CopyActionType" xml:space="preserve">
<value>Copiar</value>
</data>
<data name="CreateActionType" xml:space="preserve">
<value>Criar</value>
</data>
<data name="CustomNavigationSettingsUpdated" xml:space="preserve">
<value>As Definições de Navegação Personalizada foram Atualizadas</value>
</data>
<data name="DateCol" xml:space="preserve">
<value>Data</value>
</data>
<data name="DefaultStartPageSettingsUpdated" xml:space="preserve">
<value>As Configurações da Página Inicial Predefinida foram Atualizadas</value>
</data>
<data name="DeleteActionType" xml:space="preserve">
<value>Eliminar</value>
</data>
<data name="DetachActionType" xml:space="preserve">
<value>Desanexar</value>
</data>
<data name="DnsSettingsUpdated" xml:space="preserve">
<value>As Definições DNS foram Atualizadas</value>
</data>
<data name="DocumentServiceLocationSetting" xml:space="preserve">
<value>A Localização do Serviço de Documentos foi Atualizada</value>
</data>
<data name="DocumentsForcesave" xml:space="preserve">
<value>As definições do Guardar Forçado foram Atualizadas</value>
</data>
<data name="DocumentsOverwritingSettingsUpdated" xml:space="preserve">
<value>Atualização das Definições de Substituição</value>
</data>
<data name="DocumentsProduct" xml:space="preserve">
<value>Documentos</value>
</data>
<data name="DocumentsSettingsModule" xml:space="preserve">
<value>Definições</value>
</data>
<data name="DocumentsStoreForcesave" xml:space="preserve">
<value>As Configurações de Armazenamento do Guardar Forçado foram Atualizadas</value>
</data>
<data name="DocumentsThirdPartySettingsUpdated" xml:space="preserve">
<value>As Definições Relacionadas com Serviços de Terceiros foram Atualizadas</value>
</data>
<data name="DocumentsUploadingFormatsSettingsUpdated" xml:space="preserve">
<value>As Definições dos Formatos de Carregamentos foram Atualizadas</value>
</data>
<data name="DownloadActionType" xml:space="preserve">
<value>Descarregar</value>
</data>
<data name="ExportActionType" xml:space="preserve">
<value>Exportar</value>
</data>
<data name="FileChangeOwner" xml:space="preserve">
<value>Definir proprietário de [{0}] como [{1}]</value>
</data>
<data name="FileConverted" xml:space="preserve">
<value>Ficheiro Convertido para o Formato Interno: {0}</value>
</data>
<data name="FileCopied" xml:space="preserve">
<value>Ficheiros [{0}]. Copiado da Pasta "{1}" Para a Pasta: "{2}"</value>
</data>
<data name="FileCopiedWithOverwriting" xml:space="preserve">
<value>Ficheiros [{0}]. Copiado com Substituição da Pasta "{1}" para a Pasta: "{2}"</value>
</data>
<data name="FileCreated" xml:space="preserve">
<value>Ficheiro Criado: {0}</value>
</data>
<data name="FileCreatedVersion" xml:space="preserve">
<value>Ficheiros [{0}]. Revisão: {1}. Versão Criada</value>
</data>
<data name="FileDeleted" xml:space="preserve">
<value>Ficheiro Eliminado: {0}</value>
</data>
<data name="FileDeletedVersion" xml:space="preserve">
<value>Ficheiros [{0}]. Revisão: {1}. Versão Eliminada</value>
</data>
<data name="FileDownloaded" xml:space="preserve">
<value>Ficheiro Descarregado: {0}</value>
</data>
<data name="FileDownloadedAs" xml:space="preserve">
<value>Ficheiros [{0}]. Descarregados Como: {1}</value>
</data>
<data name="FileImported" xml:space="preserve">
<value>Pastas [{0}]. Ficheiro Importado: {1}. Fornecedor: {2}</value>
</data>
<data name="FileLocked" xml:space="preserve">
<value>Ficheiro Trancado: {0}</value>
</data>
<data name="FileMoved" xml:space="preserve">
<value>Ficheiros [{0}]. Movidas da Página "{1}" Para a Pasta: "{2}"</value>
</data>
<data name="FileMovedToTrash" xml:space="preserve">
<value>Ficheiros [{0}]. Movidos para a Pasta da Reciclagem</value>
</data>
<data name="FileMovedWithOverwriting" xml:space="preserve">
<value>Ficheiros [{0}]. Movidos com Substituição da Pasta "{1}" Para a Pasta: "{2}"</value>
</data>
<data name="FileRenamed" xml:space="preserve">
<value>Ficheiro Renomeado: {0}</value>
</data>
<data name="FileRestoreVersion" xml:space="preserve">
<value>Ficheiros [{0}]. Revisão: {1}. Versão Restaurada</value>
</data>
<data name="FilesDocumentSigned" xml:space="preserve">
<value>Documento {1} assinado via {0}</value>
</data>
<data name="FileSendAccessLink" xml:space="preserve">
<value>O link partilhado foi enviado para o ficheiro [{0}]. Destinatários: [{1}]</value>
</data>
<data name="FilesModule" xml:space="preserve">
<value>Ficheiros</value>
</data>
<data name="FilesRequestSign" xml:space="preserve">
<value>Documento {1} enviado para assinar através de {0}</value>
</data>
<data name="FileUnlocked" xml:space="preserve">
<value>Ficheiro Desbloqueado: {0}</value>
</data>
<data name="FileUpdated" xml:space="preserve">
<value>Ficheiro Atualizado: {0}</value>
</data>
<data name="FileUpdatedAccess" xml:space="preserve">
<value>Ficheiros [{0}]. O Acesso foi Atualizado</value>
</data>
<data name="FileUpdatedRevisionComment" xml:space="preserve">
<value>Ficheiros [{0}]. Revisão {1}. Comentários Atualizados</value>
</data>
<data name="FileUploaded" xml:space="preserve">
<value>Ficheiro Carregado: {0}</value>
</data>
<data name="FolderCopied" xml:space="preserve">
<value>Pastas [{0}]. Copiada para a Pasta: {1}</value>
</data>
<data name="FolderCopiedWithOverwriting" xml:space="preserve">
<value>Pastas [{0}]. Copiada com Substituição para a Pasta: {1}</value>
</data>
<data name="FolderCreated" xml:space="preserve">
<value>Pasta Criada: {0}</value>
</data>
<data name="FolderDeleted" xml:space="preserve">
<value>Pasta Eliminada: {0}</value>
</data>
<data name="FolderMoved" xml:space="preserve">
<value>Pastas [{0}]. Movido para a Pasta: {1}</value>
</data>
<data name="FolderMovedToTrash" xml:space="preserve">
<value>Pastas [{0}]. Movido para a Pasta de Reciclagem</value>
</data>
<data name="FolderMovedWithOverwriting" xml:space="preserve">
<value>Pastas [{0}]. Movido com Substituição para a Pasta: {1}</value>
</data>
<data name="FolderRenamed" xml:space="preserve">
<value>Pasta Renomeada: {0}</value>
</data>
<data name="FoldersModule" xml:space="preserve">
<value>Pastas</value>
</data>
<data name="FolderUpdatedAccess" xml:space="preserve">
<value>Pastas [{0}]. O Acesso foi Atualizado</value>
</data>
<data name="FollowActionType" xml:space="preserve">
<value>Seguir</value>
</data>
<data name="FullTextSearchSetting" xml:space="preserve">
<value>As Definições do Sphinx foram atualizadas</value>
</data>
<data name="GeneralModule" xml:space="preserve">
<value>Geral</value>
</data>
<data name="GreetingSettingsUpdated" xml:space="preserve">
<value>As Configurações da Página de Boas Vindas foram Atualizadas</value>
</data>
<data name="GroupCreated" xml:space="preserve">
<value>Grupo Criado: {0}</value>
</data>
<data name="GroupDeleted" xml:space="preserve">
<value>Grupo Eliminado: {0}</value>
</data>
<data name="GroupsModule" xml:space="preserve">
<value>Grupos</value>
</data>
<data name="GroupUpdated" xml:space="preserve">
<value>Grupo Atualizado: {0}</value>
</data>
<data name="GuestAccount" xml:space="preserve">
<value>Conta de Convidado</value>
</data>
<data name="GuestActivated" xml:space="preserve">
<value>Convidado Ativado: {0}</value>
</data>
<data name="GuestCreated" xml:space="preserve">
<value>Convidado Criado: {0}</value>
</data>
<data name="GuestCreatedViaInvite" xml:space="preserve">
<value>Convidado Criado Através de Convite: {0}</value>
</data>
<data name="GuestImported" xml:space="preserve">
<value>Convidado Importado: {0}</value>
</data>
<data name="ImportActionType" xml:space="preserve">
<value>Importar</value>
</data>
<data name="IpCol" xml:space="preserve">
<value>IP</value>
</data>
<data name="LanguageSettingsUpdated" xml:space="preserve">
<value>As Definições de Língua foram Atualizadas</value>
</data>
<data name="LicenseKeyUploaded" xml:space="preserve">
<value>Chave de licença carregada</value>
</data>
<data name="LinkActionType" xml:space="preserve">
<value>Link</value>
</data>
<data name="LoginFail" xml:space="preserve">
<value>Falha no Login</value>
</data>
<data name="LoginFailBruteForce" xml:space="preserve">
<value>Falha no Login. Demasiadas tentativas</value>
</data>
<data name="LoginFailDisabledProfile" xml:space="preserve">
<value>Falha no Login. Perfil Desativado</value>
</data>
<data name="LoginFailInvalidCombination" xml:space="preserve">
<value>Falha no Login. A combinação de nome de utilizador e palavra-chave é inválida</value>
</data>
<data name="LoginFailIpSecurity" xml:space="preserve">
<value>Segurança de IP: Falha no Login</value>
</data>
<data name="LoginFailRecaptcha" xml:space="preserve">
<value>Falha no Login. O Recaptcha é inválido</value>
</data>
<data name="LoginFailSocialAccountNotFound" xml:space="preserve">
<value>Falha no Login. A Conta de Redes Sociais Associada Não foi Encontrada</value>
</data>
<data name="LoginFailViaApi" xml:space="preserve">
<value>Falha no Login através da API</value>
</data>
<data name="LoginFailViaApiSms" xml:space="preserve">
<value>Falha no Login através da API e do Código SMS</value>
</data>
<data name="LoginFailViaApiSocialAccount" xml:space="preserve">
<value>Falha no Login via API e através das Redes Sociais</value>
</data>
<data name="LoginFailViaApiTfa" xml:space="preserve">
<value>Falha no Login via API e pela aplicação autenticadora</value>
</data>
<data name="LoginFailViaSms" xml:space="preserve">
<value>O Login via SMS falhou</value>
</data>
<data name="LoginFailViaSSO" xml:space="preserve">
<value>O Login via SSO Falhou</value>
</data>
<data name="LoginFailViaTfaApp" xml:space="preserve">
<value>O Login através do autenticador Falhou</value>
</data>
<data name="LoginHistoryReportDownloaded" xml:space="preserve">
<value>O Relatório do Histórico de Logins foi Descarregado</value>
</data>
<data name="LoginHistoryReportName" xml:space="preserve">
<value>Relatório do Histórico de Logins ({0}-{1})</value>
</data>
<data name="LoginSuccess" xml:space="preserve">
<value>Login Bem-sucedido</value>
</data>
<data name="LoginSuccessSocialAccount" xml:space="preserve">
<value>Login através da Conta de Redes Sociais Bem-sucedido</value>
</data>
<data name="LoginSuccessSocialApp" xml:space="preserve">
<value>O login através da conta de redes sociais foi Bem-sucedido</value>
</data>
<data name="LoginSuccessViaApi" xml:space="preserve">
<value>Login através da API bem-sucedido</value>
</data>
<data name="LoginSuccessViaApiSms" xml:space="preserve">
<value>Login através da API e do Código de SMS bem-sucedido</value>
</data>
<data name="LoginSuccessViaApiTfa" xml:space="preserve">
<value>Login via API e autenticador Bem-sucedido</value>
</data>
<data name="LoginSuccessViaSms" xml:space="preserve">
<value>Login via SMS bem-sucedido</value>
</data>
<data name="LoginSuccessViaSocialAccount" xml:space="preserve">
<value>Login via API e Redes Sociais Bem-sucedido</value>
</data>
<data name="LoginSuccessViaSSO" xml:space="preserve">
<value>Login via SSO Bem-sucedido</value>
</data>
<data name="LoginSuccesViaTfaApp" xml:space="preserve">
<value>Login via autenticador Bem-sucedido</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Terminar Sessão</value>
</data>
<data name="MailServiceSettingsUpdated" xml:space="preserve">
<value>As Definições do Serviço de E-mail foram Atualizadas</value>
</data>
<data name="ModuleCol" xml:space="preserve">
<value>Módulo</value>
</data>
<data name="MoveActionType" xml:space="preserve">
<value>Mover</value>
</data>
<data name="OthersProduct" xml:space="preserve">
<value>Outros</value>
</data>
<data name="OwnerChanged" xml:space="preserve">
<value>Proprietário Alterado: {0}</value>
</data>
<data name="OwnerSentChangeOwnerInstructions" xml:space="preserve">
<value>Proprietário [{0}]. Enviou Instruções para a Alteração de Proprietário</value>
</data>
<data name="OwnerSentPortalDeactivationInstructions" xml:space="preserve">
<value>Proprietário [{0}]. Enviou Instruções para a Desativação do Portal</value>
</data>
<data name="OwnerSentPortalDeleteInstructions" xml:space="preserve">
<value>Proprietário [{0}]. Enviou Instruções para a Eliminação do Portal</value>
</data>
<data name="PageCol" xml:space="preserve">
<value>Página</value>
</data>
<data name="PasswordStrengthSettingsUpdated" xml:space="preserve">
<value>As Definições da Força da Palavra-Passe foram Atualizadas</value>
</data>
<data name="PeopleProduct" xml:space="preserve">
<value>Pessoas</value>
</data>
<data name="PlatformCol" xml:space="preserve">
<value>Plataforma</value>
</data>
<data name="PortalAccessSettingsUpdated" xml:space="preserve">
<value>As Definições de Acesso ao Portal foram Atualizadas</value>
</data>
<data name="PortalDeactivated" xml:space="preserve">
<value>Portal Desativado</value>
</data>
<data name="PortalDeleted" xml:space="preserve">
<value>Portal Eliminado</value>
</data>
<data name="PrivacyRoomDisable" xml:space="preserve">
<value>A Sala Privada está desativada</value>
</data>
<data name="PrivacyRoomEnable" xml:space="preserve">
<value>A Sala Privada está ativada</value>
</data>
<data name="ProductAccessOpened" xml:space="preserve">
<value>Produtos [{0}]. Acesso Aberto</value>
</data>
<data name="ProductAccessOpenedForGroups" xml:space="preserve">
<value>Produtos: [{0}]. Grupos [{1}]. Acesso Aberto</value>
</data>
<data name="ProductAccessOpenedForUsers" xml:space="preserve">
<value>Produtos: [{0}]. Utilizadores [{1}]. Acesso Aberto</value>
</data>
<data name="ProductAccessRestricted" xml:space="preserve">
<value>Produtos [{0}]. Acesso Restrito</value>
</data>
<data name="ProductAddedAdministrator" xml:space="preserve">
<value>Produtos [{0}]. Administrador Adicionado: {1}</value>
</data>
<data name="ProductCol" xml:space="preserve">
<value>Produto</value>
</data>
<data name="ProductDeletedAdministrator" xml:space="preserve">
<value>Produtos [{0}]. Administrador Eliminado: {1}</value>
</data>
<data name="ProductsListUpdated" xml:space="preserve">
<value>Lista de Produtos Atualizada</value>
</data>
<data name="ProductsModule" xml:space="preserve">
<value>Produtos</value>
</data>
<data name="ReassignsActionType" xml:space="preserve">
<value>Reatribuições</value>
</data>
<data name="SendActionType" xml:space="preserve">
<value>Enviar</value>
</data>
<data name="SentInviteInstructions" xml:space="preserve">
<value>As Instruções de Convite foram Enviadas: {0}</value>
</data>
<data name="SessionCompleted" xml:space="preserve">
<value>Sessão Concluída</value>
</data>
<data name="SessionStarted" xml:space="preserve">
<value>Sessão Iniciada</value>
</data>
<data name="SettingsProduct" xml:space="preserve">
<value>Definições</value>
</data>
<data name="SSODisabled" xml:space="preserve">
<value>SSO Desativado</value>
</data>
<data name="SSOEnabled" xml:space="preserve">
<value>SSO Ativado</value>
</data>
<data name="StartBackupSetting" xml:space="preserve">
<value>Iniciar cópia de segurança</value>
</data>
<data name="StartStorageDecryption" xml:space="preserve">
<value>Iniciar decriptação de armazenamento</value>
</data>
<data name="StartStorageEncryption" xml:space="preserve">
<value>Iniciar encriptação do armazenamento</value>
</data>
<data name="StartTransferSetting" xml:space="preserve">
<value>Iniciar a migração da região do portal</value>
</data>
<data name="SystemAccount" xml:space="preserve">
<value>Conta do Sistema</value>
</data>
<data name="TargetIdCol" xml:space="preserve">
<value>ID do Alvo</value>
</data>
<data name="TeamTemplateChanged" xml:space="preserve">
<value>O Modelo da Equipa foi Alterado</value>
</data>
<data name="TimeTrackingModule" xml:space="preserve">
<value>Monitorização do Tempo</value>
</data>
<data name="TimeZoneSettingsUpdated" xml:space="preserve">
<value>As Definições de Fuso Horário foram Atualizadas</value>
</data>
<data name="TrashEmptied" xml:space="preserve">
<value>Lixo esvaziado</value>
</data>
<data name="TrustedMailDomainSettingsUpdated" xml:space="preserve">
<value>As definições de E-mails fidedignos foram atualizadas</value>
</data>
<data name="TwoFactorAuthenticationSettingsDisabled" xml:space="preserve">
<value>As Definições de Autenticação de Dois Fatores atualizadas: A autenticação de dois Fatores foi desativada</value>
</data>
<data name="TwoFactorAuthenticationSettingsEnabledBySms" xml:space="preserve">
<value>As Definições de Autenticação de Dois Fatores foram atualizadas: A confirmação por SMS foi ativada</value>
</data>
<data name="TwoFactorAuthenticationSettingsEnabledByTfaApp" xml:space="preserve">
<value>As Definições de Autenticação de Dois Fatores foram Atualizadas: A aplicação de Autenticação foi ativada</value>
</data>
<data name="TwoFactorAuthenticationSettingsUpdated" xml:space="preserve">
<value>As Definições de Autenticação de Dois Fatores foram Atualizadas</value>
</data>
<data name="UnfollowActionType" xml:space="preserve">
<value>Não Seguir</value>
</data>
<data name="UnknownAccount" xml:space="preserve">
<value>Conta Desconhecida</value>
</data>
<data name="UnlinkActionType" xml:space="preserve">
<value>Desvincular</value>
</data>
<data name="UpdateAccessActionType" xml:space="preserve">
<value>Acesso Atualizado</value>
</data>
<data name="UpdateActionType" xml:space="preserve">
<value>Atualizar</value>
</data>
<data name="UploadActionType" xml:space="preserve">
<value>Carregar</value>
</data>
<data name="UserActivated" xml:space="preserve">
<value>Utilizador Ativado: {0}</value>
</data>
<data name="UserAddedAvatar" xml:space="preserve">
<value>Utilizadores [{0}]. Avatar Adicionad</value>
</data>
<data name="UserCol" xml:space="preserve">
<value>Utilizador</value>
</data>
<data name="UserCreated" xml:space="preserve">
<value>Utilizador Criado: {0}</value>
</data>
<data name="UserCreatedViaInvite" xml:space="preserve">
<value>Utilizador Criado Via Convite: {0}</value>
</data>
<data name="UserDataReassigns" xml:space="preserve">
<value>Dados Reatribuídos: do utilizador {0} para o utilizador {1}</value>
</data>
<data name="UserDataRemoving" xml:space="preserve">
<value>Os dados do utilizador para o {0} foram eliminados</value>
</data>
<data name="UserDeleted" xml:space="preserve">
<value>Utilizador Eliminado: {0}</value>
</data>
<data name="UserDeletedAvatar" xml:space="preserve">
<value>Utilizadores [{0}]. Avatar Eliminado</value>
</data>
<data name="UserFileUpdated" xml:space="preserve">
<value>Utilizadores [{0}]. Ficheiro Atualizado: {1}</value>
</data>
<data name="UserImported" xml:space="preserve">
<value>Utilizador Importado: {0}</value>
</data>
<data name="UserLinkedSocialAccount" xml:space="preserve">
<value>Conta das Redes Sociais Associada. Fornecedor: {0}</value>
</data>
<data name="UsersDeleted" xml:space="preserve">
<value>Utilizadores Eliminados: {0}</value>
</data>
<data name="UserSentActivationInstructions" xml:space="preserve">
<value>Utilizadores [{0}]. As Instruções de Ativação foram Enviadas</value>
</data>
<data name="UserSentDeleteInstructions" xml:space="preserve">
<value>As Instruções para Eliminar o Perfil foram Enviadas</value>
</data>
<data name="UserSentEmailInstructions" xml:space="preserve">
<value>Utilizadores [{0}]. As Instruções de alteração de E-mail foram Enviadas</value>
</data>
<data name="UserSentPasswordInstructions" xml:space="preserve">
<value>Utilizadores [{0}]. As Instruções para a Alteração de Palavra-Chave foram Enviadas</value>
</data>
<data name="UsersModule" xml:space="preserve">
<value>Utilizadores</value>
</data>
<data name="UsersSentActivationInstructions" xml:space="preserve">
<value>Utilizadores [{0}]. As Instruções de Ativação foram Enviadas</value>
</data>
<data name="UsersUpdatedStatus" xml:space="preserve">
<value>Utilizadores [{0}]. O Estado foi Atualizado</value>
</data>
<data name="UsersUpdatedType" xml:space="preserve">
<value>Utilizadores [{0}]. O Tipo foi Atualizado</value>
</data>
<data name="UserTfaDisconnected" xml:space="preserve">
<value>Aplicação autenticadora desconectada: {0}</value>
</data>
<data name="UserTfaGenerateCodes" xml:space="preserve">
<value>Foram criados novos códigos de cópia de segurança</value>
</data>
<data name="UserUnlinkedSocialAccount" xml:space="preserve">
<value>Conta de Redes Sociais Desvinculada. Fornecedor: {0}</value>
</data>
<data name="UserUpdated" xml:space="preserve">
<value>Utilizador Atualizado: {0}</value>
</data>
<data name="UserUpdatedAvatarThumbnails" xml:space="preserve">
<value>Utilizadores [{0}]. As Miniaturas de Avatar foram Atualizadas</value>
</data>
<data name="UserUpdatedEmail" xml:space="preserve">
<value>Email Atualizado</value>
</data>
<data name="UserUpdatedLanguage" xml:space="preserve">
<value>Idioma Atualizado</value>
</data>
<data name="UserUpdatedMobileNumber" xml:space="preserve">
<value>Utilizadores [{0}]. Número de Telemóvel Atualizado: {1}</value>
</data>
<data name="UserUpdatedPassword" xml:space="preserve">
<value>Palavra-chave Atualizada</value>
</data>
<data name="ThirdPartyCreated" xml:space="preserve">
<value>Foi Criado um Terceiro: {0}. Fornecedor: {1}</value>
</data>
<data name="ThirdPartyDeleted" xml:space="preserve">
<value>Terceiro Eliminado {0}. Fornecedor: {1}</value>
</data>
<data name="ThirdPartyUpdated" xml:space="preserve">
<value>Terceiro Atualizado: {0}. Fornecedor: {1}</value>
</data>
</root>

View File

@ -514,10 +514,10 @@
<value>Products [{0}]. Access Opened</value>
</data>
<data name="ProductAccessOpenedForGroups" xml:space="preserve">
<value>Products: [{0}]. Groups [{1}]. Access Opened</value>
<value>Products: [{0}]. Groups [{1}]. Access Opened</value>
</data>
<data name="ProductAccessOpenedForUsers" xml:space="preserve">
<value>Products: [{0}]. Users [{1}]. Access Opened</value>
<value>Products: [{0}]. Users [{1}]. Access Opened</value>
</data>
<data name="ProductAccessRestricted" xml:space="preserve">
<value>Products [{0}]. Access Restricted</value>

View File

@ -718,4 +718,10 @@
<data name="TagsDeleted" xml:space="preserve">
<value>Теги удалены: {0}</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Страна</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Город</value>
</data>
</root>

View File

@ -652,4 +652,76 @@
<data name="TagsDeleted" xml:space="preserve">
<value>Tagy vymazaný: {0}</value>
</data>
<data name="UserDataRemoving" xml:space="preserve">
<value>Údaje používateľa pre {0} boli odstránené</value>
</data>
<data name="TwoFactorAuthenticationSettingsDisabled" xml:space="preserve">
<value>Nastavenia dvojfaktorového overovania aktualizované: dvojfaktorové overovanie vypnuté</value>
</data>
<data name="TwoFactorAuthenticationSettingsEnabledBySms" xml:space="preserve">
<value>Nastavenia dvojfaktorového overovania aktualizované: potvrdzovanie prostredníctvom SMS povolené</value>
</data>
<data name="TwoFactorAuthenticationSettingsEnabledByTfaApp" xml:space="preserve">
<value>Nastavenia dvojfaktorového overovania aktualizované: povolená aplikácia na overovanie</value>
</data>
<data name="LoginSuccessSocialApp" xml:space="preserve">
<value>Úspešné prihlásenie prostredníctvom sociálnej aplikácie</value>
</data>
<data name="LoginSuccesViaTfaApp" xml:space="preserve">
<value>Úspešné prihlásenie prostredníctvom aplikácie autentifikátora</value>
</data>
<data name="LoginSuccessViaApiTfa" xml:space="preserve">
<value>Úspešné prihlásenie prostredníctvom API a aplikácie autentifikátora</value>
</data>
<data name="DocumentsStoreForcesave" xml:space="preserve">
<value>Nastavenia Forcesave pre úložisko aktualizované</value>
</data>
<data name="StartStorageEncryption" xml:space="preserve">
<value>Spustiť šifrovanie úložiska</value>
</data>
<data name="StartStorageDecryption" xml:space="preserve">
<value>Spustiť dešifrovanie úložiska</value>
</data>
<data name="PrivacyRoomEnable" xml:space="preserve">
<value>Súkromná miestnosť je zapnutá</value>
</data>
<data name="PrivacyRoomDisable" xml:space="preserve">
<value>Súkromná miestnosť je vypnutá</value>
</data>
<data name="UserTfaGenerateCodes" xml:space="preserve">
<value>Boli vytvorené nové záložné kódy</value>
</data>
<data name="LoginFailViaTfaApp" xml:space="preserve">
<value>Prihlásenie cez aplikáciu autentifikátora zlyhalo</value>
</data>
<data name="LoginFailBruteForce" xml:space="preserve">
<value>Prihlásenie zlyhalo. Príliš veľa pokusov</value>
</data>
<data name="LoginFailRecaptcha" xml:space="preserve">
<value>Prihlásenie zlyhalo. Recaptcha je neplatná</value>
</data>
<data name="DocumentsForcesave" xml:space="preserve">
<value>Nastavenia Forcesave aktualizované</value>
</data>
<data name="LoginFailViaApiTfa" xml:space="preserve">
<value>Prihlásenie cez API a aplikáciu autentifikátora zlyhalo</value>
</data>
<data name="UserUpdatedEmail" xml:space="preserve">
<value>E-mail aktualizovaný</value>
</data>
<data name="CustomNavigationSettingsUpdated" xml:space="preserve">
<value>Vlastné nastavenia navigácie aktualizované</value>
</data>
<data name="UserTfaDisconnected" xml:space="preserve">
<value>Aplikácia autentifikátora odpojená: {0}</value>
</data>
<data name="AuditSettingsUpdated" xml:space="preserve">
<value>Nastavenia auditu životnosti aktualizované</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Mesto</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Krajina</value>
</data>
</root>

View File

@ -718,4 +718,7 @@
<data name="UserUpdatedMobileNumber" xml:space="preserve">
<value>Uporabniki [{0}]. Mobilna številka posodobljena: {1}</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Mesto</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="UserUpdatedEmail" xml:space="preserve">
<value>E-posta Güncellendi</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Şehir</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Ülke</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="LoginFailViaApiTfa" xml:space="preserve">
<value>Не вдалося увійти через API й застосунок для аутентифікації</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Місто</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Країна</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="UserUpdatedEmail" xml:space="preserve">
<value>Email được cập nhật</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>Thành phố</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>Quốc gia</value>
</data>
</root>

View File

@ -718,4 +718,10 @@
<data name="UserTfaDisconnected" xml:space="preserve">
<value>身份验证应用已断开连接:{0}</value>
</data>
<data name="CityCol" xml:space="preserve">
<value>市</value>
</data>
<data name="CountryCol" xml:space="preserve">
<value>国家</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

View File

@ -982,6 +982,126 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>LoginWithBruteForceCaptcha</name>
<description/>
<comment/>
<default_text/>
<translations>
<translation>
<language>ar-SA</language>
<approved>false</approved>
</translation>
<translation>
<language>az-Latn-AZ</language>
<approved>false</approved>
</translation>
<translation>
<language>bg-BG</language>
<approved>false</approved>
</translation>
<translation>
<language>cs-CZ</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>el-GR</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fi-FI</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>hy-AM</language>
<approved>false</approved>
</translation>
<translation>
<language>it-IT</language>
<approved>false</approved>
</translation>
<translation>
<language>ja-JP</language>
<approved>false</approved>
</translation>
<translation>
<language>ko-KR</language>
<approved>false</approved>
</translation>
<translation>
<language>lo-LA</language>
<approved>false</approved>
</translation>
<translation>
<language>lv-LV</language>
<approved>false</approved>
</translation>
<translation>
<language>nl-NL</language>
<approved>false</approved>
</translation>
<translation>
<language>pl-PL</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-BR</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-PT</language>
<approved>false</approved>
</translation>
<translation>
<language>ro-RO</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>sk-SK</language>
<approved>false</approved>
</translation>
<translation>
<language>sl-SI</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
<translation>
<language>uk-UA</language>
<approved>false</approved>
</translation>
<translation>
<language>vi-VN</language>
<approved>false</approved>
</translation>
<translation>
<language>zh-CN</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>RecaptchaInvalid</name>
<description/>

View File

@ -73,7 +73,7 @@
"LinkDeletedSuccessfully": "Link deleted successfully",
"LinkDisabledSuccessfully": "Link disabled successfully",
"LinkEditedSuccessfully": "Link edited successfully",
"LinkEnabledSuccessfully": "Link enable successfully",
"LinkEnabledSuccessfully": "Link enabled successfully",
"LinkForPortalUsers": "Link for DocSpace users",
"LinkForRoomMembers": "Link for room members",
"Links": "Links",

View File

@ -11,6 +11,9 @@
"AddTrustedDomain": "Add trusted domain",
"Admins": "Admins",
"AdminsMessage": "Administrator Message Settings",
"AdminsMessageSave": "Click the <strong>Save</strong> button at the bottom to apply.",
"AdminsMessageSettingDescription": "Enable this option to display the DocSpace administrator contact form on the Sign In page.",
"AdminsMessageMobileDescription": "Administrator Message Settings is a way to contact the portal administrator.",
"AdminsMessageDescription": "<1>Administrator Message Settings</1> is a way to contact the DocSpace administrator. <br> Enable this option to display the contact form on the <2>Sign In</2> page so that people could send the message to the space administrator in case they have troubles accessing the space. <br> To make the parameters you set take effect click the <3>Save</3> button at the bottom of the section.",
"AdminsMessageHelper": "Enable this option to display the contact form on the Sign In page so that people could send the message to the administrator in case they have troubles accessing your DocSpace.",
"AllDomains": "Any domains",
@ -70,10 +73,12 @@
"CustomTitles": "Custom titles",
"CustomTitlesFrom": "From",
"CustomTitlesSettingsDescription": "Welcome Page Settings is a way to change the default space title to be displayed on the Welcome Page. The same name is also used for the From field of the space email notifications.",
"CustomTitlesSettingsNavDescription": "Welcome Page Settings is a way to change the default portal title to be displayed on the Welcome Page of your portal. The same name is also used for the From field of your portal email notifications.",
"CustomTitlesSettingsTooltip": "<0>{{ welcomeText }}</0> is a way to change the default space title to be displayed on the <2>{{ text }}</2> of your space. The same name is also used for the <4>{{ from }}</4> field of the space email notifications.",
"CustomTitlesSettingsTooltipDescription": "Enter the name you like in the <1>{{ header }}</1> field.",
"CustomTitlesText": "Welcome Page",
"CustomTitlesWelcome": "Welcome Page Settings",
"CustomTitlesDescription": "Adjust the default space title displayed on the Welcome Page and in the From field of the email notifications.",
"DataBackup": "Data backup",
"Deactivate": "Deactivate",
"DeactivateOrDeletePortal": "Deactivate or delete space.",
@ -86,7 +91,8 @@
"DeveloperTools": "Developer Tools",
"Disabled": "Disabled",
"DNSSettings": "DNS Settings",
"DNSSettingsDescription": "DNS Settings is a way to set an alternative URL for your space.",
"DNSSettingsDescription": "Set an alternative URL address for your space. Send your request to our support team to get help with the settings.",
"DNSSettingsNavDescription": "DNS Settings is a way to set an alternative URL for your portal.",
"DNSSettingsMobile": "Send your request to our support team, and our specialists will help you with the settings.",
"DNSSettingsTooltipMain": "DNS Settings allow you to set an alternative URL address for your {{ organizationName }} space.",
"DNSSettingsTooltipStandalone": "Check the 'Custom domain name box' and specify your own domain name for the ONLYOFFICE space in the field below. To make the parameters you set take effect click the 'Save button' at the bottom of the section.",
@ -110,10 +116,13 @@
"ForcePathStyle": "Force Path Style",
"IntegrationRequest": "Missing a useful integration or component in ONLYOFFICE DocSpace? Leave a request to our team and we will look into that.",
"IPSecurity": "IP Security",
"IPSecuritySettingDescription": "Configure IP Security to restrict login possibility to select IP addresses. Use either exact IP addresses in the IPv4 format, IP range or CIDR masking. The IP security does not work for space owners, they can access the space from any IP address.",
"IPSecurityMobileDescription": "IP Security is used to restrict login to the portal from all IP addresses except certain addresses.",
"IPSecurityDescription": "<1>IP Security</1> is used to restrict login to the space from all IP addresses except certain addresses. You can set the allowed IP addresses using either exact IP addresses in the IPv4 format (#.#.#.#, where # is a numeric value from 0 to 255), IP range (in the #.#.#.#-#.#.#.# format), or CIDR masking (in the #.#.#.#/# format). The IP security does not work for space owners, they can access the space from any IP address. Rules set in the For all users section apply to full access administrators as well. At the same time, you can set additional rules for full access administrators in the corresponding section.",
"IPSecurityHelper": "You can set the allowed IP addresses using either exact IP addresses in the IPv4 format (#.#.#.#, where # is a numeric value from 0 to 255) or IP range (in the #.#.#.#-#.#.#.# format).",
"IPSecurityWarningHelper": "First, you need to specify your current IP or the IP range your current IP address belongs to, otherwise your space access will be blocked right after you save the settings. The space owner will have the space access from any IP address.",
"LanguageAndTimeZoneSettingsDescription": "Language and Time Zone Settings is a way to change the language of the space for all users and to configure the time zone so that all the actions will be shown with the correct date and time.",
"LanguageAndTimeZoneSettingsNavDescription": "Language and Time Zone Settings is a way to change the language of the whole portal for all portal users and to configure the time zone so that all the events of the portal will be shown with the correct date and time.",
"LanguageTimeSettingsTooltip": "<0>{{text}}</0> is a way to change the language of the space for all users and to configure the time zone so that all the actions of the {{ organizationName }} space will be shown with the correct date and time.",
"LanguageTimeSettingsTooltipDescription": "To make the parameters you set take effect click the <1>{{save}}</1> button at the bottom of the section.<3>{{learnMore}}</3>",
"Lifetime": "Lifetime (min)",
@ -145,6 +154,7 @@
"Plugins": "Plugins",
"PortalAccess": "DocSpace access",
"PortalAccessSubTitle": "This section allows you to provide users with safe and convenient ways to access the space.",
"PortalSecurityTitle": "This subsection allows you to provide users with secure and convenient ways to access the portal.",
"PortalDeactivation": "Deactivate DocSpace",
"PortalDeactivationDescription": "Use this option to deactivate your space temporarily.",
"PortalDeactivationHelper": "If you wish to deactivate this DocSpace, your space and all information associated with it will be blocked so that no one has access to it for a particular period. To do that, click the Deactivate button. A link to confirm the operation will be sent to the email address of the space owner.\nIn case you want to come back to the space and continue using it, you will need to use the second link provided in the confirmation email. So, please, keep this email in a safe place.",
@ -157,7 +167,10 @@
"PortalNameIncorrect": "Incorrect account name",
"PortalNameLength": "The account name must be between {{minLength}} and {{maxLength}} characters long",
"PortalRenaming": "DocSpace Renaming",
"PortalRenamingDescriptionText": "Change the space address that appears next to {{ domain }}.",
"PortalRenamingNote": "<strong>Note:</strong> Your old space address will become unavailable to new users once you click the Save button.",
"PortalRenamingDescription": "Here you can change your space address.",
"PortalRenamingNavDescription": "Here you can change your portal address.",
"PortalRenamingLabelText": "New space name",
"PortalRenamingMobile": "Enter the part that will appear next to the {{domain}} space address. Please note: your old space address will become unavailable to new users once you click the Save button.",
"PortalRenamingModalText": "You are about to rename your portal. Are you sure you want to continue?",
@ -176,9 +189,15 @@
"ServerSideEncryptionMethod": "Server Side Encryption Method",
"ServiceUrl": "Service Url",
"SessionLifetime": "Session Lifetime",
"SessionLifetimeSettingDescription": "Adjust Session Lifetime to define the time period before automatic logoff. After saving, logoff will be performed for all users.",
"SessionLifetimeMobileDescription": "Session Lifetime allows to set time (in minutes) before the DocSpace users will need to enter the space credentials again in order to access the space.",
"SessionLifetimeDescription": "<1>Session Lifetime</1> allows to set time (in minutes) before the space users will need to enter the space credentials again in order to access the space. After save all the users will be logged out from space.",
"SessionLifetimeHelper": "After saving, all the users will be logged out from the space.",
"SettingPasswordStrength": "Setting password strength",
"SettingPasswordTittle": "Password Strength Settings",
"SettingPasswordDescription": "Configure Password Strength Settings to enforce more secure, computation-resistant passwords.",
"SettingPasswordDescriptionSave": "Click the <strong>Save</strong> button at the bottom to apply.",
"SettingPasswordStrengthMobileDescription": "Password Strength Settings is a way to determine the effectiveness of a password in resisting guessing and brute-force attacks.",
"SettingPasswordStrengthDescription": "<1>Password Strength Settings</1> is a way to determine the effectiveness of a password in resisting guessing and brute-force attacks. <br> Use the <2>Minimum Password Length</2> bar to determine how long the password should be. Check the appropriate boxes below to determine the character set that must be used in the password. <br> To make the parameters you set take effect click the <3>Save</3> button at the bottom of the section.",
"SettingPasswordStrengthHelper": "Use the Minimum Password Length bar to determine how long the password should be. Check the appropriate boxes below to determine the character set that must be used in the password.",
"ShowFeedbackAndSupport": "Show Feedback & Support link",
@ -189,6 +208,8 @@
"SMTPSettingsDescription": "The SMTP settings are needed to set up an email account which will be used to send notifications from the portal using your own SMTP server instead of the one {{organizationName}} uses. Please fill in all the fields and click the 'Save' button. You can use the 'Send Test Mail' button to check if all the settings you entered are correct and work as supposed.",
"StoragePeriod": "Storage period",
"StudioTimeLanguageSettings": "Language and Time Zone Settings",
"TimeLanguageSettingsDescription": "Change Language and Time Zone Settings to adjust common DocSpace language and time.",
"TimeLanguageSettingsSave": "Click <strong>Save</strong> at the bottom to apply.",
"Submit": "Submit",
"SuccessfullySaveGreetingSettingsMessage": "Welcome Page settings have been successfully saved",
"SuccessfullySavePortalNameMessage": "Space has been renamed successfully",
@ -206,9 +227,16 @@
"ThirdPartyTitleDescription": "With Authorization keys, you can connect third-party services to your space. Sign in easily with Facebook, Google, or LinkedIn. Add Dropbox, OneDrive, and other accounts to work with files stored there.",
"TimeZone": "Time zone",
"TrustedMail": "Trusted mail domain settings",
"TrustedMailSettingDescription": "Configure Trusted Mail Domain Settings to specify the allowed mail servers to use for self-registration.",
"TrustedMailSave": "Click the <strong>Save</strong> button at the bottom to apply.",
"TrustedMailMobileDescription": "Trusted Mail Domain Settings is a way to specify the mail servers used for user self-registration.",
"TrustedMailDescription": "<1>Trusted Mail Domain Settings</1> is a way to specify the mail servers used for user self-registration. <br> You can either check the <2>Custom domains</2> option and enter the trusted mail server in the field below so that a person who has an account at it will be able to register him(her)self by clicking the Join link on the <3>Sign In</3> page or disable this option. <br> To make the parameters you set take effect, click the <4>Save</4> button at the bottom of the section.",
"TrustedMailHelper": "You can either check the Custom domains option and enter the trusted mail server in the field below so that a person who has an account at it will be able to register him(her)self by clicking the Join link on the Sign In page or disable this option.",
"TwoFactorAuth": "Two-factor authentication",
"TwoFactorAuthEnableDescription": "Enable two-factor authentication for a more secure DocSpace access for users.",
"TwoFactorAuthSave": "Click the <strong>Save</strong> button below to apply.",
"TwoFactorAuthNote": "<strong>Note:</strong> make sure to always have positive balance when SMS message option is chosen.",
"TwoFactorAuthMobileDescription": "Two-factor authentication is a more secure way for the users to enter the portal. After the credentials are entered, the user will have to enter the code from the SMS received to the mobile phone with the number which was specified at the first portal login or the code from an authentication application.",
"TwoFactorAuthDescription": "<1>Two-factor authentication</1> is a more secure way for the users to enter the DocSpace. After the credentials are entered, the user will have to enter the code from the SMS received to the mobile phone with the number which was specified at the first space login or the code from an authentication application. <br> Enable this option for a more secure DocSpace access by all the DocSpace users. <br> To apply the changes you made click the <2>Save</2> button below this section. <br> <3>Note</3>: SMS messages can be sent if you have a positive balance only. You can always check your current balance in your SMS provider account. Do not forget to replenish your balance in good time.",
"TwoFactorAuthHelper": "Note: SMS messages can be sent if you have a positive balance only. You can always check your current balance in your SMS provider account. Do not forget to replenish your balance in good time.",
"UnsavedChangesBody": "If you close the link settings menu right now, your changes will not be saved.",

View File

@ -15,6 +15,5 @@
"ShareEmailBody": "You have been granted access to the {{itemName}} document. Click the link below to open the document right now: {{shareLink}}.",
"ShareEmailSubject": "You have been granted access to the {{itemName}} document",
"ShareVia": "Share via",
"SharingSettingsTitle": "Sharing settings",
"Shorten": "Shorten"
"SharingSettingsTitle": "Sharing settings"
}

View File

@ -40,7 +40,7 @@
"Other": "Other",
"OwnerChange": "Change owner",
"Presentations": "Presentations",
"PublicRoomLinkValidTime": "This link is valid until {{date}} Once it expires, it will be impossible to access the room via this link.",
"PublicRoomLinkValidTime": "This link is valid until {{date}}. Once it expires, it will be impossible to access the room via this link.",
"Remove": "Remove",
"RoleCommentator": "Сommenter",
"RoleCommentatorDescription": "Operations with existing files: viewing, commenting.",

View File

@ -23,23 +23,23 @@
"PayloadUrl": "Payload URL",
"ReadMore": "Read more",
"Request": "Request",
"RequestBodyCopied": "Request post body successfully copied to clipboard",
"RequestHeaderCopied": "Request post header successfully copied to clipboard",
"RequestPostBody": "Request post body",
"RequestPostHeader": "Request post header",
"RequestBodyCopied": "POST request body successfully copied to clipboard",
"RequestHeaderCopied": "POST request header successfully copied to clipboard",
"RequestPostBody": "POST request body",
"RequestPostHeader": "POST request header",
"ResetKey": "Reset key",
"Response": "Response",
"ResponseBodyCopied": "Response post body successfully copied to clipboard",
"ResponseHeaderCopied": "Response post header successfully copied to clipboard",
"ResponsePostBody": "Response post body",
"ResponsePostHeader": "Response post header",
"ResponseBodyCopied": "POST response body successfully copied to clipboard",
"ResponseHeaderCopied": "POST response header successfully copied to clipboard",
"ResponsePostBody": "POST response body",
"ResponsePostHeader": "POST response header",
"Retry": "Retry",
"SecretKey": "Secret key",
"SecretKeyHint": "Setting a webhook secret allows you to verify requests sent to the payload URL.",
"SecretKeyWarning": "You cannot retrieve your secret key again once it has been saved. If you've lost or forgotten this secret key, you can reset it, but all integrations using this secret will need to be updated.",
"SelectDate": "Select date",
"SelectDeliveryTime": "Select Delivery time",
"SettingsWebhook": "Settings webhook",
"SettingsWebhook": "Webhook settings",
"SSLHint": "By default, we verify SSL certificates when delivering payloads.",
"SSLVerification": "SSL verification",
"State": "State",

View File

@ -12,6 +12,8 @@
"MakeRoomPrivateDescription": "Все файлы в этой комнате будут зашифрованы.",
"MakeRoomPrivateLimitationsWarningDescription": "С помощью данной функции Вы можете пригласить только уже существующих пользователей DocSpace. После создания комнаты изменить список пользователей будет нельзя.",
"MakeRoomPrivateTitle": "Сделайте комнату приватной",
"PublicRoomBarDescription": "Эта комната доступна всем, у кого есть ссылка. Сторонние пользователи получат разрешение только на просмотр всех файлов.",
"PublicRoomDescription": "Пригласите пользователей через внешние ссылки, чтобы они могли просматривать документы без регистрации. Также вы можете встроить эту комнату в любой веб-интерфейс.",
"ReviewRoomDescription": "Запроситe рецензию или комментарии к документам",
"ReviewRoomTitle": "Комната для рецензирования",
"RoomEditing": "Редактирование комнаты",

View File

@ -5,5 +5,9 @@
"ErrorDeactivatedText": "Этот DocSpace деактивирован",
"ErrorEmptyResponse": "Пустой ответ",
"ErrorOfflineText": "Нет подключения к интернету",
"ErrorUnavailableText": "DocSpace недоступен"
"ErrorUnavailableText": "DocSpace недоступен",
"ExpiredLink": "Просроченная ссылка",
"InvalidLink": "Недействительная ссылка",
"LinkDoesNotExist": "Ссылки, которую вы пытаетесь открыть, не существует.",
"LinkHasExpired": "Ссылка, по которой вы перешли, просрочена."
}

View File

@ -1,7 +1,10 @@
{
"AddMembersDescription": "Вы можете добавить новых участников команды вручную или пригласить их по ссылке.",
"AddNewExternalLink": "Добавить новую внешнюю ссылку",
"AddNewLink": "Добавить новую ссылку",
"All": "Все",
"AllFiles": "Все файлы",
"AllLinksAreDisabled": "Все ссылки отключены",
"ArchiveAction": "Пустой архив",
"ArchivedRoomAction": "Комната '{{name}}' заархивирована",
"ArchivedRoomsAction": "Комнаты заархивированы",
@ -14,19 +17,29 @@
"ByCreation": "Создан",
"ByErasure": "Стирание",
"ByLastModified": "Изменен",
"ChooseExpirationDate": "Вы можете выбрать срок действия для этой ссылки",
"ChooseExpirationDate": "Вы можете выбрать срок действия для этой ссылки.",
"Clean": "Очистить",
"CollaborationRooms": "Совместное редактирование",
"ContainsSpecCharacter": "Название не должно содержать следующих символов: *+:\"<>?|/",
"Convert": "Конвертация",
"CopyItem": "<strong>{{title}}</strong> скопирован",
"CopyItems": "Скопировано элементов: <strong>{{qty}}</strong>",
"CopyLinkPassword": "Скопировать пароль для ссылки",
"CopyPassword": "Скопировать пароль",
"CreateRoom": "Создание комнаты",
"CustomRooms": "Пользовательская",
"DaysRemaining": "Дней осталось: {{daysRemaining}}",
"DeleteLink": "Удалить ссылку",
"DeleteLinkNote": "Ссылка будет удалена окончательно. Вы не сможете отменить это действие.",
"DisableDownload": "Отключить функцию скачивания",
"DisableLink": "Отключить ссылку",
"DisableNotifications": "Выключить уведомления",
"Document": "Документ",
"DocumentEdited": "Невозможно выполнить действие, так как документ редактируется.",
"DownloadAll": "Скачать все",
"EditLink": "Редактировать ссылку",
"EditRoom": "Изменить комнату",
"EmbeddingSettings": "Настройки встраивания",
"EmptyFile": "Пустой файл",
"EmptyFilterDescriptionText": "В этом разделе нет файлов или папок, соответствующих фильтру. Пожалуйста, выберите другие параметры или очистите фильтр, чтобы показать все файлы в этом разделе.",
"EmptyFilterSubheadingText": "Здесь нет файлов, соответствующих этому фильтру",
@ -36,6 +49,7 @@
"EmptyRecycleBin": "Очистить корзину",
"EmptyRootRoomHeader": "Добро пожаловать в DocSpace!",
"EmptyScreenFolder": "Здесь пока нет документов",
"EnableLink": "Активировать ссылку",
"EnableNotifications": "Включить уведомления",
"ExcludeSubfolders": "Исключить вложенные папки",
"FavoritesEmptyContainerDescription": "Чтобы добавить файлы в избранное или удалить их из этого списка, используйте контекстное меню.",
@ -54,43 +68,60 @@
"GoToPersonal": "Перейти к Моим документам",
"Images": "Изображения",
"InviteUsersInRoom": "Пригласить пользователей в комнату",
"LimitByTimePeriod": "Ограничение по периоду времени",
"LinkAddedSuccessfully": "Ссылка успешно добавлена",
"LinkDeletedSuccessfully": "Ссылка успешно удалена",
"LinkDisabledSuccessfully": "Ссылка успешно отключена",
"LinkEditedSuccessfully": "Ссылка успешно отредактирована",
"LinkEnabledSuccessfully": "Ссылка успешно активирована",
"LinkForPortalUsers": "Ссылка для пользователей портала",
"LinkForRoomMembers": "Ссылка для участников комнаты",
"Links": "Ссылки",
"LinkSuccessfullyCopied": "Ссылка успешно скопирована",
"LinkValidUntil": "Эта ссылка действительна до",
"MarkAsFavorite": "Добавить в избранное",
"MarkAsRevision": "Отметить как ревизию",
"MarkAsVersion": "Отметить как версию",
"MarkedAsFavorite": "Добавлено в избранное",
"MarkRead": "Пометить прочтённым",
"MaximumNumberOfExternalLinksCreated": "Максимальное количество созданных внешних ссылок",
"Media": "Медиа",
"MoveItem": "<strong>{{title}}</strong> перемещен",
"MoveItems": "Перемещено элементов: <strong>{{qty}}</strong>",
"MoveOrCopy": "Переместить или скопировать",
"MoveToArchive": "Переместить в архив",
"MoveToFolderMessage": "Нельзя перенести папку в свою дочернюю папку",
"MoveToPublicRoom": "Эта комната и все ее содержимое доступна всем, у кого есть ссылка. Продолжить?",
"MoveToPublicRoomTitle": "Перейти в Публичную комнату",
"New": "Новое",
"NewRoom": "Новая комната",
"NoAccessRoomDescription": "Вы будете автоматически перенаправлены в Мои комнаты через 5 секунд.",
"NoAccessRoomTitle": "Извините, у вас нет доступа к этой комнате.",
"NoExternalLinks": "Нет внешних ссылок",
"NoFilesHereYet": "Здесь пока нет файлов",
"Open": "Открыть",
"OpenLocation": "Открыть папку",
"PasswordLink": "Вы можете поставить пароль на ссылку",
"PasswordAccess": "Доступ по паролю",
"PasswordLink": "Добавить пароль для защиты ссылки.",
"PasswordSuccessfullyCopied": "Пароль успешно скопирован",
"Pin": "Закрепить",
"PinToTop": "Закрепить наверху",
"Presentation": "Презентация",
"PreventDownloadFilesAndFolders": "Вы можете запретить скачивать файлы и папки в этой комнате",
"PreventDownloadFilesAndFolders": "Заблокировать скачивание файлов и папок из этой комнаты для безопасности ваших данных.",
"PrivateRoomDescriptionEncrypted": "Зашифрованное редактирование и совместная работа в режиме реального времени.",
"PrivateRoomDescriptionSafest": "Самое безопасное хранилище для файлов docx, xlsx и pptx.",
"PrivateRoomDescriptionSecure": "Безопасное предоставление доступа доверенным участникам команды.",
"PrivateRoomDescriptionUnbreakable": "Стойкий алгоритм AES-256.",
"PrivateRoomHeader": "Добро пожаловать в Приватную комнату ONLYOFFICE, где каждый символ, который вы вводите, шифруется",
"PrivateRoomSupport": "Работа в Приватной комнате доступна через десктопное приложение {{organizationName}}. <3>Инструкции</3>",
"PublicRoom": "Публичная комната",
"RecentEmptyContainerDescription": "В этом разделе будут отображаться ваши последние просмотренные или отредактированные документы.",
"RecycleBinAction": "Пустая корзина",
"RemovedFromFavorites": "Удалено из избранного",
"RemoveFromFavorites": "Удалить из избранного",
"RemoveFromList": "Убрать из списка",
"RestoreAll": "Восстановить все",
"RoomAvailableViaExternalLink": "Комната доступна по внешней ссылке",
"RoomCreated": "Комната создана",
"RoomEmptyContainerDescription": "Пожалуйста, создайте первую комнату.",
"RoomEmptyContainerDescriptionUser": "Комнаты, которыми с вами поделились, будут отображаться здесь.",
@ -101,10 +132,14 @@
"RoomsRemoved": "Комнаты удалены",
"RoomUnpinned": "Комната откреплена",
"SearchByContent": "Поиск по содержимому файла",
"SelectorEmptyScreenHeader": "Здесь пока нет файлов и папок",
"SendByEmail": "Отправить по почте",
"Share": "Доступ",
"ShareRoom": "Предоставить доступ к комнате",
"ShowLinkActions": "Показать действия со ссылками",
"ShowVersionHistory": "Показать историю версий",
"Spreadsheet": "Таблица",
"TableSettingsTitle": "Управление отображаемыми столбцами",
"TooltipElementCopyMessage": "Скопировать {{element}}",
"TooltipElementsCopyMessage": "Скопировать {{element}} элемента(ов)",
"TooltipElementsMoveMessage": "Переместить {{element}} элемента(ов)",
@ -119,5 +154,7 @@
"ViewList": "Список",
"ViewOnlyRooms": "Просмотр",
"ViewTiles": "Плитки",
"WantToRestoreTheRoom": "Все внешние ссылки в этой комнате станут активными, и ее содержимое будет доступно всем, у кого есть ссылка. Восстановить комнату?",
"WantToRestoreTheRooms": "Все внешние ссылки в восстановленных комнатах станут активными, а их содержимое будет доступно всем, у кого есть ссылки на комнаты. Восстановить комнаты?",
"WithSubfolders": "С подпапками"
}

View File

@ -29,6 +29,7 @@
"HistoryEmptyScreenText": "Здесь будет отображаться история активности",
"ItemsSelected": "Выбрано элементов",
"LastModifiedBy": "Автор последнего корректива",
"LinksToViewingIcon": "Ссылки на просмотр",
"PendingInvitations": "Приглашения в ожидании",
"Properties": "Cвойства",
"RoomsEmptyScreenTent": "Здесь будут представлены подробности о комнатах",

View File

@ -40,6 +40,7 @@
"BackupList": "Список резервных копий",
"BackupListWarningText": "При удалении любых элементов из списка соответствующие им файлы тоже будут удалены. Это действие необратимо. Для удаления всех файлов используйте ссылку:",
"BetaLabel": "БЕТА",
"BlockingTime": "Время блокировки (сек)",
"Branding": "Брендинг",
"BrandingSectionDescription": "Укажите информацию о своей компании, добавьте ссылки на внешние ресурсы и адреса электронной почты, отображаемые в интерфейсе DocSpace.",
"BrandingSubtitle": "Используйте эту опцию, чтобы предоставить пользователям возможность использовать порталы, соответствующие имиджу и идентичности вашего бренда.",
@ -47,12 +48,17 @@
"BreakpointSmallTextPrompt": "Пожалуйста, измените размер окна или включите полноэкранный режим",
"BreakpointWarningText": "Этот раздел доступен только в десктопной версии",
"BreakpointWarningTextPrompt": "Пожалуйста, используйте десктопный сайт для доступа к настройкам <1>{{sectionName}}</1> .",
"BruteForceProtection": "Защита от брутфорс-атак",
"BruteForceProtectionDescription": "Установите лимит неудачных попыток входа пользователя для защиты от брутфорс-атак. При достижении лимита попытки входа с соответствующего IP-адреса будут заблокированы на определенный период времени или потребуется ввести код проверки, если такая функция включена.",
"BruteForceProtectionDescriptionMobile": "Для защиты портала от брутфорс-атак вы можете настроить лимит неудачных попыток входа пользователя.",
"ButtonsColor": "Кнопки",
"ByApp": "С помощью приложения для аутентификации",
"BySms": "С помощью SMS",
"ChangeLogoButton": "Сменить логотип",
"Characters": "{{length}} символов",
"CheckPeriod": "Период проверки (сек)",
"ClearBackupList": "Удалить все резервные копии",
"CloseMenu": "Закрыть меню",
"CompanyInfoSettings": "Настройки информации о компании",
"CompanyInfoSettingsDescription": "Эта информация будет отображаться в окне <1>{{link}}</1> .",
"ConfirmEmailSended": "Письмо с подтверждением отправлено {{ownerName}}",
@ -94,7 +100,10 @@
"EmptyBackupList": "Еще не было создано ни одной резервной копии. Создайте хотя бы одну резервную копию, чтобы она появилась в этом списке.",
"EnableAutomaticBackup": "Давать возможность автоматически копировать данные",
"EnableAutomaticBackupDescription": "Используйте эту опцию для выполнения резервного копирования данных портала.",
"EnterNumber": "Ввести число",
"EnterTime": "Ввести время",
"EnterTitle": "Укажите название",
"ErrorMessageBruteForceProtection": "Указанный аргумент находился вне диапазона допустимых значений.",
"EveryDay": "Каждый день",
"EveryMonth": "Каждый месяц",
"EveryWeek": "Каждую неделю",
@ -128,6 +137,7 @@
"MaxCopies": "{{copiesCount}} - максимальное число копий",
"Migration": "Миграция",
"NewColorScheme": "Новая цветовая схема",
"NumberOfAttempts": "Число попыток",
"PasswordMinLenght": "Минимальная длина пароля",
"Path": "Путь",
"PleaseNote": "Пожалуйста, обратите внимание",
@ -201,6 +211,7 @@
"TwoFactorAuth": "Двухфакторная аутентификация",
"TwoFactorAuthDescription": "<1>Двухфакторная аутентификация</1> обеспечивает более безопасный способ входа пользователей в DocSpace. После ввода учетных данных пользователю необходимо будет ввести код из SMS, полученного на мобильный телефон с номером, который был указан при первом входе на портал, или код из приложения для аутентификации. <br> Включите этот параметр, чтобы обеспечить более безопасный доступ к DocSpace для всех пользователей DocSpace. <br> Чтобы применить внесенные изменения, нажмите кнопку <2>Сохранить</2> под этим разделом. <br> <3>Примечание</3>: SMS-сообщения можно отправлять только при положительном балансе. Вы всегда можете проверить текущий баланс в своем аккаунте провайдера SMS. Не забывайте своевременно пополнять баланс.",
"TwoFactorAuthHelper": "Обратите внимание: отправка SMS-сообщений осуществляется только при положительном балансе. Вы всегда можете проверить текущий баланс в учетной записи вашего SMS-провайдера. Не забывайте своевременно пополнять баланс.",
"UnsavedChangesBody": "Если вы закроете меню настроек ссылки прямо сейчас, изменения не сохранятся.",
"UseAsLogoButton": "Использовать как логотип",
"UseDigits": "Использовать цифры",
"UseHttp": "Использовать Http",

View File

@ -9,6 +9,7 @@
"ExternalLink": "Внешняя ссылка",
"FormFilling": "Заполнение форм",
"InternalLink": "Внутренняя ссылка",
"LinkName": "Имя ссылки",
"Notify users": "Уведомить пользователей",
"ReadOnly": "Только чтение",
"ShareEmailBody": "Вам предоставлен доступ к документу {{itemName}}. Нажмите на ссылку ниже, чтобы открыть документ прямо сейчас: {{shareLink}}.",

View File

@ -27,6 +27,7 @@
"FolderTitleYandex": "Каталог Яндекса",
"FormTemplates": "Шаблоны форм",
"LinkCopySuccess": "Ссылка скопирована в буфер обмена",
"LinkHasExpiredAndHasBeenDisabled": "Срок действия ссылки истек, она отключена",
"LinkValidTime": "Эта ссылка действительна только {{days_count}} дней.",
"MobileAndroid": "Скачать ONLYOFFICE Документы в Google Play",
"MobileIos": "Скачать ONLYOFFICE Документы в App Store",
@ -39,6 +40,7 @@
"Other": "Другой",
"OwnerChange": "Сменить владельца",
"Presentations": "Презентации",
"PublicRoomLinkValidTime": "Эта ссылка действительна до {{date}}. По истечении срока ее действия доступ к комнате по этой ссылке будет невозможен.",
"Remove": "Удалить",
"RoleCommentator": "Комментатор",
"RoleCommentatorDescription": "Операции с существующими файлами: просмотр, комментирование.",

View File

@ -240,7 +240,11 @@ export default function withFileActions(WrappedFileItem) {
let className = isDragging ? " droppable" : "";
if (draggable) className += " draggable";
let value = !item.isFolder ? `file_${id}` : `folder_${id}`;
let value = item.isFolder
? `folder_${id}`
: item.isDash
? `dash_${id}`
: `file_${id}`;
value += draggable ? "_draggable" : "_false";
value += `_index_${itemIndex}`;

View File

@ -78,6 +78,8 @@ export type useRoomsHelperProps = {
isFirstLoad: boolean;
setIsRoot: (value: boolean) => void;
searchValue?: string;
isRoomsOnly: boolean;
onSetBaseFolderPath?: (value: number | string | undefined) => void;
};
export type useFilesHelpersProps = {
@ -98,13 +100,24 @@ export type useFilesHelpersProps = {
setSelectedTreeNode: (treeNode: any) => void;
filterParam?: string;
getRootData?: () => Promise<void>;
onSetBaseFolderPath?: (value: number | string | undefined) => void;
isRoomsOnly: boolean;
rootThirdPartyId?: string;
getRoomList?: (
startIndex: number,
isInit?: boolean,
search?: string | null,
isErrorPath?: boolean
) => void;
t: any;
};
export type FilesSelectorProps = {
isPanelVisible: boolean;
withoutBasicSelection: boolean;
withoutImmediatelyClose: boolean;
// withoutImmediatelyClose: boolean;
isThirdParty: boolean;
rootThirdPartyId?: string;
isRoomsOnly: boolean;
isEditorDialog: boolean;
setMoveToPublicRoomVisible: (visible: boolean, operationData: object) => void;

View File

@ -21,6 +21,7 @@ import {
FilterType,
FolderType,
} from "@docspace/common/constants";
import toastr from "@docspace/components/toast/toastr";
const getIconUrl = (extension: string, isImage: boolean, isMedia: boolean) => {
// if (extension !== iconPath) return iconSize32.get(iconPath);
@ -267,6 +268,11 @@ export const useFilesHelper = ({
setSelectedTreeNode,
filterParam,
getRootData,
onSetBaseFolderPath,
isRoomsOnly,
rootThirdPartyId,
getRoomList,
t,
}: useFilesHelpersProps) => {
const getFileList = React.useCallback(
async (
@ -313,21 +319,27 @@ export const useFilesHelper = ({
filter.folder = id.toString();
try {
const setSettings = async (folderId, isErrorPath = false) => {
if (isInit && getRootData) {
const folder = await getFolderInfo(id);
const folder = await getFolderInfo(folderId);
if (
folder.rootFolderType === FolderType.TRASH ||
folder.rootFolderType === FolderType.Archive
) {
if (isRoomsOnly) {
await getRoomList(0, true, null, true);
toastr.error(
t("Files:ArchivedRoomAction", { name: folder.title })
);
return;
}
await getRootData();
return;
}
}
const currentFolder = await getFolder(id, filter);
const currentFolder = await getFolder(folderId, filter);
const { folders, files, total, count, pathParts, current } =
currentFolder;
@ -350,35 +362,31 @@ export const useFilesHelper = ({
setSelectedTreeNode({ ...current, path: pathParts });
if (isInit) {
if (isThirdParty) {
const breadCrumbs: BreadCrumb[] = [
{ label: current.title, isRoom: false, id: current.id },
];
const breadCrumbs: BreadCrumb[] = await Promise.all(
pathParts.map(async (folderId: number | string) => {
const folderInfo: any = await getFolderInfo(folderId);
setBreadCrumbs(breadCrumbs);
setIsBreadCrumbsLoading(false);
} else {
const breadCrumbs: BreadCrumb[] = await Promise.all(
pathParts.map(async (folderId: number | string) => {
const folderInfo: any = await getFolderInfo(folderId);
const { title, id, parentId, rootFolderType, roomType } =
folderInfo;
const { title, id, parentId, rootFolderType, roomType } =
folderInfo;
return {
label: title,
id: id,
isRoom: parentId === 0 && rootFolderType === FolderType.Rooms,
roomType,
};
})
);
return {
label: title,
id: id,
isRoom: parentId === 0 && rootFolderType === FolderType.Rooms,
roomType,
};
})
);
!isThirdParty &&
!isRoomsOnly &&
breadCrumbs.unshift({ ...defaultBreadCrumb });
setBreadCrumbs(breadCrumbs);
setIsBreadCrumbsLoading(false);
}
onSetBaseFolderPath &&
onSetBaseFolderPath(isErrorPath ? [] : breadCrumbs);
setBreadCrumbs(breadCrumbs);
setIsBreadCrumbsLoading(false);
}
if (isFirstLoad || startIndex === 0) {
@ -392,7 +400,24 @@ export const useFilesHelper = ({
}
setIsRoot(false);
setIsNextPageLoading(false);
};
try {
await setSettings(id);
} catch (e) {
if (isThirdParty) {
await setSettings(rootThirdPartyId, true);
toastr.error(e);
return;
}
if (isRoomsOnly) {
await getRoomList(0, true, null, true);
toastr.error(e);
return;
}
getRootData && getRootData();
}
},

View File

@ -76,9 +76,16 @@ const useRoomsHelper = ({
isFirstLoad,
setIsBreadCrumbsLoading,
searchValue,
isRoomsOnly,
onSetBaseFolderPath,
}: useRoomsHelperProps) => {
const getRoomList = React.useCallback(
async (startIndex: number, isInit?: boolean, search?: string | null) => {
async (
startIndex: number,
isInit?: boolean,
search?: string | null,
isErrorPath?: boolean
) => {
setIsNextPageLoading(true);
const filterValue = search
@ -103,10 +110,12 @@ const useRoomsHelper = ({
const { title, id } = current;
if (isInit) {
const breadCrumbs: BreadCrumb[] = [
{ ...defaultBreadCrumb },
{ label: title, id, isRoom: true },
];
const breadCrumbs: BreadCrumb[] = [{ label: title, id, isRoom: true }];
!isRoomsOnly && breadCrumbs.unshift({ ...defaultBreadCrumb });
onSetBaseFolderPath &&
onSetBaseFolderPath(isErrorPath ? [] : breadCrumbs);
setBreadCrumbs(breadCrumbs);

View File

@ -33,11 +33,12 @@ import useSocketHelper from "./helpers/useSocketHelper";
const FilesSelector = ({
isPanelVisible = false,
withoutBasicSelection = false,
withoutImmediatelyClose = false,
// withoutImmediatelyClose = false,
isThirdParty = false,
isRoomsOnly = false,
isEditorDialog = false,
rootThirdPartyId,
filterParam,
onClose,
@ -70,7 +71,7 @@ const FilesSelector = ({
onSelectFolder,
onSetBaseFolderPath,
onSetNewFolderPath,
//onSetNewFolderPath,
onSelectTreeNode,
onSave,
onSelectFile,
@ -159,6 +160,8 @@ const FilesSelector = ({
isFirstLoad,
setIsRoot,
searchValue,
isRoomsOnly,
onSetBaseFolderPath,
});
const { getFileList } = useFilesHelper({
@ -179,6 +182,11 @@ const FilesSelector = ({
setSelectedTreeNode,
filterParam,
getRootData,
onSetBaseFolderPath,
isRoomsOnly,
rootThirdPartyId,
getRoomList,
t,
});
const onSelectAction = (item: Item) => {
@ -218,26 +226,36 @@ const FilesSelector = ({
}, [selectedItemId, isRoot]);
React.useEffect(() => {
if (!withoutBasicSelection) {
onSelectFolder && onSelectFolder(currentFolderId);
onSetBaseFolderPath && onSetBaseFolderPath(currentFolderId);
const getRoomSettings = () => {
setSelectedItemType("rooms");
getRoomList(0, true);
};
const needRoomList = isRoomsOnly && !currentFolderId;
if (needRoomList) {
getRoomSettings();
return;
}
if (!currentFolderId) {
getRootData();
} else {
setSelectedItemId(currentFolderId);
if (
parentId === 0 &&
rootFolderType === FolderType.Rooms &&
!isThirdParty
) {
setSelectedItemType("rooms");
getRoomList(0, true);
} else {
setSelectedItemType("files");
getFileList(0, currentFolderId, true);
}
return;
}
setSelectedItemId(currentFolderId);
if (
needRoomList ||
(!isThirdParty && parentId === 0 && rootFolderType === FolderType.Rooms)
) {
getRoomSettings();
return;
}
setSelectedItemType("files");
getFileList(0, currentFolderId, true);
}, []);
const onClickBreadCrumb = (item: BreadCrumb) => {
@ -276,15 +294,17 @@ const FilesSelector = ({
const onCloseAction = () => {
if (onClose) {
onClose();
return;
}
if (isCopy) {
setCopyPanelVisible(false);
setIsFolderActions(false);
} else if (isRestoreAll) {
setRestoreAllPanelVisible(false);
} else {
if (isCopy) {
setCopyPanelVisible(false);
setIsFolderActions(false);
} else if (isRestoreAll) {
setRestoreAllPanelVisible(false);
} else {
setMoveToPanelVisible(false);
}
setMoveToPanelVisible(false);
}
};
@ -389,30 +409,16 @@ const FilesSelector = ({
toastr.error(t("Common:ErrorEmptyList"));
}
} else {
setIsRequestRunning(true);
onSetNewFolderPath && onSetNewFolderPath(selectedItemId);
onSelectFolder && onSelectFolder(selectedItemId);
//setIsRequestRunning(true);
//onSetNewFolderPath && onSetNewFolderPath(breadCrumbs);
onSelectFolder && onSelectFolder(selectedItemId, breadCrumbs);
onSave &&
selectedItemId &&
onSave(null, selectedItemId, fileName, isChecked);
onSelectTreeNode && onSelectTreeNode(selectedTreeNode);
const info: {
id: string | number;
title: string;
path?: string[];
} = {
id: selectedFileInfo?.id || "",
title: selectedFileInfo?.title || "",
path: [],
};
breadCrumbs.forEach((item, index) => {
if (index !== 0 && info.path) info.path.push(item.label);
});
onSelectFile && selectedFileInfo && onSelectFile(info);
!withoutImmediatelyClose && onCloseAction();
onSelectFile && onSelectFile(selectedFileInfo, breadCrumbs);
onCloseAction();
//!withoutImmediatelyClose && onCloseAction();
}
};
@ -544,7 +550,7 @@ export default inject(
dialogsStore,
filesStore,
}: any,
{ isCopy, isRestoreAll, isMove, isPanelVisible, id, passedFoldersTree }: any
{ isCopy, isRestoreAll, isMove, isPanelVisible, id }: any
) => {
const {
id: selectedId,
@ -561,8 +567,6 @@ export default inject(
const fromFolderId = id
? id
: passedFoldersTree?.length > 0
? passedFoldersTree[0].id
: rootFolderType === FolderType.Archive ||
rootFolderType === FolderType.TRASH
? undefined

View File

@ -0,0 +1,8 @@
import styled, { css } from "styled-components";
const StyledBodyWrapper = styled.div`
margin: 16px 0;
max-width: ${(props) => (props.maxWidth ? props.maxWidth : "350px")};
`;
export { StyledBodyWrapper };

View File

@ -0,0 +1,119 @@
import { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import FileInput from "@docspace/components/file-input";
import FilesSelector from "../FilesSelector";
import { StyledBodyWrapper } from "./StyledComponents";
const FilesSelectorInput = (props) => {
const {
id,
isThirdParty,
isRoomsOnly,
setNewPath,
newPath,
onSelectFolder: setSelectedFolder,
onSelectFile: setSelectedFile,
setBasePath,
basePath,
isDisabled,
isError,
toDefault,
maxWidth,
withoutInitPath,
rootThirdPartyId,
isErrorPath,
filterParam,
descriptionText,
} = props;
const isFilesSelection = !!filterParam;
const [isPanelVisible, setIsPanelVisible] = useState(false);
const [isLoading, setIsLoading] = useState(!!id && !withoutInitPath);
useEffect(() => {
return () => toDefault();
}, []);
const onClick = () => {
setIsPanelVisible(true);
};
const onClose = () => {
setIsPanelVisible(false);
};
const onSetBasePath = (folders) => {
console.log("onSetBasePath", withoutInitPath);
!withoutInitPath && setBasePath(folders);
isLoading && setIsLoading(false);
};
const onSelectFolder = (folderId, folders) => {
console.log("onSelectFolder", folderId, folders);
setSelectedFolder && setSelectedFolder(folderId);
folders && setNewPath(folders);
};
const onSelectFile = (fileInfo, folders) => {
console.log("onSelectFile", fileInfo, folders);
setSelectedFile && setSelectedFile(fileInfo);
folders && setNewPath(folders, fileInfo?.title);
};
const filesSelectionProps = {
onSelectFile: onSelectFile,
filterParam: filterParam,
};
const foldersSelectionProps = {
onSelectFolder: onSelectFolder,
onSetBaseFolderPath: onSetBasePath,
};
return (
<StyledBodyWrapper maxWidth={maxWidth}>
<FileInput
onClick={onClick}
fromStorage
path={newPath || basePath}
isLoading={isLoading}
isDisabled={isDisabled || isLoading}
hasError={isError || isErrorPath}
scale
/>
<FilesSelector
descriptionText={descriptionText}
filterParam={filterParam}
rootThirdPartyId={rootThirdPartyId}
isThirdParty={isThirdParty}
isRoomsOnly={isRoomsOnly}
id={id}
onClose={onClose}
isPanelVisible={isPanelVisible}
{...(isFilesSelection ? filesSelectionProps : foldersSelectionProps)}
/>
</StyledBodyWrapper>
);
};
export default inject(({ filesSelectorInput }) => {
const { basePath, newPath, setNewPath, setBasePath, toDefault, isErrorPath } =
filesSelectorInput;
return {
isErrorPath,
setBasePath,
basePath,
newPath,
setNewPath,
toDefault,
};
})(observer(FilesSelectorInput));

View File

@ -1,83 +0,0 @@
import styled, { css } from "styled-components";
import Base from "@docspace/components/themes/base";
const paddingRightStyle = props =>
props.theme.fileInput.paddingRight[props.size];
const widthIconStyle = props => props.theme.fileInput.icon.width[props.size];
const heightIconStyle = props => props.theme.fileInput.icon.height[props.size];
const StyledFileInput = styled.div`
display: flex;
position: relative;
outline: none;
.file-text-input {
width: 100%;
margin: 0;
}
width: ${props =>
(props.scale && "100%") ||
(props.size === "base" && props.theme.input.width.base) ||
(props.size === "middle" && props.theme.input.width.middle) ||
(props.size === "big" && props.theme.input.width.big) ||
(props.size === "huge" && props.theme.input.width.huge) ||
(props.size === "large" && props.theme.input.width.large)};
:hover {
.icon {
border-color: ${props =>
(props.hasError && props.theme.input.hoverErrorBorderColor) ||
(props.hasWarning && props.theme.input.hoverWarningBorderColor) ||
(props.isDisabled && props.theme.input.hoverDisabledBorderColor) ||
props.theme.input.hoverBorderColor};
}
}
:active {
.icon {
border-color: ${props =>
(props.hasError && props.theme.input.focusErrorBorderColor) ||
(props.hasWarning && props.theme.input.focusWarningBorderColor) ||
(props.isDisabled && props.theme.input.focusDisabledBorderColor) ||
props.theme.input.focusBorderColor};
}
}
.icon {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
${props =>
props.theme.interfaceDirection === "rtl"
? css`
left: 0;
`
: css`
right: 0;
`}
background: ${props => props.theme.fileInput.icon.background};
width: ${props => widthIconStyle(props)};
height: ${props => heightIconStyle(props)};
margin: 0;
border: ${props => props.theme.fileInput.icon.border};
border-radius: ${props => props.theme.fileInput.icon.borderRadius};
border-color: ${props =>
(props.hasError && props.theme.input.errorBorderColor) ||
(props.hasWarning && props.theme.input.warningBorderColor) ||
(props.isDisabled && props.theme.input.disabledBorderColor) ||
props.theme.input.borderColor};
cursor: ${props => (props.isDisabled ? "default" : "pointer")};
}
.icon-button {
cursor: ${props => (props.isDisabled ? "default" : "pointer")};
}
`;
StyledFileInput.defaultProps = { theme: Base };
export default StyledFileInput;

View File

@ -1,112 +0,0 @@
import CatalogFolderReactSvgUrl from "PUBLIC_DIR/images/catalog.folder.react.svg?url";
import React from "react";
import PropTypes from "prop-types";
import IconButton from "@docspace/components/icon-button";
import TextInput from "@docspace/components/text-input";
import StyledFileInput from "./StyledSimpleFileInput";
let iconSize;
const SimpleFileInput = ({
size,
placeholder,
isDisabled,
scale,
isError,
hasWarning,
id,
onClickInput,
name,
className,
textField,
...rest
}) => {
switch (size) {
case "base":
iconSize = 15;
break;
case "middle":
iconSize = 15;
break;
case "big":
iconSize = 16;
break;
case "huge":
iconSize = 16;
break;
case "large":
iconSize = 16;
break;
}
return (
<StyledFileInput
size={size}
scale={scale ? 1 : 0}
hasError={isError}
hasWarning={hasWarning}
isDisabled={isDisabled}
className={className}
{...rest}
>
<TextInput
id={id}
className="file-text-input"
placeholder={placeholder}
value={textField}
size={size}
isDisabled={isDisabled}
hasError={isError}
hasWarning={hasWarning}
scale={scale}
onClick={onClickInput}
isReadOnly
name={name}
/>
<div className="icon" onClick={!isDisabled ? onClickInput : null}>
<IconButton
className="icon-button"
iconName={CatalogFolderReactSvgUrl}
// color={"#A3A9AE"}
isDisabled={isDisabled}
size={iconSize}
/>
</div>
</StyledFileInput>
);
};
SimpleFileInput.propTypes = {
/** Accepts css style */
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
/** Placeholder text for the input */
placeholder: PropTypes.string,
/** Supported size of the input fields */
size: PropTypes.oneOf(["base", "middle", "big", "huge", "large"]),
/** Indicates the input field has scale */
scale: PropTypes.bool,
/** Accepts class */
className: PropTypes.string,
/** Indicates the input field has an error */
hasError: PropTypes.bool,
/** Indicates the input field has a warning */
hasWarning: PropTypes.bool,
/** Used as HTML `id` property */
id: PropTypes.string,
/** Indicates that the field cannot be used (e.g not authorised, or changes not saved) */
isDisabled: PropTypes.bool,
/** Used as HTML `name` property */
name: PropTypes.string,
fontSizeInput: PropTypes.string,
};
SimpleFileInput.defaultProps = {
size: "base",
scale: false,
hasWarning: false,
hasError: false,
isDisabled: false,
baseFolder: "",
};
export default SimpleFileInput;

View File

@ -19,11 +19,6 @@ const LinkBlock = (props) => {
setLinkNameValue(e.target.value);
};
const onShortenClick = () => {
alert("api in progress");
// setLinkValue
};
return (
<div className="edit-link_link-block">
<Text className="edit-link-text" fontSize="13px" fontWeight={600}>
@ -55,17 +50,6 @@ const LinkBlock = (props) => {
value={linkValue}
placeholder={t("ExternalLink")}
/>
<Link
fontSize="13px"
fontWeight={600}
isHovered
type="action"
isDisabled={isLoading}
onClick={onShortenClick}
>
{t("Shorten")}
</Link>
</div>
);
};

View File

@ -5,7 +5,7 @@ import Scrollbar from "@docspace/components/scrollbar";
const StyledEditLinkPanel = styled.div`
.edit-link-panel {
.scroll-body {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-left: 0 !important;
@ -33,7 +33,7 @@ const StyledEditLinkPanel = styled.div`
.edit-link_required-icon {
display: inline-flex;
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 2px;
@ -50,16 +50,16 @@ const StyledEditLinkPanel = styled.div`
}
.edit-link-toggle-block {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0 0 20px 16px;
padding: 0 20px 16px;
`
: css`
padding: 0 16px 20px 0;
padding: 0 16px 20px;
`}
border-top: ${props => props.theme.filesPanels.sharing.borderBottom};
border-top: ${(props) => props.theme.filesPanels.sharing.borderBottom};
.edit-link-toggle-header {
display: flex;
@ -67,7 +67,7 @@ const StyledEditLinkPanel = styled.div`
padding-bottom: 8px;
.edit-link-toggle {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
@ -105,7 +105,7 @@ const StyledEditLinkPanel = styled.div`
}
.edit-link_generate-icon {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin: 16px 8px 0px 0px;
@ -125,7 +125,7 @@ const StyledEditLinkPanel = styled.div`
.edit-link_header {
padding: 0 16px;
border-bottom: ${props => props.theme.filesPanels.sharing.borderBottom};
border-bottom: ${(props) => props.theme.filesPanels.sharing.borderBottom};
.edit-link_heading {
font-weight: 700;
@ -158,8 +158,8 @@ const StyledButtons = styled(Box)`
position: absolute;
bottom: 0px;
width: 100%;
background: ${props => props.theme.filesPanels.sharing.backgroundButtons};
border-top: ${props => props.theme.filesPanels.sharing.borderTop};
background: ${(props) => props.theme.filesPanels.sharing.backgroundButtons};
border-top: ${(props) => props.theme.filesPanels.sharing.borderTop};
`;
export { StyledEditLinkPanel, StyledScrollbar, StyledButtons };

View File

@ -1,27 +0,0 @@
import React from "react";
import { Provider as MobxProvider } from "mobx-react";
import { I18nextProvider } from "react-i18next";
import store from "client/store";
import SelectFileInput from "./index";
import i18n from "./i18n";
const { auth: authStore } = store;
const SelectFileModalWrapper = (props) => <SelectFileInput {...props} />;
class SelectFileInputWrapper extends React.Component {
componentDidMount() {
authStore.init(true);
}
render() {
return (
<MobxProvider {...store}>
<I18nextProvider i18n={i18n}>
<SelectFileModalWrapper {...this.props} />
</I18nextProvider>
</MobxProvider>
);
}
}
export default SelectFileInputWrapper;

View File

@ -1,10 +0,0 @@
import styled from "styled-components";
const StyledComponent = styled.div`
.select-file_file-input {
margin-bottom: 16px;
width: 100%;
max-width: ${(props) => (props.maxWidth ? props.maxWidth : "350px")};
}
`;
export default StyledComponent;

View File

@ -1,39 +0,0 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "@docspace/common/utils/i18next-http-backend";
import { LANGUAGE } from "@docspace/common/constants";
import config from "PACKAGE_FILE";
import { getCookie } from "@docspace/common/utils";
import { loadLanguagePath } from "SRC_DIR/helpers/utils";
const newInstance = i18n.createInstance();
newInstance
.use(Backend)
.use(initReactI18next)
.init({
lng: getCookie(LANGUAGE) || "en",
fallbackLng: "en",
load: "currentOnly",
//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: loadLanguagePath(config.homepage),
},
ns: ["SelectFile"],
defaultNS: "SelectFile",
react: {
useSuspense: false,
},
});
export default newInstance;

View File

@ -1,93 +0,0 @@
import React from "react";
import { inject, observer } from "mobx-react";
import PropTypes from "prop-types";
import StyledComponent from "./StyledSelectFileInput";
import SimpleFileInput from "../../SimpleFileInput";
import FilesSelector from "SRC_DIR/components/FilesSelector";
class SelectFileInput extends React.PureComponent {
componentDidMount() {
this.props.setFirstLoad(false);
}
componentWillUnmount() {
const { setExpandedPanelKeys, setFolderId, setFile, onSelectFile } =
this.props;
setExpandedPanelKeys(null);
setFolderId(null);
setFile({});
onSelectFile && onSelectFile({});
}
render() {
const {
onClickInput,
hasError,
t,
placeholder,
maxInputWidth,
maxFolderInputWidth,
isPanelVisible,
isDisabled,
isError,
fileName,
folderId,
...rest
} = this.props;
return (
<StyledComponent maxInputWidth={maxInputWidth}>
<SimpleFileInput
className="select-file_file-input"
textField={fileName}
isDisabled={isDisabled}
isError={isError}
onClickInput={onClickInput}
/>
{isPanelVisible && (
<FilesSelector
{...rest}
id={folderId}
isPanelVisible={isPanelVisible}
onSetFileNameAndLocation={this.onSetFileNameAndLocation}
maxInputWidth={maxFolderInputWidth}
/>
)}
</StyledComponent>
);
}
}
SelectFileInput.propTypes = {
onClickInput: PropTypes.func.isRequired,
hasError: PropTypes.bool,
placeholder: PropTypes.string,
};
SelectFileInput.defaultProps = {
hasError: false,
placeholder: "",
};
export default inject(
(
{ clientLoadingStore, treeFoldersStore, selectFileDialogStore },
{ fileName: fileNameProps }
) => {
const { setFirstLoad } = clientLoadingStore;
const { folderId, setFolderId, setFile, fileInfo } = selectFileDialogStore;
const fileName = fileInfo?.title || fileNameProps;
const { setExpandedPanelKeys } = treeFoldersStore;
return {
setFirstLoad,
setFolderId,
setFile,
fileInfo,
folderId,
fileName,
setExpandedPanelKeys,
};
}
)(observer(SelectFileInput));

View File

@ -1,27 +0,0 @@
import React from "react";
import { Provider as MobxProvider } from "mobx-react";
import { I18nextProvider } from "react-i18next";
import store from "client/store";
import SelectFolderInput from "./index";
import i18n from "./i18n";
const { auth: authStore } = store;
const SelectFolderModalWrapper = (props) => <SelectFolderInput {...props} />;
class SelectFolderInputWrapper extends React.Component {
componentDidMount() {
authStore.init(true);
}
render() {
return (
<MobxProvider {...store}>
<I18nextProvider i18n={i18n}>
<SelectFolderModalWrapper {...this.props} />
</I18nextProvider>
</MobxProvider>
);
}
}
export default SelectFolderInputWrapper;

View File

@ -1,44 +0,0 @@
import styled, { css } from "styled-components";
const StyledComponent = styled.div`
.select-folder_file-input {
margin-bottom: 16px;
margin-top: 3px;
width: 100%;
max-width: ${props => (props.maxWidth ? props.maxWidth : "350px")};
:hover {
.icon {
svg {
path {
fill: ${props => props.theme.iconButton.hoverColor};
}
}
}
}
}
.panel-loader-wrapper {
margin-top: 8px;
${props =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 32px;
`
: css`
padding-left: 32px;
`}
}
.panel-loader {
display: inline;
${props =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 10px;
`
: css`
margin-right: 10px;
`}
}
`;
export default StyledComponent;

View File

@ -1,39 +0,0 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "@docspace/common/utils/i18next-http-backend";
import { LANGUAGE } from "@docspace/common/constants";
import config from "PACKAGE_FILE";
import { getCookie } from "@docspace/common/utils";
import { loadLanguagePath } from "SRC_DIR/helpers/utils";
const newInstance = i18n.createInstance();
newInstance
.use(Backend)
.use(initReactI18next)
.init({
lng: getCookie(LANGUAGE) || "en",
fallbackLng: "en",
load: "currentOnly",
//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: loadLanguagePath(config.homepage),
},
ns: ["SelectFolder"],
defaultNS: "SelectFolder",
react: {
useSuspense: false,
},
});
export default newInstance;

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