Merge branch 'develop' into feature/info-panel-update and solved merge conflict

This commit is contained in:
mushka 2022-11-03 14:52:11 +03:00
commit bb16de4ed9
144 changed files with 4868 additions and 10925 deletions

View File

@ -38,9 +38,14 @@ echo "SERVICE_DOCEDITOR: $doceditor"
echo "SERVICE_LOGIN: $login"
echo "SERVICE_CLIENT: $client"
echo "Stop all backend services"
# Stop all backend services"
$dir/build/start/stop.backend.docker.sh
echo "Remove all backend containers"
docker rm -f $(docker ps -a | egrep "onlyoffice" | egrep -v "mysql|rabbitmq|redis|elasticsearch|documentserver" | awk 'NR>0 {print $1}')
echo "Remove all backend images"
docker rmi -f $(docker images -a | egrep "onlyoffice" | egrep -v "mysql|rabbitmq|redis|elasticsearch|documentserver" | awk 'NR>0 {print $3}')
echo "Remove all docker images except 'mysql, rabbitmq, redis, elasticsearch, documentserver'"
docker image rm -f $(docker images -a | egrep "onlyoffice" | egrep -v "mysql|rabbitmq|redis|elasticsearch|documentserver" | awk 'NR>0 {print $3}')
@ -68,7 +73,9 @@ if [ "$1" = "--no_ds" ]; then
echo "SKIP Document server"
else
echo "Run Document server"
docker compose -f ds.yml up -d
DOCUMENT_SERVER_IMAGE_NAME=onlyoffice/documentserver-de:latest \
ROOT_DIR=$dir \
docker compose -f ds.dev.yml up -d
fi
echo "Build all backend services"
@ -86,5 +93,5 @@ echo "Run DB migration"
DOCKERFILE=$docker_file \
docker compose -f migration-runner.yml up -d
echo "Start all backend services"
# Start all backend services"
$dir/build/start/start.backend.docker.sh

12
build/clear.backend.docker.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
echo "Stop all onlyoffice containers."
docker stop $(docker ps -a | egrep "onlyoffice" | awk 'NR>0 {print $1}')
echo "Remove all onlyoffice containers."
docker rm -f $(docker ps -a | egrep "onlyoffice" | awk 'NR>0 {print $1}')
echo "Remove all onlyoffice images."
docker rmi -f $(docker images -a | egrep "onlyoffice" | awk 'NR>0 {print $3}')
echo "Remove unused volumes."
docker volume prune -f
echo "Remove unused networks."
docker network prune -f

View File

@ -34,6 +34,7 @@ RUN apt-get -y update && \
apt-get install -y nodejs && \
rm -rf /var/lib/apt/lists/*
ADD https://api.github.com/repos/ONLYOFFICE/DocSpace/git/refs/heads/${GIT_BRANCH} version.json
RUN echo ${GIT_BRANCH} && \
git clone --recurse-submodules -b ${GIT_BRANCH} https://github.com/ONLYOFFICE/DocSpace.git ${SRC_PATH}
@ -196,7 +197,7 @@ WORKDIR ${BUILD_PATH}/services/ASC.ApiSystem/
COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py
COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.ApiSystem/service/ .
CMD [" ASC.ApiSystem.dll", " ASC.ApiSystem"]
CMD ["ASC.ApiSystem.dll", "ASC.ApiSystem"]
## ASC.ClearEvents ##
FROM dotnetrun AS clear-events

View File

@ -0,0 +1,23 @@
version: "3.6"
services:
onlyoffice-document-server:
image: "${DOCUMENT_SERVER_IMAGE_NAME}"
container_name: ${DOCUMENT_SERVER_HOST}
# Strings below enable the JSON Web Token validation.
environment:
- JWT_ENABLED=true
- JWT_SECRET=${DOCUMENT_SERVER_JWT_SECRET}
- JWT_HEADER=${DOCUMENT_SERVER_JWT_HEADER}
- JWT_IN_BODY=true
expose:
- "80"
volumes:
- ${ROOT_DIR}/Data:/var/www/onlyoffice/Data
stdin_open: true
restart: always
stop_grace_period: 60s
networks:
default:
external:
name: ${NETWORK_NAME}

View File

@ -1,4 +1,4 @@
@echo off
PUSHD %~dp0..\..
set servicepath=%cd%\web\ASC.Web.HealthChecks.UI\bin\Debug\ASC.Web.HealthChecks.UI.exe urls=http://0.0.0.0:5027
set servicepath=%cd%\web\ASC.Web.HealthChecks.UI\bin\Debug\ASC.Web.HealthChecks.UI.exe urls=http://0.0.0.0:5033

View File

@ -39,7 +39,7 @@ docker_file=Dockerfile.dev
env_extension="dev"
core_base_domain="localhost"
echo "Start all backend services"
echo "Start all backend services (containers)"
DOCKERFILE=$docker_file \
ROOT_DIR=$dir \
RELEASE_DATE=$build_date \

View File

@ -1,23 +1,4 @@
#!/bin/bash
#rd="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
#echo "Run script directory:" $dir
#dir=$(builtin cd $rd/../../; pwd)
#echo "Root directory:" $dir
#cd $dir/build/install/docker/
#docker_dir="$( pwd )"
#echo "Docker directory:" $docker_dir
echo "Stop all backend containers"
# DOCKERFILE=Dockerfile.dev \
# docker compose -f docspace.dev.yml down
docker stop $(docker ps -a | egrep "onlyoffice" | egrep -v "mysql|rabbitmq|redis|elasticsearch|documentserver" | awk 'NR>0 {print $1}')
echo "Remove all backend containers"
docker rm -f $(docker ps -a | egrep "onlyoffice" | egrep -v "mysql|rabbitmq|redis|elasticsearch|documentserver" | awk 'NR>0 {print $1}')
echo "Remove all backend images"
docker rmi -f $(docker images -a | egrep "onlyoffice" | egrep -v "mysql|rabbitmq|redis|elasticsearch|documentserver" | awk 'NR>0 {print $3}')
echo "Stop all backend services (containers)"
docker stop $(docker ps -a | egrep "onlyoffice" | egrep -v "mysql|rabbitmq|redis|elasticsearch|documentserver" | awk 'NR>0 {print $1}')

View File

@ -16,6 +16,8 @@
<PackageReference Include="AspNetCore.HealthChecks.Kafka" Version="6.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.MySql" Version="6.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="6.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="6.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="6.0.4" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="6.0.5" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.7" />

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using CsvHelper;
namespace ASC.Api.Core.Core;
public static class CustomHealthCheck
@ -64,6 +66,7 @@ public static class CustomHealthCheck
}
var elasticSettings = configuration.GetSection("elastic");
if (elasticSettings != null && elasticSettings.GetChildren().Any())
{
var host = elasticSettings.GetSection("Host").Value ?? "localhost";
@ -77,7 +80,25 @@ public static class CustomHealthCheck
name: "elasticsearch",
tags: new string[] { "elasticsearch" });
}
}
}
var redisConfiguration = configuration.GetSection("Redis").Get<RedisConfiguration>();
if (redisConfiguration != null)
{
hcBuilder.AddRedis(redisConfiguration.ConfigurationOptions.ToString(),
name: "redis",
tags: new string[] { "redis" });
}
var rabbitMQConfiguration = configuration.GetSection("RabbitMQ").Get<RabbitMQSettings>();
if (rabbitMQConfiguration != null)
{
hcBuilder.AddRabbitMQ(x => rabbitMQConfiguration.GetConnectionFactory(),
name: "rabbitMQ",
tags: new string[] { "rabbitMQ" });
}
return services;
}

View File

@ -84,39 +84,8 @@ public static class ServiceCollectionExtension
var cfg = sp.GetRequiredService<IConfiguration>();
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
DispatchConsumersAsync = true
};
if (!string.IsNullOrEmpty(rabbitMQConfiguration.Uri))
{
factory.Uri = new Uri(rabbitMQConfiguration.Uri);
}
else
{
factory.HostName = rabbitMQConfiguration.HostName;
factory.UserName = rabbitMQConfiguration.UserName;
factory.Password = rabbitMQConfiguration.Password;
factory.Port = rabbitMQConfiguration.Port;
factory.VirtualHost = rabbitMQConfiguration.VirtualHost;
}
if (rabbitMQConfiguration.EnableSsl)
{
factory.Ssl = new SslOption
{
Enabled = rabbitMQConfiguration.EnableSsl,
Version = SslProtocols.Tls12
};
if (!string.IsNullOrEmpty(rabbitMQConfiguration.SslCertPath))
{
factory.Ssl.CertPath = rabbitMQConfiguration.SslCertPath;
factory.Ssl.ServerName = rabbitMQConfiguration.SslServerName;
}
}
var connectionFactory = rabbitMQConfiguration.GetConnectionFactory();
var retryCount = 5;
@ -125,7 +94,7 @@ public static class ServiceCollectionExtension
retryCount = int.Parse(cfg["core:eventBus:connectRetryCount"]);
}
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
return new DefaultRabbitMQPersistentConnection(connectionFactory, logger, retryCount);
});
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>

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.Api.Core.Model;
public class TaskProgressDto
{
public bool IsCompleted { get; set; }
public int Progress { get; set; }
public static TaskProgressDto GetSample()
{
return new TaskProgressDto
{
IsCompleted = true,
Progress = 0
};
}
}

View File

@ -54,36 +54,8 @@ public class RabbitMQCache<T> : IDisposable, ICacheNotify<T> where T : IMessage<
_actions = new ConcurrentDictionary<string, List<Action<T>>>();
var rabbitMQConfiguration = configuration.GetSection("rabbitmq").Get<RabbitMQSettings>();
_factory = new ConnectionFactory();
if (!string.IsNullOrEmpty(rabbitMQConfiguration.Uri))
{
_factory.Uri = new Uri(rabbitMQConfiguration.Uri);
}
else
{
_factory.HostName = rabbitMQConfiguration.HostName;
_factory.UserName = rabbitMQConfiguration.UserName;
_factory.Password = rabbitMQConfiguration.Password;
_factory.Port = rabbitMQConfiguration.Port;
_factory.VirtualHost = rabbitMQConfiguration.VirtualHost;
}
if (rabbitMQConfiguration.EnableSsl)
{
_factory.Ssl = new SslOption
{
Enabled = rabbitMQConfiguration.EnableSsl,
Version = SslProtocols.Tls12
};
if (!string.IsNullOrEmpty(rabbitMQConfiguration.SslCertPath))
{
_factory.Ssl.CertPath = rabbitMQConfiguration.SslCertPath;
_factory.Ssl.ServerName = rabbitMQConfiguration.SslServerName;
}
}
_factory = rabbitMQConfiguration.GetConnectionFactory();
_connection = _factory.CreateConnection();
_consumerChannel = CreateConsumerChannel();
@ -139,8 +111,7 @@ public class RabbitMQCache<T> : IDisposable, ICacheNotify<T> where T : IMessage<
_logger.ErrorStartBasicConsumeCanNotCall();
}
}
private void TryConnect()
{
lock (_lock)

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System.Security.Authentication;
namespace ASC.Common.Caching;
public class RabbitMQSettings
{
@ -36,4 +38,42 @@ public class RabbitMQSettings
public bool EnableSsl { get; set; }
public string SslServerName { get; set; }
public string SslCertPath { get; set; }
public ConnectionFactory GetConnectionFactory()
{
var factory = new ConnectionFactory()
{
DispatchConsumersAsync = true
};
if (!string.IsNullOrEmpty(Uri))
{
factory.Uri = new Uri(Uri);
}
else
{
factory.HostName = HostName;
factory.UserName = UserName;
factory.Password = Password;
factory.Port = Port;
factory.VirtualHost = VirtualHost;
}
if (EnableSsl)
{
factory.Ssl = new SslOption
{
Enabled = EnableSsl,
Version = SslProtocols.Tls12
};
if (!string.IsNullOrEmpty(SslCertPath))
{
factory.Ssl.CertPath = SslCertPath;
factory.Ssl.ServerName = SslServerName;
}
}
return factory;
}
}

View File

@ -600,7 +600,7 @@ public class UserManager
return GetUsers(employeeStatus).Where(u => IsUserInGroupInternal(u.Id, groupId, refs)).ToArray();
}
public void AddUserIntoGroup(Guid userId, Guid groupId)
public void AddUserIntoGroup(Guid userId, Guid groupId, bool dontClearAddressBook = false)
{
if (Constants.LostUser.Id == userId || Constants.LostGroupInfo.ID == groupId)
{
@ -618,7 +618,11 @@ public class UserManager
var myUri = (_accessor?.HttpContext != null) ? _accessor.HttpContext.Request.GetUrlRewriter().ToString() :
(_cache.Get<string>("REWRITE_URL" + tenant.Id) != null) ?
new Uri(_cache.Get<string>("REWRITE_URL" + tenant.Id)).ToString() : tenant.GetTenantDomain(_coreSettings);
_cardDavAddressbook.Delete(myUri, user.Id, user.Email, tenant.Id).Wait(); //todo
if (!dontClearAddressBook)
{
_cardDavAddressbook.Delete(myUri, user.Id, user.Email, tenant.Id).Wait(); //todo
}
}
}

View File

@ -101,12 +101,8 @@ class DbQuotaService : IQuotaService
using var coreDbContext = _dbContextFactory.CreateDbContext();
using var tx = coreDbContext.Database.BeginTransaction();
AddQuota(coreDbContext, Guid.Empty);
if (row.UserId != Guid.Empty)
{
AddQuota(coreDbContext, row.UserId);
}
AddQuota(coreDbContext, row.UserId);
tx.Commit();
});

View File

@ -76,6 +76,7 @@ public class TenantQuotaController : IQuotaController
CurrentSize += size;
}
SetTenantQuotaRow(module, domain, size, dataTag, true, Guid.Empty);
SetTenantQuotaRow(module, domain, size, dataTag, true, _authContext.CurrentAccount.ID);
}
@ -87,6 +88,7 @@ public class TenantQuotaController : IQuotaController
CurrentSize += size;
}
SetTenantQuotaRow(module, domain, size, dataTag, true, Guid.Empty);
SetTenantQuotaRow(module, domain, size, dataTag, true, _authContext.CurrentAccount.ID);
}

View File

@ -113,14 +113,14 @@ namespace ASC.Notify.Textile.Resources {
}
/// <summary>
/// Looks up a localized string similar to This email is generated automatically and you do not need to answer it.
///&lt;br /&gt;You receive this email because you are a registered user of &lt;a href=&quot;{0}&quot; style=&quot;color: #7b7b7b;&quot; target=&quot;_blank&quot;&gt;{0}&lt;/a&gt;
///&lt;br /&gt;Click here to unsubscribe from informational emails: &lt;a href=&quot;{1}&quot; style=&quot;color: #7b7b7b;&quot; target=&quot;_blank&quot;&gt;Unsubscribe&lt;/a&gt;
/// Looks up a localized string similar to For any purchase questions, email us at &lt;a href=&quot;mailto:sales@onlyoffice.com&quot; style=&quot;color: #7b7b7b;&quot; target=&quot;_blank&quot;&gt;sales@onlyoffice.com&lt;/a&gt;.
///&lt;br /&gt;In case of technical problems contact our &lt;a href=&quot;https://www.onlyoffice.com/support-contact-form.aspx&quot; style=&quot;color: #7b7b7b;&quot; target=&quot;_blank&quot;&gt;support team&lt;/a&gt;.
///&lt;br /&gt; &lt;a href=&quot;{0}&quot; style=&quot;color: #7b7b7b;&quot; target=&quot;_blank&quot;&gt;Click here to unsubscribe&lt;/a&gt;
///&lt;br /&gt;.
/// </summary>
public static string TextForFooterWithUnsubscribeLink {
public static string TextForFooterUnsubsribeDocSpace {
get {
return ResourceManager.GetString("TextForFooterWithUnsubscribeLink", resourceCulture);
return ResourceManager.GetString("TextForFooterUnsubsribeDocSpace", resourceCulture);
}
}
}

View File

@ -192,10 +192,4 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Bu e-poçt avtomatik olaraq yaradılır və ona cavab vermək lazım deyil.
&lt;br /&gt;Siz &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;-nin qeydiyyatdan keçmiş istifadəçisi olduğunuz üçün bu e-poçtu alırsınız.
&lt;br /&gt;Bu e-poçtların artıq sizə göndərilməsini istəmirsinizsə, aşağıdakı linkə klikləyin: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Abunəlikdən çıxın&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -158,10 +158,4 @@
&lt;/div&gt;
&lt;/body&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Този имейл се генерира автоматично и не е необходимо да отговаряте.
&lt;br/&gt;Получавате този имейл, защото сте регистриран потребител на &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br/&gt;Ако вече не искате да получавате тези имейли, кликнете върху следната връзка: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Отписване&lt;/a&gt;
&lt;br/&gt;</value>
</data>
</root>

View File

@ -128,10 +128,4 @@
&lt;/div&gt;
&lt;/body&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Tento e-mail je generován automaticky a nemusíte na něj odpovídat.
&lt;br /&gt;Tento e-mail obdržíte, protože jste registrovaným uživatelem &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Pokud již nechcete přijímat tyto e-maily, klikněte na následující odkaz: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Unsubscribe&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -192,10 +192,4 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Diese E-Mail ist automatisch generiert und erfordert keine Antwort.
&lt;br /&gt;Sie erhalten diese E-Mail, weil Sie ein registrierter Benutzer bzw. eine registrierte Benutzerin von &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt; sind.
&lt;br /&gt;Um keine E-Mails mehr zu erhalten, klicken Sie bitte hier: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Abmelden&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -192,10 +192,4 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Este correo electrónico se genera automáticamente y no es necesario responderlo.
&lt;br /&gt;Ha recibido este correo electrónico porque es un usuario registrado de &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Haga clic aquí para cancelar la suscripción a los correos electrónicos informativos: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Cancelar la suscripción&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -192,10 +192,4 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Cet e-mail est généré automatiquement et vous n'avez pas besoin de répondre.
&lt;br /&gt;Vous recevez ce courriel parce que vous êtes un utilisateur enregistré de &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Cliquez ici pour vous désabonner des e-mails d'information: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Se Désabonner&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -192,10 +192,4 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Questo messaggio è stato generato automaticamente e non necessità di risposta.
&lt;br /&gt;Hai ricevuto questo messaggio in quanto sei un untente registrato su &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Сlicca qui per annullare l'iscrizione alle email informative:&lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Rimuovi sottoscrizione&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -158,10 +158,4 @@
&lt;/div&gt;
&lt;/body&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Šis e-pasts ir izveidots automātiski, un jums nav uz to jāatbild.
&lt;br /&gt;Jūs saņēmāt šo e-pastu, jo esat reģistrēts lietotājs portālā &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Ja nevēlaties saņemt šos e-pastus, spiediet uz šīs saites: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Atsaukt abonēšanu&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -128,10 +128,4 @@
&lt;/div&gt;
&lt;/body&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Deze e-mail is automatisch gegenereerd en u hoeft deze niet te beantwoorden.
&lt;br /&gt;U ontvangt deze e-mail omdat u een geregistreerde gebruiker bent van &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Als u deze e-mails niet langer wilt ontvangen, klik dan op de volgende link: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Afmelden&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -128,10 +128,4 @@
&lt;/div&gt;
&lt;/body&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Wiadomość utworzona automatycznie i odpowiadać na nią nie trzeba.
&lt;br /&gt;otrzymujesz tę wiadomość, ponieważ jesteś zarejestrowanym użytkownikiem &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Jeśli nie chcesz otrzymywać tych wiadomości, kliknij na poniższy link: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Rezygnacja&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -192,10 +192,4 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Este e-mail é gerado automaticamente e você não precisa respondê-lo.
&lt;br /&gt;Você está recebendo este e-mail por que é um usuário registrado do &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Se você não quiser mais receber estes e-mails, clique no seguinte link: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Cancelar subscrição&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -192,10 +192,10 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>This email is generated automatically and you do not need to answer it.
&lt;br /&gt;You receive this email because you are a registered user of &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Click here to unsubscribe from informational emails: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Unsubscribe&lt;/a&gt;
<data name="TextForFooterUnsubsribeDocSpace" xml:space="preserve">
<value>For any purchase questions, email us at &lt;a href="mailto:sales@onlyoffice.com" style="color: #7b7b7b;" target="_blank"&gt;sales@onlyoffice.com&lt;/a&gt;.
&lt;br /&gt;In case of technical problems contact our &lt;a href="https://www.onlyoffice.com/support-contact-form.aspx" style="color: #7b7b7b;" target="_blank"&gt;support team&lt;/a&gt;.
&lt;br /&gt; &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;Click here to unsubscribe&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -192,10 +192,4 @@
&lt;/td&gt;
&lt;/tr&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Это сообщение создано автоматически, и отвечать на него не нужно.
&lt;br /&gt;Вы получили это сообщение, так как являетесь зарегистрированным пользователем &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Если вы хотите отписаться от информационных электронных писем, нажмите на следующую ссылку: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Отписаться&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -128,10 +128,4 @@
&lt;/div&gt;
&lt;/body&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" 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}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Ak si viac neprajete dostávať tieto e-maily, kliknite na nasledujúci link: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Unsubscribe&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -128,10 +128,4 @@
&lt;/div&gt;
&lt;/body&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>Bu e-posta otomatik olarak oluşturulmuştur, lütfen yanıtlamayın.
&lt;br /&gt;Bu e-postayı almanızın sebebi &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt; kayıtlı kullanıcısı olmanızdır.
&lt;br /&gt;Bu e-postaları almak istemiyorsanız lütfen linke tıklayın: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Abonelikten Ayrıl&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

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

View File

@ -128,10 +128,4 @@
&lt;/div&gt;
&lt;/body&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" 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ý &lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;
&lt;br /&gt;Nếu bạn không còn muốn nhận những email này, hãy nhấp vào liên kết sau: &lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;Hủy đăng ký&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -128,10 +128,4 @@
&lt;/div&gt;
&lt;/body&gt;</value>
</data>
<data name="TextForFooterWithUnsubscribeLink" xml:space="preserve">
<value>此电子邮件是自动生成的,您无需回复。
&lt;br/&gt;您收到此邮件,因为您是&lt;a href="{0}" style="color: #7b7b7b;" target="_blank"&gt;{0}&lt;/a&gt;的用户
&lt;br/&gt;点击这里,取消邮件订阅。&lt;a href="{1}" style="color: #7b7b7b;" target="_blank"&gt;取消订阅&lt;/a&gt;
&lt;br /&gt;</value>
</data>
</root>

View File

@ -263,14 +263,6 @@ public class TextileStyler : IPatternStyler
return string.Empty;
}
var rootPathArgument = message.GetArgument("__VirtualRootPath");
var rootPath = rootPathArgument == null ? string.Empty : (string)rootPathArgument.Value;
if (string.IsNullOrEmpty(rootPath))
{
return string.Empty;
}
var unsubscribeLink = _coreBaseSettings.CustomMode && _coreBaseSettings.Personal
? GetSiteUnsubscribeLink(message, settings)
: GetPortalUnsubscribeLink(message, settings);
@ -280,7 +272,7 @@ public class TextileStyler : IPatternStyler
return string.Empty;
}
return string.Format(NotifyTemplateResource.TextForFooterWithUnsubscribeLink, rootPath, unsubscribeLink);
return string.Format(NotifyTemplateResource.TextForFooterUnsubsribeDocSpace, unsubscribeLink);
}
private string GetPortalUnsubscribeLink(NoticeMessage message, MailWhiteLabelSettings settings)

View File

@ -1,9 +1,14 @@
const winston = require("winston");
const winston = require("winston"),
WinstonCloudWatch = require('winston-cloudwatch');
require("winston-daily-rotate-file");
const path = require("path");
const config = require("../config");
const fs = require("fs");
const os = require("os");
const { randomUUID } = require('crypto');
const date = require('date-and-time');
let logpath = config.get("logPath");
if(logpath != null)
@ -17,6 +22,14 @@ if(logpath != null)
const fileName = logpath ? path.join(logpath, "socket-io.%DATE%.log") : path.join(__dirname, "..", "..", "..", "Logs", "socket-io.%DATE%.log");
const dirName = path.dirname(fileName);
const aws = config.get("aws");
const accessKeyId = aws.accessKeyId;
const secretAccessKey = aws.secretAccessKey;
const awsRegion = aws.region;
const logGroupName = aws.logGroupName;
const logStreamName = aws.logStreamName;
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName);
}
@ -38,25 +51,67 @@ var options = {
json: false,
colorize: true,
},
cloudWatch: {
name: 'aws',
level: "debug",
logGroupName: () => {
const hostname = os.hostname();
return logGroupName.replace("${instance-id}", hostname);
},
logStreamName: () => {
const now = new Date();
const guid = randomUUID();
const dateAsString = date.format(now, 'YYYY/MM/DDTHH.mm.ss');
return logStreamName.replace("${guid}", guid)
.replace("${date}", dateAsString);
},
awsRegion: awsRegion,
jsonMessage: true,
awsOptions: {
credentials: {
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey
}
}
}
};
//const fileTransport = new winston.transports.DailyRotateFile(options.file);
const transports = [
var transports = [
new winston.transports.Console(options.console),
new winston.transports.DailyRotateFile(options.file),
new winston.transports.DailyRotateFile(options.file)
];
if (aws != null && aws.accessKeyId !== '')
{
transports.push(new WinstonCloudWatch(options.cloudWatch));
}
//winston.exceptions.handle(fileTransport);
const customFormat = winston.format(info => {
const now = new Date();
info.date = date.format(now, 'YYYY-MM-DD HH:mm:ss');
info.applicationContext = "SocketIO";
info.level = info.level.toUpperCase();
const hostname = os.hostname();
info["instance-id"] = hostname;
return info;
})();
module.exports = new winston.createLogger({
//defaultMeta: { component: "socket.io-server" },
format: winston.format.combine(
winston.format.timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
winston.format.json()
customFormat,
winston.format.json()
),
transports: transports,
exitOnError: false,
});
});

View File

@ -3,5 +3,12 @@
"port": 9899,
"appsettings": "../../../config",
"environment": "Development"
},
"aws":{
"accessKeyId": "",
"secretAccessKey": "",
"region": "",
"logGroupName": "/docspace/ASC.SocketIO/instance/${instance-id}/general",
"logStreamName": "${guid} - ${date}"
}
}

View File

@ -7,9 +7,11 @@
"start:dev": "nodemon server.js"
},
"dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.199.0",
"axios": "0.24.0",
"connect-redis": "~6.0.0",
"cookie-parser": "~1.4.6",
"date-and-time": "^2.4.1",
"express": "~4.17.2",
"express-session": "~1.17.2",
"express-socket.io-session": "~1.3.5",
@ -20,7 +22,8 @@
"nodemon": "^2.0.15",
"redis": "^3.1.2",
"socket.io": "^4.4.0",
"winston": "^3.3.3",
"winston": "^3.8.2",
"winston-cloudwatch": "^6.1.1",
"winston-daily-rotate-file": "^4.5.5"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -102,4 +102,6 @@ global using Microsoft.Extensions.Options;
global using Newtonsoft.Json.Linq;
global using NLog;
global using NLog;
global using HealthChecks.UI.Client;
global using Microsoft.AspNetCore.Diagnostics.HealthChecks;

View File

@ -25,6 +25,8 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using StackExchange.Redis.Extensions.Core.Configuration;
var options = new WebApplicationOptions
{
Args = args,
@ -45,6 +47,16 @@ var logger = LogManager.Setup()
})
.LoadConfiguration(builder.Configuration, builder.Environment)
.GetLogger(typeof(Startup).Namespace);
var path = builder.Configuration["pathToConf"];
logger.Debug("path: " + path);
logger.Debug("EnvironmentName: " + builder.Environment.EnvironmentName);
var redisConfiguration = builder.Configuration.GetSection("Redis").Get<RedisConfiguration>();
logger.Error($"redisConfiguration is null: {redisConfiguration == null}");
var kafkaConfiguration = builder.Configuration.GetSection("kafka").Get<KafkaSettings>();
logger.Debug($"kafkaConfiguration is null: {kafkaConfiguration == null}");
var rabbitMQConfiguration = builder.Configuration.GetSection("RabbitMQ").Get<RabbitMQSettings>();
logger.Debug($"rabbitMQConfiguration is null: {rabbitMQConfiguration == null}");
try
{

View File

@ -80,7 +80,6 @@ public class Startup
services.AddSingleton(jsonOptions);
_diHelper.AddControllers();
_diHelper.TryAdd<CultureMiddleware>();
_diHelper.TryAdd<IpSecurityFilter>();
_diHelper.TryAdd<PaymentFilter>();
_diHelper.TryAdd<ProductSecurityFilter>();
@ -122,11 +121,19 @@ public class Startup
app.UseAuthorization();
app.UseCultureMiddleware();
app.UseEndpoints(endpoints =>
{
endpoints.MapCustom();
endpoints.MapHealthChecks("/health", new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
{
Predicate = r => r.Name.Contains("self")
});
});
}
}

View File

@ -24,17 +24,24 @@
// 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 ASC.Api.Core.Core;
using ASC.Common.Logging;
namespace ASC.ClearEvents.Extensions;
public static class ServiceCollectionExtension
{
public static IServiceCollection AddClearEventsServices(this IServiceCollection services)
public static IServiceCollection AddClearEventsServices(this IServiceCollection services, IConfiguration configuration)
{
var diHelper = new DIHelper(services);
services.AddScoped<EFLoggerFactory>();
services.AddHostedService<ClearEventsService>();
diHelper.TryAdd<ClearEventsService>();
services.AddBaseDbContextPool<MessagesContext>();
services.AddCustomHealthCheck(configuration);
return services;
}

View File

@ -24,6 +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
using ASC.Api.Core.Core;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using NLog;
var options = new WebApplicationOptions
@ -50,7 +54,7 @@ try
{
logger.Info("Configuring web host ({applicationContext})...", Program.AppName);
builder.Host.ConfigureDefault();
builder.Services.AddClearEventsServices();
builder.Services.AddClearEventsServices(builder.Configuration);
builder.Host.ConfigureContainer<ContainerBuilder>((context, builder) =>
{
@ -59,6 +63,22 @@ try
var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health", new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
{
Predicate = r => r.Name.Contains("self")
});
});
logger.Info("Starting web host ({applicationContext})...", Program.AppName);
await app.RunWithTasksAsync();
}

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.6 8.00029C1.6 4.46548 4.46508 1.6 7.99857 1.6C11.5348 1.6 14.4 4.46592 14.4 8.00029C14.4 11.5335 11.5348 14.4 7.99857 14.4C6.16874 14.4 4.52033 13.6337 3.35202 12.3999C2.26458 11.2515 1.6 9.70508 1.6 8.00029ZM7.99857 0C3.58105 0 0 3.58219 0 8.00029C0 10.1304 0.83265 12.0663 2.19023 13.5C3.64805 15.0395 5.71117 16 7.99857 16C12.4184 16 16 12.4172 16 8.00029C16 3.58219 12.4184 0 7.99857 0ZM9.88726 11L10.9985 9.88994L9.10981 8.00274L10.9985 6.11255L9.88726 5L7.99804 6.8877L6.11031 5L4.99854 6.11255L6.88676 8.00274L4.99854 9.88994L6.11031 11L7.99804 9.1133L9.88726 11Z" fill="#A3A9AE"/>
</svg>

After

Width:  |  Height:  |  Size: 744 B

View File

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.70719 10.7072L11.7072 3.70718L10.293 2.29297L4.00008 8.58585L1.70718 6.29297L0.292969 7.70718L3.29298 10.7072C3.6835 11.0977 4.31666 11.0977 4.70719 10.7072Z" fill="#20D21F"/>
</svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@ -28,6 +28,7 @@
"EmptyFilterDescriptionTextRooms": "No rooms match this filter. Try a different one or clear filter to view all rooms.",
"EmptyFilterSubheadingText": "No files to be displayed for this filter here",
"EmptyFolderDecription": "Drop files here or create new ones.",
"EmptyFolderDescriptionUser": "Files and folders uploaded by admins will appeared here.",
"EmptyFolderHeader": "No files in this folder",
"EmptyRecycleBin": "Empty Trash",
"EmptyScreenFolder": "No docs here yet",

View File

@ -1,10 +1,13 @@
{
"ActiveSessions": "Active Sessions",
"ChangeEmailSuccess": "Email has been changed successfully",
"ChangesApplied": "Changes are applied",
"ConnectSocialNetworks": "Сonnect your social networks",
"ContactInformation": "Contact Information",
"CountCodesRemaining": "codes remaining",
"ChangePasswordAfterLoggingOut": "Change password after logging out",
"DarkTheme": "Dark theme",
"DescriptionForSecurity": "For more security, you need to change your password.",
"EditPhoto": "Edit photo",
"EditSubscriptionsBtn": "Edit subscriptions",
"EditUser": "Edit profile",
@ -13,6 +16,14 @@
"InviteAgainLbl": "Invite again",
"LightTheme": "Light theme",
"LoginSettings": "Login settings",
"LogoutAllActiveSessions": "Log out from all active sessions",
"LogoutActiveConnection": "Log out from active connection",
"LogoutAllActiveConnections": "Log out from all active connections",
"LogoutDescription": "Note. All active connections except this connection will be logged out, as it is currently in use.",
"LogoutBtn": "Log out",
"LogoutFrom": "Log out from {{platform}} {{browser}} ?",
"SuccessLogout": "The active connection was logged out: {{platform}}, {{browser}}",
"LogoutAllActiveSessionsDescription": "All active connections except this connection will be logged out, as it is currently in use.",
"MessageEmailActivationInstuctionsSentOnEmail": "The email activation instructions have been sent to the <strong>{{ email }}</strong> email address",
"MyProfile": "My profile",
"PhoneLbl": "Phone",

View File

@ -77,10 +77,9 @@ export default function withContent(WrappedContent) {
access === ShareAccessRights.FullAccess || // only badges?
access === ShareAccessRights.None; // TODO: fix access type for owner (now - None)
const linkStyles =
isTrashFolder || isArchiveFolder //|| window.innerWidth <= 1024
? { noHover: true }
: { onClick: onFilesClick };
const linkStyles = isTrashFolder //|| window.innerWidth <= 1024
? { noHover: true }
: { onClick: onFilesClick };
if (!isDesktop && !isTrashFolder && !isArchiveFolder) {
linkStyles.href = href;

View File

@ -158,8 +158,7 @@ export default function withFileActions(WrappedFileItem) {
!!e.target.closest(".lock-file") ||
!!e.target.closest(".additional-badges") ||
e.target.closest(".tag") ||
isTrashFolder ||
isArchiveFolder
isTrashFolder
)
return;

View File

@ -353,7 +353,7 @@ const Items = ({
);
if (isVisitor) {
items.length > 1 && items.splice(2, 0, filesHeader);
items.length > 2 && items.splice(2, 0, filesHeader);
} else {
items.splice(3, 0, filesHeader);
}

View File

@ -90,7 +90,8 @@ const ArticleMainButtonContent = (props) => {
isOwner,
isAdmin,
isVisitor,
canCreateFiles,
setInvitePanelOptions,
} = props;
@ -123,6 +124,7 @@ const ArticleMainButtonContent = (props) => {
);
const onCreateRoom = React.useCallback(() => {
console.log("click");
const event = new Event(Events.ROOM_CREATE);
window.dispatchEvent(event);
}, []);
@ -405,7 +407,9 @@ const ArticleMainButtonContent = (props) => {
? t("Common:Invite")
: t("Common:Actions");
const isDisabled = (!canCreate && !canInvite) || isArchiveFolder;
const isDisabled =
((!canCreate || (!canCreateFiles && !isRoomsFolder)) && !canInvite) ||
isArchiveFolder;
const isProfile = history.location.pathname === "/accounts/view/@self";
return (
@ -420,7 +424,7 @@ const ArticleMainButtonContent = (props) => {
!isArchiveFolder &&
!isArticleLoading &&
!isProfile &&
(canCreate || canInvite) && (
((canCreate && (canCreateFiles || isRoomsFolder)) || canInvite) && (
<MobileView
t={t}
titleProp={t("Upload")}
@ -485,6 +489,7 @@ export default inject(
uploadDataStore,
treeFoldersStore,
selectedFolderStore,
accessRightsStore,
}) => {
const { isLoaded, firstLoad, isLoading, canCreate } = filesStore;
const {
@ -509,6 +514,8 @@ export default inject(
const { isAdmin, isOwner, isVisitor } = auth.userStore.user;
const { canCreateFiles } = accessRightsStore;
return {
showText: auth.settingsStore.showText,
isMobileArticle: auth.settingsStore.isMobileArticle,
@ -525,6 +532,7 @@ export default inject(
selectedTreeNode,
canCreate,
canCreateFiles,
startUpload,

View File

@ -15,6 +15,7 @@ const EmptyFolderContainer = ({
linkStyles,
isRooms,
sectionWidth,
canCreateFiles,
}) => {
const onBackToParentFolder = () => {
setIsLoading(true);
@ -24,7 +25,7 @@ const EmptyFolderContainer = ({
: fetchFiles(parentId).finally(() => setIsLoading(false));
};
const buttons = (
const buttons = canCreateFiles ? (
<>
<div className="empty-folder_container-links">
<img
@ -75,13 +76,19 @@ const EmptyFolderContainer = ({
</Link>
</div>
</>
) : (
<></>
);
return (
<EmptyContainer
headerText={t("EmptyScreenFolder")}
style={{ gridColumnGap: "39px" }}
descriptionText={t("EmptyFolderDecription")}
descriptionText={
canCreateFiles
? t("EmptyFolderDecription")
: t("EmptyFolderDescriptionUser")
}
imageSrc="/static/images/empty_screen_alt.svg"
buttons={buttons}
sectionWidth={sectionWidth}
@ -90,24 +97,29 @@ const EmptyFolderContainer = ({
);
};
export default inject(({ filesStore, selectedFolderStore }) => {
const { fetchFiles, fetchRooms } = filesStore;
const { navigationPath, parentId } = selectedFolderStore;
export default inject(
({ accessRightsStore, filesStore, selectedFolderStore }) => {
const { fetchFiles, fetchRooms } = filesStore;
const { navigationPath, parentId } = selectedFolderStore;
let isRootRoom, isRoom, id;
if (navigationPath && navigationPath.length) {
const elem = navigationPath[0];
let isRootRoom, isRoom, id;
if (navigationPath && navigationPath.length) {
const elem = navigationPath[0];
isRootRoom = elem.isRootRoom;
isRoom = elem.isRoom;
id = elem.id;
isRootRoom = elem.isRootRoom;
isRoom = elem.isRoom;
id = elem.id;
}
const { canCreateFiles } = accessRightsStore;
return {
fetchFiles,
fetchRooms,
setIsLoading: filesStore.setIsLoading,
parentId: id ?? parentId,
isRooms: isRoom || isRootRoom,
canCreateFiles,
};
}
return {
fetchFiles,
fetchRooms,
setIsLoading: filesStore.setIsLoading,
parentId: id ?? parentId,
isRooms: isRoom || isRootRoom,
};
})(withTranslation(["Files", "Translations"])(observer(EmptyFolderContainer)));
)(withTranslation(["Files", "Translations"])(observer(EmptyFolderContainer)));

View File

@ -42,7 +42,6 @@ const RenameEvent = ({
const onUpdate = React.useCallback((e, value) => {
const originalTitle = getTitleWithoutExst(item);
setIsLoading(true);
let timerId;
const isSameTitle =
@ -52,7 +51,7 @@ const RenameEvent = ({
if (isSameTitle) {
setStartValue(originalTitle);
setIsLoading(false);
onClose();
return editCompleteAction(item, type);
@ -82,7 +81,6 @@ const RenameEvent = ({
timerId = null;
clearActiveOperations([item.id]);
setIsLoading(false);
onClose();
})
: renameFolder(item.id, value)
@ -106,7 +104,6 @@ const RenameEvent = ({
clearTimeout(timerId);
timerId = null;
clearActiveOperations(null, [item.id]);
setIsLoading(false);
onClose();
});
}, []);

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { isMobile, isIOS } from "react-device-detect";
import { inject, observer } from "mobx-react";
import { useLocation } from "react-router-dom";
import SmartBanner from "react-smartbanner";
import "./main.css";
@ -10,14 +11,9 @@ const Wrapper = styled.div`
`;
const ReactSmartBanner = (props) => {
const {
t,
ready,
isBannerVisible,
setIsBannerVisible,
currentProductId,
} = props;
const { t, ready, isBannerVisible, setIsBannerVisible } = props;
const force = isIOS ? "ios" : "android";
const location = useLocation();
const [isDocuments, setIsDocuments] = useState(false);
@ -43,12 +39,12 @@ const ReactSmartBanner = (props) => {
}, []);
useEffect(() => {
if (window.location.pathname.toLowerCase().includes("files")) {
if (window.location.pathname.toLowerCase().includes("rooms")) {
setIsDocuments(true);
} else {
setIsDocuments(false);
}
}, [currentProductId]);
}, [location]);
const storeText = {
ios: t("SmartBanner:AppStore"),
@ -99,10 +95,9 @@ const ReactSmartBanner = (props) => {
);
};
export default inject(({ auth, bannerStore }) => {
export default inject(({ bannerStore }) => {
return {
isBannerVisible: bannerStore.isBannerVisible,
setIsBannerVisible: bannerStore.setIsBannerVisible,
currentProductId: auth.settingsStore.currentProductId,
};
})(observer(ReactSmartBanner));

View File

@ -114,7 +114,6 @@
}
.smartbanner-ios {
background: #f2f2f2;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
line-height: 80px;
}
.smartbanner-ios .smartbanner-close {

View File

@ -0,0 +1,68 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import ModalDialog from "@docspace/components/modal-dialog";
import Checkbox from "@docspace/components/checkbox";
import Button from "@docspace/components/button";
import Box from "@docspace/components/box";
import Text from "@docspace/components/text";
import ModalDialogContainer from "../ModalDialogContainer";
const LogoutAllConnectionDialog = ({
visible,
onClose,
onRemoveAllSessions,
loading,
onRemoveAllExceptThis,
}) => {
const { t } = useTranslation(["Profile", "Common"]);
const [isChecked, setIsChecked] = useState(false);
const onChangeCheckbox = () => {
setIsChecked((prev) => !prev);
};
return (
<ModalDialogContainer
visible={visible}
onClose={onClose}
displayType="modal"
>
<ModalDialog.Header>
{t("Profile:LogoutAllActiveConnections")}
</ModalDialog.Header>
<ModalDialog.Body>
<Text as="p">{t("Profile:LogoutDescription")}</Text>
<Text as="p" style={{ margin: "15px 0" }}>
{t("Profile:DescriptionForSecurity")}
</Text>
<Box displayProp="flex" alignItems="center">
<Checkbox isChecked={isChecked} onChange={onChangeCheckbox} />
{t("Profile:ChangePasswordAfterLoggingOut")}
</Box>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="LogoutBtn"
label={t("Profile:LogoutBtn")}
size="normal"
scale
primary={true}
onClick={() =>
isChecked ? onRemoveAllSessions() : onRemoveAllExceptThis()
}
isLoading={loading}
/>
<Button
key="CloseBtn"
label={t("Common:CancelButton")}
size="normal"
scale
onClick={onClose}
isDisabled={loading}
/>
</ModalDialog.Footer>
</ModalDialogContainer>
);
};
export default LogoutAllConnectionDialog;

View File

@ -0,0 +1,54 @@
import React from "react";
import { useTranslation } from "react-i18next";
import ModalDialog from "@docspace/components/modal-dialog";
import Button from "@docspace/components/button";
import ModalDialogContainer from "../ModalDialogContainer";
const LogoutConnectionDialog = ({
visible,
onClose,
onRemoveSession,
data,
loading,
}) => {
const { t } = useTranslation(["Profile", "Common"]);
return (
<ModalDialogContainer
visible={visible}
onClose={onClose}
displayType="modal"
>
<ModalDialog.Header>
{t("Profile:LogoutActiveConnection")}
</ModalDialog.Header>
<ModalDialog.Body>
{t("Profile:LogoutFrom", {
platform: data.platform,
browser: data.browser,
})}
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="LogoutBtn"
label={t("Profile:LogoutBtn")}
size="normal"
scale
primary={true}
onClick={() => onRemoveSession(data.id)}
isLoading={loading}
/>
<Button
key="CloseBtn"
label={t("Common:CancelButton")}
size="normal"
scale
onClick={onClose}
isDisabled={loading}
/>
</ModalDialog.Footer>
</ModalDialogContainer>
);
};
export default LogoutConnectionDialog;

View File

@ -27,6 +27,8 @@ import ChangeNameDialog from "./ChangeNameDialog";
import AvatarEditorDialog from "./AvatarEditorDialog";
import DeletePortalDialog from "./DeletePortalDialog";
import InviteUsersWarningDialog from "./InviteUsersWarningDialog";
import LogoutConnectionDialog from "./LogoutConnectionDialog";
import LogoutAllConnectionDialog from "./LogoutAllConnectionDialog";
export {
EmptyTrashDialog,
@ -57,5 +59,7 @@ export {
ChangeNameDialog,
AvatarEditorDialog,
DeletePortalDialog,
LogoutConnectionDialog,
InviteUsersWarningDialog,
LogoutAllConnectionDialog,
};

View File

@ -107,6 +107,7 @@ const Table = ({
hasMoreAccounts,
filterTotal,
withPaging,
canChangeUserType,
}) => {
const ref = useRef(null);
@ -156,6 +157,7 @@ const Table = ({
isOwner={isOwner}
changeUserType={changeType}
userId={userId}
canChangeUserType={canChangeUserType}
/>
))}
</TableBody>
@ -165,7 +167,7 @@ const Table = ({
);
};
export default inject(({ peopleStore, auth }) => {
export default inject(({ peopleStore, auth, accessRightsStore }) => {
const {
usersStore,
filterStore,
@ -180,6 +182,8 @@ export default inject(({ peopleStore, auth }) => {
const { isVisible: infoPanelVisible } = auth.infoPanelStore;
const { isAdmin, isOwner, id: userId } = auth.userStore.user;
const { canChangeUserType } = accessRightsStore;
return {
peopleList,
viewAs,
@ -195,5 +199,6 @@ export default inject(({ peopleStore, auth }) => {
fetchMoreAccounts,
hasMoreAccounts,
filterTotal,
canChangeUserType,
};
})(observer(Table));

View File

@ -129,14 +129,15 @@ const PeopleTableRow = (props) => {
checkedProps,
onContentRowSelect,
onEmailClick,
isAdmin,
isOwner,
theme,
changeUserType,
userId,
setBufferSelection,
isActive,
isSeveralSelection,
canChangeUserType,
} = props;
const {
@ -147,10 +148,7 @@ const PeopleTableRow = (props) => {
position,
rooms,
id,
role,
isVisitor,
} = item;
@ -278,26 +276,9 @@ const PeopleTableRow = (props) => {
</Text>
);
if (userId === id || statusType === "disabled") return text;
const canChange = canChangeUserType(item);
switch (role) {
case "owner":
return text;
case "admin":
case "manager":
if (isOwner) {
return combobox;
} else {
return text;
}
case "user":
return combobox;
default:
return text;
}
return canChange ? combobox : text;
};
const typeCell = renderTypeCell();

View File

@ -78,6 +78,7 @@ const SectionFilterContent = ({
newFilter.page = 0;
newFilter.role = role;
newFilter.group = group;
setIsLoading(true);
@ -144,11 +145,11 @@ const SectionFilterContent = ({
isHeader: true,
isLast: true,
},
{ key: "admin", group: "filter-type", label: t("Administrator") },
{ key: "admin", group: "filter-type", label: t("Common:DocSpaceAdmin") },
{
key: "manager",
group: "filter-type",
label: "Manager",
label: t("Common:RoomAdmin"),
},
{
key: "user",
@ -266,14 +267,14 @@ const SectionFilterContent = ({
switch (filter.role) {
case "admin":
label = t("Administrator");
label = t("Common:DocSpaceAdmin");
break;
case "manager":
label = t("Common:RoomAdmin");
break;
case "user":
label = userCaption;
break;
case "guest":
label = guestCaption;
break;
default:
label = "";
}

View File

@ -163,7 +163,7 @@ const SectionHeaderContent = (props) => {
setInfoPanelIsVisible,
isInfoPanelVisible,
isOwner,
isAdmin,
setInvitePanelOptions,
} = props;

View File

@ -54,7 +54,7 @@ const PureHome = ({
setIsRefresh(true);
const newFilter = Filter.getFilter(location);
//console.log("PEOPLE URL changed", pathname, newFilter);
getUsersList(newFilter).finally(() => {
getUsersList(newFilter, true).finally(() => {
setFirstLoad(false);
setIsLoading(false);
setIsRefresh(false);

View File

@ -179,7 +179,6 @@ export default inject(({ auth, selectedFolderStore }) => {
calculateSelection,
normalizeSelection,
isItemChanged,
selectionParentRoom,
setSelectionParentRoom,
roomsView,
fileView,

View File

@ -50,6 +50,8 @@ const ItemContextOptions = ({
contextMenuRef.current.hide();
}, [selection]);
const options = contextHelper.getItemContextOptions();
return (
<StyledItemContextOptions onClick={setItemAsBufferSelection}>
<ContextMenu
@ -57,14 +59,16 @@ const ItemContextOptions = ({
getContextModel={contextHelper.getItemContextOptions}
withBackdrop={false}
/>
<ContextMenuButton
className="expandButton"
title={"Show item actions"}
onClick={onContextMenu}
getData={contextHelper.getItemContextOptions}
directionX="right"
isNew={true}
/>
{options.length > 0 && (
<ContextMenuButton
className="expandButton"
title={"Show item actions"}
onClick={onContextMenu}
getData={contextHelper.getItemContextOptions}
directionX="right"
isNew={true}
/>
)}
</StyledItemContextOptions>
);
};

View File

@ -17,6 +17,7 @@ const Accounts = ({
isOwner,
isAdmin,
changeUserType,
canChangeUserType,
selfId,
}) => {
const [statusLabel, setStatusLabel] = React.useState("");
@ -127,26 +128,9 @@ const Accounts = ({
const status = getUserStatus(selection);
if (selfId === id || status === "disabled") return text;
const canChange = canChangeUserType({ ...selection, statusType: status });
switch (role) {
case "owner":
return text;
case "admin":
case "manager":
if (isOwner) {
return combobox;
} else {
return text;
}
case "user":
return combobox;
default:
return text;
}
return canChange ? combobox : text;
};
const typeData = renderTypeData();

View File

@ -31,12 +31,14 @@ const Members = ({
setInvitePanelOptions,
changeUserType,
canInviteUserInRoom,
}) => {
const membersHelper = new MembersHelper({ t });
const [members, setMembers] = useState(null);
const [showLoader, setShowLoader] = useState(false);
const isDisabledInvite = !canInviteUserInRoom({ access: selection.access });
const fetchMembers = async (roomId) => {
let timerId;
if (members) timerId = setTimeout(() => setShowLoader(true), 1000);
@ -123,6 +125,7 @@ const Members = ({
isFill={true}
onClick={onClickInviteUsers}
size={16}
isDisabled={isDisabledInvite}
/>
</StyledUserTypeHeader>
@ -174,28 +177,32 @@ const Members = ({
);
};
export default inject(({ auth, filesStore, peopleStore, dialogsStore }) => {
const { selectionParentRoom, setSelectionParentRoom } = auth.infoPanelStore;
const { getRoomMembers, updateRoomMemberRole } = filesStore;
const { isOwner, isAdmin, id: selfId } = auth.userStore.user;
const { setInvitePanelOptions } = dialogsStore;
const { changeType: changeUserType } = peopleStore;
export default inject(
({ auth, filesStore, peopleStore, dialogsStore, accessRightsStore }) => {
const { selectionParentRoom, setSelectionParentRoom } = auth.infoPanelStore;
const { getRoomMembers, updateRoomMemberRole } = filesStore;
const { isOwner, isAdmin, id: selfId } = auth.userStore.user;
const { setInvitePanelOptions } = dialogsStore;
const { changeType: changeUserType } = peopleStore;
const { canInviteUserInRoom } = accessRightsStore;
return {
selectionParentRoom,
setSelectionParentRoom,
return {
selectionParentRoom,
setSelectionParentRoom,
getRoomMembers,
updateRoomMemberRole,
getRoomMembers,
updateRoomMemberRole,
isOwner,
isAdmin,
selfId,
isOwner,
isAdmin,
selfId,
setInvitePanelOptions,
changeUserType,
};
})(
setInvitePanelOptions,
changeUserType,
canInviteUserInRoom,
};
}
)(
withTranslation([
"InfoPanel",
"Common",

View File

@ -17,6 +17,7 @@ import {
import { ColorTheme, ThemeType } from "@docspace/common/components/ColorTheme";
import { StyledInfoPanelHeader } from "./styles/common";
import { FolderType } from "@docspace/common/constants";
const InfoPanelHeaderContent = (props) => {
const {
@ -30,6 +31,8 @@ const InfoPanelHeaderContent = (props) => {
getIsGallery,
getIsAccounts,
isRootFolder,
canRemoveUserFromRoom,
rootFolderType,
} = props;
const isRooms = getIsRooms();
@ -47,6 +50,9 @@ const InfoPanelHeaderContent = (props) => {
const setHistory = () => setView("history");
const setDetails = () => setView("details");
const isArchiveRoot = rootFolderType === FolderType.Archive;
const canRemove = canRemoveUserFromRoom({ access: selection?.access });
const submenuData = [
{
id: "members",
@ -68,7 +74,11 @@ const InfoPanelHeaderContent = (props) => {
},
];
const roomsSubmenu = [...submenuData];
const roomsSubmenu = isArchiveRoot
? !canRemove
? [{ ...submenuData[2] }]
: [{ ...submenuData[0] }, { ...submenuData[2] }]
: [...submenuData];
const personalSubmenu = [submenuData[1], submenuData[2]];
const isTablet =
@ -122,7 +132,7 @@ const InfoPanelHeaderContent = (props) => {
);
};
export default inject(({ auth, selectedFolderStore }) => {
export default inject(({ auth, selectedFolderStore, accessRightsStore }) => {
const {
selection,
setIsVisible,
@ -134,7 +144,8 @@ export default inject(({ auth, selectedFolderStore }) => {
getIsGallery,
getIsAccounts,
} = auth.infoPanelStore;
const { isRootFolder } = selectedFolderStore;
const { isRootFolder, rootFolderType } = selectedFolderStore;
const { canRemoveUserFromRoom } = accessRightsStore;
return {
selection,
@ -148,6 +159,8 @@ export default inject(({ auth, selectedFolderStore }) => {
getIsAccounts,
isRootFolder,
rootFolderType,
canRemoveUserFromRoom,
};
})(
withTranslation(["Common", "InfoPanel"])(

View File

@ -183,6 +183,8 @@ const StyledTableRow = styled(TableRow)`
.row_update-text,
.expandButton,
.badges,
.tag,
.author-cell,
.table-container_cell > p {
margin-top: 2px;
}

View File

@ -4,7 +4,7 @@ import Avatar from "@docspace/components/avatar";
const AuthorCell = ({ fileOwner, sideColor, item }) => {
return (
<StyledAuthorCell>
<StyledAuthorCell className="author-cell">
<Avatar
source={item.createdBy.avatarSmall}
className="author-avatar-cell"

View File

@ -395,7 +395,11 @@ const SectionFilterContent = ({
}
filterValues.push({
key: isMe ? FilterKeys.me : roomsFilter.subjectId,
key: isMe
? roomsFilter.excludeSubject
? FilterKeys.other
: FilterKeys.me
: roomsFilter.subjectId,
group: FilterGroups.roomFilterOwner,
label: label,
});

View File

@ -4,7 +4,11 @@ import styled, { css } from "styled-components";
import { withRouter } from "react-router";
import toastr from "@docspace/components/toast/toastr";
import Loaders from "@docspace/common/components/Loaders";
import { AppServerConfig } from "@docspace/common/constants";
import {
AppServerConfig,
FolderType,
RoomSearchArea,
} from "@docspace/common/constants";
import { withTranslation } from "react-i18next";
import { isMobile, isTablet, isMobileOnly } from "react-device-detect";
import DropDownItem from "@docspace/components/drop-down-item";
@ -546,16 +550,22 @@ class SectionHeaderContent extends React.Component {
setAlreadyFetchingRooms,
categoryType,
rootFolderType,
} = this.props;
setIsLoading(true);
setAlreadyFetchingRooms(true);
fetchRooms(null, null)
.then(() => {
const filter = RoomsFilter.getDefault();
const filter = RoomsFilter.getDefault();
if (rootFolderType === FolderType.Archive) {
filter.searchArea = RoomSearchArea.Archive;
}
fetchRooms(null, filter)
.then(() => {
const filterParamsStr = filter.toUrlParams();
const url = getCategoryUrl(categoryType, filter.folder);
@ -595,7 +605,7 @@ class SectionHeaderContent extends React.Component {
showText,
isRoomsFolder,
isEmptyPage,
isVisitor,
canCreateFiles,
} = this.props;
const menuItems = this.getMenuItems();
const isLoading = !title || !tReady;
@ -624,7 +634,7 @@ class SectionHeaderContent extends React.Component {
sectionWidth={context.sectionWidth}
showText={showText}
isRootFolder={isRootFolder}
canCreate={canCreate && !isVisitor}
canCreate={canCreate && canCreateFiles}
title={title}
isDesktop={isDesktop}
isTabletView={isTabletView}
@ -669,7 +679,7 @@ export default inject(
treeFoldersStore,
filesActionsStore,
settingsStore,
accessRightsStore,
contextOptionsStore,
}) => {
const {
@ -732,6 +742,7 @@ export default inject(
roomType,
pathParts,
navigationPath,
rootFolderType,
} = selectedFolderStore;
const selectedFolder = { ...selectedFolderStore };
@ -748,6 +759,8 @@ export default inject(
onClickReconnectStorage,
} = contextOptionsStore;
const { canCreateFiles } = accessRightsStore;
return {
showText: auth.settingsStore.showText,
isDesktop: auth.settingsStore.isDesktopClient,
@ -759,6 +772,7 @@ export default inject(
pathParts: pathParts,
navigationPath: navigationPath,
canCreate,
canCreateFiles,
setIsInfoPanelVisible: setIsVisible,
isInfoPanelVisible: isVisible,
isHeaderVisible,
@ -818,6 +832,8 @@ export default inject(
onClickInviteUsers,
onShowInfoPanel,
onClickArchive,
rootFolderType,
};
}
)(

View File

@ -135,6 +135,16 @@ class ArticleBodyContent extends React.Component {
if (tReady) setIsLoadedArticleBody(true);
if (prevProps.location.pathname !== this.props.location.pathname) {
if (this.props.location.pathname.includes("payments")) {
this.setState({ selectedKeys: ["4-0"] });
}
if (this.props.location.pathname.includes("common")) {
this.setState({ selectedKeys: ["0-0"] });
}
}
if (!isArrayEqual(prevState.selectedKeys, this.state.selectedKeys)) {
const { selectedKeys } = this.state;

View File

@ -0,0 +1,261 @@
import React, { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import { ReactSVG } from "react-svg";
import { isMobile } from "react-device-detect";
import Text from "@docspace/components/text";
import Link from "@docspace/components/link";
import Box from "@docspace/components/box";
import HelpButton from "@docspace/components/help-button";
import toastr from "@docspace/components/toast/toastr";
import {
LogoutConnectionDialog,
LogoutAllConnectionDialog,
} from "SRC_DIR/components/dialogs";
import {
StyledFooter,
Table,
TableHead,
TableRow,
TableHeaderCell,
TableBody,
TableDataCell,
} from "./styled-active-sessions";
const removeIcon = (
<ReactSVG className="remove-icon" src="images/remove.session.svg" />
);
const tickIcon = (
<ReactSVG className="tick-icon" wrapper="span" src="images/tick.svg" />
);
const ActiveSessions = ({
t,
culture,
getAllSessions,
removeAllSessions,
removeSession,
logoutVisible,
setLogoutVisible,
logoutAllVisible,
setLogoutAllVisible,
removeAllExecptThis,
}) => {
const [sessions, setSessions] = useState([]);
const [currentSession, setCurrentSession] = useState(0);
const [modalData, setModalData] = useState({});
const [loading, setLoading] = useState(false);
useEffect(() => {
getAllSessions().then((res) => {
setSessions(res.items);
setCurrentSession(res.loginEvent);
});
}, []);
const onClickRemoveAllSessions = async () => {
try {
setLoading(true);
await removeAllSessions().then(() =>
getAllSessions().then((res) => setSessions(res.items))
);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
setLogoutAllVisible(false);
}
};
const onClickRemoveAllExceptThis = async () => {
try {
setLoading(true);
await removeAllExecptThis().then(() =>
getAllSessions().then((res) => setSessions(res.items))
);
} catch (error) {
toastr.error(error);
} finally {
setLoading(false);
setLogoutAllVisible(false);
}
};
const onClickRemoveSession = async (id) => {
const foundSession = sessions.find((s) => s.id === id);
try {
setLoading(true);
await removeSession(foundSession.id).then(() =>
getAllSessions().then((res) => setSessions(res.items))
);
toastr.success(
t("Profile:SuccessLogout", {
platform: foundSession.platform,
browser: foundSession.browser,
})
);
} catch (error) {
toastr.error(error);
} finally {
setLoading(false);
setLogoutVisible(false);
}
};
const convertTime = (date) => {
return new Date(date).toLocaleString(culture);
};
return (
<StyledFooter>
<Text fontSize="16px" fontWeight={700}>
{t("Profile:ActiveSessions")}
</Text>
<Box
displayProp="flex"
alignItems="center"
justifyContent="flex-start"
marginProp="10px 0 0"
>
<Link
className="session-logout"
type="action"
isHovered
onClick={() => setLogoutAllVisible(true)}
>
{t("Profile:LogoutAllActiveSessions")}
</Link>
<HelpButton
iconName="/static/images/info.react.svg"
tooltipContent={
<Text fontSize="13px">
{t("Profile:LogoutAllActiveSessionsDescription")}
</Text>
}
/>
</Box>
{isMobile ? (
<Table>
<TableBody>
{sessions.map((session) => (
<TableRow key={session.id}>
<TableDataCell style={{ borderTop: "0" }}>
{session.platform}
<span className="session-browser">
<span>{session.browser}</span>
</span>
{currentSession === session.id ? tickIcon : null}
<Box flexDirection="column" alignItems="center">
<span className="session-date">
{convertTime(session.date)}
</span>
<span className="session-ip">{session.ip}</span>
</Box>
</TableDataCell>
<TableDataCell
style={{ borderTop: "0" }}
onClick={() => {
setLogoutVisible(true);
setModalData({
id: session.id,
platform: session.platform,
browser: session.browser,
});
}}
>
{currentSession !== session.id ? removeIcon : null}
</TableDataCell>
</TableRow>
))}
</TableBody>
</Table>
) : (
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>{t("Common:Sessions")}</TableHeaderCell>
<TableHeaderCell>{t("Common:Date")}</TableHeaderCell>
<TableHeaderCell>{t("Common:IpAddress")}</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{sessions.map((session) => (
<TableRow key={session.id}>
<TableDataCell>
{session.platform}
<span className="session-browser">
<span>{session.browser}</span>
</span>
{currentSession === session.id ? tickIcon : null}
</TableDataCell>
<TableDataCell>{convertTime(session.date)}</TableDataCell>
<TableDataCell>{session.ip}</TableDataCell>
<TableDataCell
onClick={() => {
setLogoutVisible(true);
setModalData({
id: session.id,
platform: session.platform,
browser: session.browser,
});
}}
>
{currentSession !== session.id ? removeIcon : null}
</TableDataCell>
</TableRow>
))}
</TableBody>
</Table>
)}
{logoutVisible && (
<LogoutConnectionDialog
visible={logoutVisible}
data={modalData}
loading={loading}
onClose={() => setLogoutVisible(false)}
onRemoveSession={onClickRemoveSession}
/>
)}
{logoutAllVisible && (
<LogoutAllConnectionDialog
visible={logoutAllVisible}
loading={loading}
onClose={() => setLogoutAllVisible(false)}
onRemoveAllSessions={onClickRemoveAllSessions}
onRemoveAllExceptThis={onClickRemoveAllExceptThis}
/>
)}
</StyledFooter>
);
};
export default inject(({ auth, setup }) => {
const { culture } = auth.settingsStore;
const {
getAllSessions,
removeAllSessions,
removeSession,
logoutVisible,
setLogoutVisible,
logoutAllVisible,
setLogoutAllVisible,
removeAllExecptThis,
} = setup;
return {
culture,
getAllSessions,
removeAllSessions,
removeSession,
logoutVisible,
setLogoutVisible,
logoutAllVisible,
setLogoutAllVisible,
removeAllExecptThis,
};
})(observer(withTranslation(["Profile", "Common"])(ActiveSessions)));

View File

@ -0,0 +1,114 @@
import styled from "styled-components";
import { hugeMobile } from "@docspace/components/utils/device";
export const StyledFooter = styled.div`
.session-logout {
font-size: 13px;
font-weight: 600;
}
.icon-button {
margin-left: 4px;
}
`;
export const Table = styled.table`
width: 100%;
border-collapse: collapse;
margin-top: 4px;
`;
export const TableHead = styled.thead`
font-size: 12px;
`;
export const TableRow = styled.tr`
display: table-row;
`;
export const TableHeaderCell = styled.th`
border-top: 1px solid ${(props) => props.theme.activeSessions.borderColor};
border-bottom: 1px solid ${(props) => props.theme.activeSessions.borderColor};
text-align: left;
font-weight: 600;
padding: 14px 0;
color: #a3a9ae;
position: relative;
border-top: 0;
:not(:first-child):before {
content: "";
position: absolute;
top: 17px;
left: -8px;
width: 1px;
height: 10px;
background: #d0d5da;
}
`;
export const TableBody = styled.tbody`
font-size: 11px;
`;
export const TableDataCell = styled.td`
border-top: 1px solid ${(props) => props.theme.activeSessions.borderColor};
border-bottom: 1px solid ${(props) => props.theme.activeSessions.borderColor};
text-align: left;
font-weight: 600;
padding: 14px 0;
color: #a3a9ae;
@media ${hugeMobile} {
.session-browser {
position: relative;
top: 4px;
max-width: 150px;
display: inline-block;
margin-left: 0 !important;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
span {
width: 100%;
}
}
}
:first-child {
font-size: 13px;
color: ${(props) => props.theme.activeSessions.color};
span {
color: #a3a9ae;
margin-left: 5px;
}
}
.session-date {
position: relative;
margin-left: 0 !important;
margin-right: 8px;
:after {
content: "";
position: absolute;
top: 4px;
right: -8px;
width: 1px;
height: 12px;
background: #d0d5da;
}
}
:last-child {
text-align: center;
}
.remove-icon {
svg {
cursor: pointer;
width: 20px;
height: 20px;
path {
fill: #a3a9ae;
}
}
}
`;

View File

@ -1,2 +1,3 @@
export { default as SectionHeaderContent } from "./Header";
export { default as SectionBodyContent } from "./Body";
export { default as SectionFooterContent } from "./Footer";

View File

@ -3,7 +3,12 @@ import PropTypes from "prop-types";
import Section from "@docspace/common/components/Section";
import toastr from "@docspace/components/toast/toastr";
import { SectionHeaderContent, SectionBodyContent } from "./Section";
import {
SectionHeaderContent,
SectionBodyContent,
SectionFooterContent,
} from "./Section";
import { withRouter } from "react-router";
import withCultureNames from "@docspace/common/hoc/withCultureNames";
import { inject, observer } from "mobx-react";
@ -80,7 +85,7 @@ class Profile extends React.Component {
}
render() {
//console.log("Profile render");
// console.log("Profile render");
const { profile, showCatalog } = this.props;
@ -93,6 +98,10 @@ class Profile extends React.Component {
<Section.SectionBody>
<SectionBodyContent profile={profile} />
</Section.SectionBody>
<Section.SectionFooter>
<SectionFooterContent profile={profile} />
</Section.SectionFooter>
</Section>
);
}

View File

@ -0,0 +1,471 @@
import { makeAutoObservable } from "mobx";
import {
EmployeeActivationStatus,
EmployeeStatus,
FolderType,
ShareAccessRights,
} from "@docspace/common/constants";
class AccessRightsStore {
authStore = null;
selectedFolderStore = null;
constructor(authStore, selectedFolderStore) {
this.authStore = authStore;
this.selectedFolderStore = selectedFolderStore;
makeAutoObservable(this);
}
canEditRoom = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = this.selectedFolderStore;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.RoomManager === room.access ||
ShareAccessRights.FullAccess === room.access
)
return true;
return false;
};
canInviteUserInRoom = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = this.selectedFolderStore;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.RoomManager === room.access ||
ShareAccessRights.FullAccess === room.access
)
return true;
return false;
};
get canChangeUserRoleInRoom() {}
canRemoveUserFromRoom = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.RoomManager === room.access ||
ShareAccessRights.FullAccess === room.access
)
return true;
return false;
};
canArchiveRoom = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access
)
return true;
return false;
};
canRemoveRoom = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access
)
return true;
return false;
};
get canCreateFiles() {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { access, rootFolderType } = this.selectedFolderStore;
if (rootFolderType === FolderType.Archive) return false;
if (
access === ShareAccessRights.None ||
access === ShareAccessRights.FullAccess ||
access === ShareAccessRights.RoomManager ||
isAdmin ||
isOwner
)
return true;
return false;
}
canDownloadFiles = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access
)
return true;
return false;
};
canEditFile = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access ||
ShareAccessRights.Editing === room.access
)
return true;
return false;
};
canFillForm = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access ||
ShareAccessRights.Editing === room.access ||
ShareAccessRights.FormFilling === room.access
)
return true;
return false;
};
canPeerReview = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access ||
ShareAccessRights.Editing === room.access ||
ShareAccessRights.FormFilling === room.access ||
ShareAccessRights.Review === room.access
)
return true;
return false;
};
canCommentFile = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (isAdmin || isOwner || ShareAccessRights.ReadOnly !== room.access)
return true;
return false;
};
canBlockFile = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access
)
return true;
return false;
};
canShowVersionHistory = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access ||
ShareAccessRights.Editing === room.access
)
return true;
return false;
};
canManageVersionHistory = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access
)
return true;
return false;
};
canMoveFile = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access
)
return true;
return false;
};
canDeleteFile = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access
)
return true;
return false;
};
canRenameFile = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access
)
return true;
return false;
};
canCopyFile = (room) => {
const { isAdmin, isOwner } = this.authStore.userStore.user;
const { rootFolderType } = room;
if (rootFolderType === FolderType.Archive) return false;
if (
isAdmin ||
isOwner ||
ShareAccessRights.None === room.access ||
ShareAccessRights.FullAccess === room.access ||
ShareAccessRights.RoomManager === room.access
)
return true;
return false;
};
canChangeUserType = (user) => {
const { id, isOwner } = this.authStore.userStore.user;
const { id: userId, statusType, role } = user;
if (userId === id || statusType === EmployeeStatus.Disabled) return false;
switch (role) {
case "owner":
return false;
case "admin":
case "manager":
if (isOwner) {
return true;
} else {
return false;
}
case "user":
return true;
default:
return false;
}
};
canMakeEmployeeUser = (user) => {
const { id, isOwner } = this.authStore.userStore.user;
const {
status,
id: userId,
isAdmin: userIsAdmin,
isOwner: userIsOwner,
isVisitor: userIsVisitor,
} = user;
const needMakeEmployee =
status !== EmployeeStatus.Disabled && userId !== id;
if (isOwner) return needMakeEmployee;
return needMakeEmployee && !userIsAdmin && !userIsOwner && userIsVisitor;
};
canActivateUser = (user) => {
const { id, isOwner, isAdmin } = this.authStore.userStore.user;
const {
status,
id: userId,
isAdmin: userIsAdmin,
isOwner: userIsOwner,
} = user;
const needActivate = status !== EmployeeStatus.Active && userId !== id;
if (isOwner) return needActivate;
if (isAdmin) return needActivate && !userIsAdmin && !userIsOwner;
return false;
};
canDisableUser = (user) => {
const { id, isOwner, isAdmin } = this.authStore.userStore.user;
const {
status,
id: userId,
isAdmin: userIsAdmin,
isOwner: userIsOwner,
} = user;
const needDisable = status !== EmployeeStatus.Disabled && userId !== id;
if (isOwner) return needDisable;
if (isAdmin) return needDisable && !userIsAdmin && !userIsOwner;
return false;
};
canInviteUser = (user) => {
const { id, isOwner } = this.authStore.userStore.user;
const {
activationStatus,
status,
id: userId,
isAdmin: userIsAdmin,
isOwner: userIsOwner,
} = user;
const needInvite =
activationStatus === EmployeeActivationStatus.Pending &&
status === EmployeeStatus.Active &&
userId !== id;
if (isOwner) return needInvite;
return needInvite && !userIsAdmin && !userIsOwner;
};
canRemoveUser = (user) => {
const { id, isOwner, isAdmin } = this.authStore.userStore.user;
const {
status,
id: userId,
isAdmin: userIsAdmin,
isOwner: userIsOwner,
} = user;
const needRemove = status === EmployeeStatus.Disabled && userId !== id;
if (isOwner) return needRemove;
if (isAdmin) return needRemove && !userIsAdmin && !userIsOwner;
return false;
};
}
export default AccessRightsStore;

View File

@ -1,7 +1,7 @@
import { makeAutoObservable } from "mobx";
class BannerStore {
isBannerVisible = false; //TODO: set to true after fix
isBannerVisible = true;
constructor() {
makeAutoObservable(this);

View File

@ -18,6 +18,7 @@ import {
FileStatus,
} from "@docspace/common/constants";
import { makeAutoObservable } from "mobx";
import { isMobile } from "react-device-detect";
import toastr from "@docspace/components/toast/toastr";
import { TIMEOUT } from "@docspace/client/src/helpers/filesConstants";
import { checkProtocol } from "../helpers/files-helpers";
@ -26,6 +27,7 @@ import { AppServerConfig } from "@docspace/common/constants";
import config from "PACKAGE_FILE";
import FilesFilter from "@docspace/common/api/files/filter";
import api from "@docspace/common/api";
import { isTablet } from "@docspace/components/utils/device";
class FilesActionStore {
authStore;
@ -36,6 +38,7 @@ class FilesActionStore {
settingsStore;
dialogsStore;
mediaViewerDataStore;
accessRightsStore;
isBulkDownload = false;
@ -47,7 +50,8 @@ class FilesActionStore {
selectedFolderStore,
settingsStore,
dialogsStore,
mediaViewerDataStore
mediaViewerDataStore,
accessRightsStore
) {
makeAutoObservable(this);
this.authStore = authStore;
@ -58,6 +62,7 @@ class FilesActionStore {
this.settingsStore = settingsStore;
this.dialogsStore = dialogsStore;
this.mediaViewerDataStore = mediaViewerDataStore;
this.accessRightsStore = accessRightsStore;
}
setIsBulkDownload = (isBulkDownload) => {
@ -404,7 +409,7 @@ class FilesActionStore {
});
try {
await removeFiles(folderIds, null, true, true).then(async (res) => {
await removeFiles(folderIds, [], true, true).then(async (res) => {
if (res[0]?.error) return Promise.reject(res[0].error);
const data = res[0] ? res[0] : null;
const pbData = {
@ -1252,46 +1257,70 @@ class FilesActionStore {
};
isAvailableOption = (option) => {
const {
isFavoritesFolder,
isRecentFolder,
isCommonFolder,
} = this.treeFoldersStore;
const { isFavoritesFolder, isRecentFolder } = this.treeFoldersStore;
const {
isAccessedSelected,
canConvertSelected,
isThirdPartyRootSelection,
hasSelection,
allFilesIsEditing,
selection,
} = this.filesStore;
const { personal } = this.authStore.settingsStore;
const { userAccess } = this.filesStore;
const {
canCopyFile,
canDeleteFile,
canMoveFile,
canArchiveRoom,
canRemoveRoom,
} = this.accessRightsStore;
const { access, rootFolderType } = this.selectedFolderStore;
switch (option) {
case "showInfo":
case "copy":
const canCopy = canCopyFile({ access, rootFolderType });
return hasSelection && canCopy;
case "showInfo":
case "download":
return hasSelection;
case "downloadAs":
return canConvertSelected;
case "moveTo":
const canMove = canMoveFile({ access, rootFolderType });
return (
!isThirdPartyRootSelection &&
hasSelection &&
isAccessedSelected &&
!isRecentFolder &&
!isFavoritesFolder &&
!allFilesIsEditing
!allFilesIsEditing &&
canMove
);
case "archive":
case "unarchive":
const canArchive = selection
.map((s) => canArchiveRoom(s))
.filter((s) => s);
return canArchive.length > 0;
case "delete-room":
const canRemove = selection
.map((s) => canRemoveRoom(s))
.filter((r) => r);
return canRemove.length > 0;
case "delete":
const canDelete = canDeleteFile({ access, rootFolderType });
const deleteCondition =
!isThirdPartyRootSelection &&
hasSelection &&
isAccessedSelected &&
!allFilesIsEditing;
return isCommonFolder ? userAccess && deleteCondition : deleteCondition;
return canDelete && deleteCondition;
}
};
@ -1373,6 +1402,14 @@ class FilesActionStore {
this.deleteItemAction(items, translations, null, null, true);
};
onShowInfoPanel = () => {
const { selection } = this.filesStore;
const { setSelection, setIsVisible } = this.authStore.infoPanelStore;
setSelection([selection]);
setIsVisible(true);
};
getOption = (option, t) => {
const {
setSharingPanelVisible,
@ -1383,6 +1420,15 @@ class FilesActionStore {
} = this.dialogsStore;
switch (option) {
case "show-info":
if (!isTablet() && !isMobile) return null;
else
return {
key: "show-info",
label: t("Common:Info"),
iconUrl: "/static/images/info.outline.react.svg",
onClick: this.onShowInfoPanel,
};
case "copy":
if (!this.isAvailableOption("copy")) return null;
else
@ -1438,23 +1484,27 @@ class FilesActionStore {
disabled: false,
};
case "archive":
return {
key: "archive",
label: t("Archived"),
iconUrl: "/static/images/room.archive.svg",
onClick: () => this.moveRoomsToArchive(t),
disabled: false,
};
if (!this.isAvailableOption("archive")) return null;
else
return {
key: "archive",
label: t("Archived"),
iconUrl: "/static/images/room.archive.svg",
onClick: () => this.moveRoomsToArchive(t),
disabled: false,
};
case "unarchive":
return {
key: "unarchive",
label: t("Common:Restore"),
iconUrl: "images/subtract.react.svg",
onClick: () => this.moveRoomsFromArchive(t),
disabled: false,
};
if (!this.isAvailableOption("unarchive")) return null;
else
return {
key: "unarchive",
label: t("Common:Restore"),
iconUrl: "images/subtract.react.svg",
onClick: () => this.moveRoomsFromArchive(t),
disabled: false,
};
case "delete-room":
if (!this.isAvailableOption("delete")) return null;
if (!this.isAvailableOption("delete-room")) return null;
else
return {
label: t("Common:Delete"),
@ -1507,8 +1557,13 @@ class FilesActionStore {
getArchiveRoomsFolderOptions = (itemsCollection, t) => {
const archive = this.getOption("unarchive", t);
const deleteOption = this.getOption("delete-room", t);
const showOption = this.getOption("show-info", t);
itemsCollection
.set("unarchive", archive)
.set("show-info", showOption)
.set("delete", deleteOption);
itemsCollection.set("unarchive", archive).set("delete", deleteOption);
return this.convertToArray(itemsCollection);
};

View File

@ -38,6 +38,8 @@ class FilesStore {
filesSettingsStore;
thirdPartyStore;
accessRightsStore;
isLoaded = false;
isLoading = false;
@ -97,7 +99,8 @@ class FilesStore {
selectedFolderStore,
treeFoldersStore,
filesSettingsStore,
thirdPartyStore
thirdPartyStore,
accessRightsStore
) {
const pathname = window.location.pathname.toLowerCase();
this.isEditor = pathname.indexOf("doceditor") !== -1;
@ -109,6 +112,7 @@ class FilesStore {
this.treeFoldersStore = treeFoldersStore;
this.filesSettingsStore = filesSettingsStore;
this.thirdPartyStore = thirdPartyStore;
this.accessRightsStore = accessRightsStore;
const { socketHelper, withPaging } = authStore.settingsStore;
@ -973,6 +977,29 @@ class FilesStore {
removeOptions = (options, toRemoveArray) =>
options.filter((o) => !toRemoveArray.includes(o));
removeSeparator = (options) => {
const newOptions = options.map((o, index) => {
if (index === 0 && o.includes("separator")) {
return false;
}
if (index === options.length - 1 && o.includes("separator")) {
return false;
}
if (
o?.includes("separator") &&
options[index + 1].includes("separator")
) {
return false;
}
return o;
});
return newOptions.filter((o) => o);
};
getFilesContextOptions = (item, canOpenPlayer) => {
const isVisitor =
(this.authStore.userStore.user &&
@ -999,8 +1026,6 @@ class FilesStore {
isRecycleBinFolder,
isPrivacyFolder,
isRecentFolder,
isCommon,
isShare,
isFavoritesFolder,
isShareFolder,
isMy,
@ -1013,15 +1038,40 @@ class FilesStore {
canFormFillingDocs,
} = this.filesSettingsStore;
const {
canEditRoom,
canInviteUserInRoom,
canArchiveRoom,
canRemoveRoom,
canEditFile,
canFillForm,
canBlockFile,
canShowVersionHistory,
canManageVersionHistory,
canDeleteFile,
canMoveFile,
canRenameFile,
canCopyFile,
} = this.accessRightsStore;
const editFile = canEditFile(item);
const fillForm = canFillForm(item);
const blockFile = canBlockFile(item);
const showVersionHistory = canShowVersionHistory(item);
const manageVersionHistory = canManageVersionHistory(item);
const deleteFile = canDeleteFile(item);
const moveFile = canMoveFile(item);
const renameFile = canRenameFile(item);
const copyFile = canCopyFile(item);
const { enablePlugins } = this.authStore.settingsStore;
const isThirdPartyFolder =
item.providerKey && item.id === item.rootFolderId;
const isShareItem = isShare(item.rootFolderType);
const isCommonFolder = isCommon(item.rootFolderType);
const isMyFolder = isMy(item.rootFolderType);
const { personal } = this.authStore.settingsStore;
const { isDesktopClient } = this.authStore.settingsStore;
const pluginAllKeys =
@ -1068,30 +1118,63 @@ class FilesStore {
"restore",
"rename",
"separator2",
"unsubscribe",
// "unsubscribe",
"delete",
];
if (!editFile) {
fileOptions = this.removeOptions(fileOptions, ["edit"]);
}
if (!fillForm) {
fileOptions = this.removeOptions(fileOptions, ["fill-form"]);
}
if (!blockFile) {
fileOptions = this.removeOptions(fileOptions, [
"block-unblock-version",
]);
}
if (!showVersionHistory) {
fileOptions = this.removeOptions(fileOptions, ["show-version-history"]);
}
if (!manageVersionHistory) {
fileOptions = this.removeOptions(fileOptions, ["finalize-version"]);
}
if (!deleteFile) {
fileOptions = this.removeOptions(fileOptions, ["delete"]);
}
if (!moveFile) {
fileOptions = this.removeOptions(fileOptions, ["move-to"]);
}
if (!renameFile) {
fileOptions = this.removeOptions(fileOptions, ["rename"]);
}
if (!copyFile) {
fileOptions = this.removeOptions(fileOptions, ["copy-to", "copy"]);
}
if (!showVersionHistory && !manageVersionHistory) {
fileOptions = this.removeOptions(fileOptions, ["version"]);
if (item.rootFolderType === FolderType.Archive) {
fileOptions = this.removeOptions(fileOptions, ["separator0"]);
}
}
if (!moveFile && !copyFile) {
fileOptions = this.removeOptions(fileOptions, ["move"]);
}
if (item.rootFolderType === FolderType.Archive) {
fileOptions = this.removeOptions(fileOptions, [
"mark-read",
"mark-as-favorite",
"remove-from-favorites",
]);
}
if (!isMasterForm)
fileOptions = this.removeOptions(fileOptions, ["make-form"]);
if (!shouldFillForm)
fileOptions = this.removeOptions(fileOptions, ["fill-form"]);
if (personal) {
fileOptions = this.removeOptions(fileOptions, [
"owner-change",
"link-for-portal-users",
"docu-sign",
"mark-read",
"unsubscribe",
]);
if (!shouldEdit && !shouldView) {
fileOptions = this.removeOptions(fileOptions, ["sharing-settings"]);
}
}
if (!canConvert) {
fileOptions = this.removeOptions(fileOptions, ["download-as"]);
}
@ -1225,29 +1308,6 @@ class FilesStore {
}
}
if (!isFullAccess) {
fileOptions = this.removeOptions(fileOptions, [
"finalize-version",
"rename",
"block-unblock-version",
"copy",
"sharing-settings",
]);
}
if (isVisitor) {
fileOptions = this.removeOptions(fileOptions, [
"block-unblock-version",
"finalize-version",
"mark-as-favorite",
"remove-from-favorites",
]);
if (!isFullAccess) {
fileOptions = this.removeOptions(fileOptions, ["rename"]);
}
}
if (!this.canShareOwnerChange(item)) {
fileOptions = this.removeOptions(fileOptions, ["owner-change"]);
}
@ -1260,28 +1320,6 @@ class FilesStore {
]);
}
if (isCommonFolder) {
if (!this.userAccess) {
fileOptions = this.removeOptions(fileOptions, [
"owner-change",
"move-to",
"delete",
"copy",
"separator2",
]);
if (!isFavorite) {
fileOptions = this.removeOptions(fileOptions, ["separator2"]);
}
}
}
if (withoutShare) {
fileOptions = this.removeOptions(fileOptions, [
"sharing-settings",
"external-link",
]);
}
if (!hasNew) {
fileOptions = this.removeOptions(fileOptions, ["mark-read"]);
}
@ -1296,22 +1334,6 @@ class FilesStore {
fileOptions = this.removeOptions(fileOptions, ["open-location"]);
}
if (isShareItem) {
if (!isFullAccess) {
fileOptions = this.removeOptions(fileOptions, ["edit"]);
}
if (isShareFolder) {
fileOptions = this.removeOptions(fileOptions, [
"copy",
"move-to",
"delete",
]);
}
} else if (!isEncrypted) {
fileOptions = this.removeOptions(fileOptions, ["unsubscribe"]);
}
if (isPrivacyFolder) {
fileOptions = this.removeOptions(fileOptions, [
"preview",
@ -1331,20 +1353,15 @@ class FilesStore {
);
}
if (!shouldEdit && !shouldView && !fileOptions.includes("view")) {
fileOptions = this.removeOptions(fileOptions, [
"edit",
"preview",
"separator0",
]);
}
if (!shouldEdit && shouldView) {
fileOptions = this.removeOptions(fileOptions, ["edit"]);
}
fileOptions = this.removeSeparator(fileOptions);
return fileOptions;
} else if (isRoom) {
const canEdit = canEditRoom(item);
const canInviteUser = canInviteUserInRoom(item);
const canArchive = canArchiveRoom(item);
const canRemove = canRemoveRoom(item);
let roomOptions = [
"select",
"separator0",
@ -1360,6 +1377,32 @@ class FilesStore {
"delete",
];
if (!canEdit) {
roomOptions = this.removeOptions(roomOptions, [
"edit-room",
"reconnect-storage",
]);
}
if (!canInviteUser) {
roomOptions = this.removeOptions(roomOptions, ["invite-users-to-room"]);
}
if (!canArchive) {
roomOptions = this.removeOptions(roomOptions, [
"archive-room",
"unarchive-room",
]);
}
if (!canRemove) {
roomOptions = this.removeOptions(roomOptions, ["delete"]);
}
if (!canRemove && !canArchive) {
roomOptions = this.removeOptions(roomOptions, ["separator1"]);
}
if (!item.providerKey) {
roomOptions = this.removeOptions(roomOptions, ["reconnect-storage"]);
}
@ -1398,12 +1441,14 @@ class FilesStore {
}
}
roomOptions = this.removeSeparator(roomOptions);
return roomOptions;
} else {
let folderOptions = [
"select",
"open",
"separator0",
// "separator0",
// "sharing-settings",
"owner-change",
"show-info",
@ -1419,20 +1464,25 @@ class FilesStore {
"rename",
"change-thirdparty-info",
"separator2",
"unsubscribe",
// "unsubscribe",
"delete",
];
if (personal) {
folderOptions = this.removeOptions(folderOptions, [
"sharing-settings",
"owner-change",
"link-for-portal-users",
"separator1",
"docu-sign",
"mark-read",
"unsubscribe",
]);
if (!deleteFile) {
folderOptions = this.removeOptions(folderOptions, ["delete"]);
}
if (!moveFile) {
folderOptions = this.removeOptions(folderOptions, ["move-to"]);
}
if (!renameFile) {
folderOptions = this.removeOptions(folderOptions, ["rename"]);
}
if (!copyFile) {
folderOptions = this.removeOptions(folderOptions, ["copy-to", "copy"]);
}
if (!moveFile && !copyFile) {
folderOptions = this.removeOptions(folderOptions, ["move"]);
}
if (isPrivacyFolder) {
@ -1447,17 +1497,6 @@ class FilesStore {
}
}
if (isShareItem) {
if (isShareFolder) {
folderOptions = this.removeOptions(folderOptions, [
"move-to",
"delete",
]);
}
} else {
folderOptions = this.removeOptions(folderOptions, ["unsubscribe"]);
}
if (isRecycleBinFolder) {
folderOptions = this.removeOptions(folderOptions, [
"open",
@ -1486,39 +1525,10 @@ class FilesStore {
}
}
if (!isFullAccess) {
//TODO: if added Projects, add project folder check
folderOptions = this.removeOptions(folderOptions, [
"rename",
"change-thirdparty-info",
]);
}
if (!this.canShareOwnerChange(item)) {
folderOptions = this.removeOptions(folderOptions, ["owner-change"]);
}
if (!isFullAccess) {
folderOptions = this.removeOptions(folderOptions, [
"owner-change",
"move-to",
"delete",
"change-thirdparty-info",
]);
if (!isShareItem) {
folderOptions = this.removeOptions(folderOptions, ["separator2"]);
}
if (isVisitor) {
folderOptions = this.removeOptions(folderOptions, ["rename"]);
}
}
if (withoutShare) {
folderOptions = this.removeOptions(folderOptions, ["sharing-settings"]);
}
if (!hasNew) {
folderOptions = this.removeOptions(folderOptions, ["mark-read"]);
}
@ -1574,6 +1584,8 @@ class FilesStore {
folderOptions = this.removeOptions(folderOptions, ["open-location"]);
}
folderOptions = this.removeSeparator(folderOptions);
return folderOptions;
}
};

View File

@ -59,7 +59,7 @@ class HotkeyStore {
activateHotkeys = (e) => {
if (
this.dialogsStore.someDialogIsOpen ||
e.target?.tagName === "INPUT" ||
(e.target?.tagName === "INPUT" && e.target.type !== "checkbox") ||
e.target?.tagName === "TEXTAREA"
)
return e;
@ -528,10 +528,14 @@ class HotkeyStore {
get caretIndex() {
const { filesList, hotkeyCaret, selection } = this.filesStore;
const item =
selection.length && selection.length === 1 && !hotkeyCaret
const item = hotkeyCaret
? hotkeyCaret
: selection.length
? selection.length === 1
? selection[0]
: hotkeyCaret;
: selection[selection.length - 1]
: null;
const caretIndex = filesList.findIndex(
(f) => f.id === item?.id && f.isFolder === item?.isFolder

View File

@ -39,10 +39,11 @@ class PeopleStore {
loadingStore = null;
infoPanelStore = null;
setupStore = null;
accessRightsStore = null;
isInit = false;
viewAs = isMobileRDD ? "row" : "table";
constructor(authStore, infoPanelStore, setupStore) {
constructor(authStore, infoPanelStore, setupStore, accessRightsStore) {
this.authStore = authStore;
this.groupsStore = new GroupsStore(this);
this.usersStore = new UsersStore(this, authStore);
@ -58,6 +59,7 @@ class PeopleStore {
this.loadingStore = new LoadingStore();
this.infoPanelStore = infoPanelStore;
this.setupStore = setupStore;
this.accessRightsStore = accessRightsStore;
this.contextOptionsStore = new AccountsContextOptionsStore(this);

View File

@ -1,8 +1,5 @@
import { makeAutoObservable } from "mobx";
import {
EmployeeStatus,
EmployeeActivationStatus,
} from "@docspace/common/constants";
import { EmployeeStatus } from "@docspace/common/constants";
import { getUserStatus } from "../helpers/people-helpers";
class SelectionStore {
@ -102,47 +99,17 @@ class SelectionStore {
}
get hasUsersToMakeEmployees() {
const { id, isOwner } = this.peopleStore.authStore.userStore.user;
const { canMakeEmployeeUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) => x.status !== EmployeeStatus.Disabled && x.id !== id
);
return users.length > 0;
}
const users = this.selection.filter(
(x) =>
x.status !== EmployeeStatus.Disabled &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner &&
x.isVisitor
);
const users = this.selection.filter((x) => canMakeEmployeeUser(x));
return users.length > 0;
}
get getUsersToMakeEmployees() {
const { id, isOwner } = this.peopleStore.authStore.userStore.user;
const { canMakeEmployeeUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) => x.status !== EmployeeStatus.Disabled && x.id !== id
);
return users.map((u) => u);
}
const users = this.selection.filter(
(x) =>
x.status !== EmployeeStatus.Disabled &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner &&
x.isVisitor
);
const users = this.selection.filter((x) => canMakeEmployeeUser(x));
return users.map((u) => u);
}
@ -156,211 +123,67 @@ class SelectionStore {
}
get hasUsersToActivate() {
const { id, isOwner, isAdmin } = this.peopleStore.authStore.userStore.user;
const { canActivateUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) => x.status !== EmployeeStatus.Active && x.id !== id
);
const users = this.selection.filter((x) => canActivateUser(x));
return users.length > 0;
}
if (isAdmin && !isOwner) {
const users = this.selection.filter(
(x) =>
x.status !== EmployeeStatus.Active &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner
);
return users.length > 0;
}
return false;
return users.length > 0;
}
get getUsersToActivate() {
const { id, isOwner, isAdmin } = this.peopleStore.authStore.userStore.user;
const { canActivateUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) => x.status !== EmployeeStatus.Active && x.id !== id
);
const users = this.selection.filter((x) => canActivateUser(x));
return users.map((u) => u);
}
if (isAdmin && !isOwner) {
const users = this.selection.filter(
(x) =>
x.status !== EmployeeStatus.Active &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner
);
return users.map((u) => u);
}
return [];
return users.map((u) => u);
}
get hasUsersToDisable() {
const { id, isOwner, isAdmin } = this.peopleStore.authStore.userStore.user;
const { canDisableUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) => x.status !== EmployeeStatus.Disabled && x.id !== id
);
const users = this.selection.filter((x) => canDisableUser(x));
return users.length > 0;
}
if (isAdmin && !isOwner) {
const users = this.selection.filter(
(x) =>
x.status !== EmployeeStatus.Disabled &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner
);
return users.length > 0;
}
return false;
return users.length > 0;
}
get getUsersToDisable() {
const { id, isOwner, isAdmin } = this.peopleStore.authStore.userStore.user;
const { canDisableUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) => x.status !== EmployeeStatus.Disabled && x.id !== id
);
const users = this.selection.filter((x) => canDisableUser(x));
return users.map((u) => u);
}
if (isAdmin && !isOwner) {
const users = this.selection.filter(
(x) =>
x.status !== EmployeeStatus.Disabled &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner
);
return users.map((u) => u);
}
return [];
return users.map((u) => u);
}
get hasUsersToInvite() {
const { id, isOwner, isAdmin } = this.peopleStore.authStore.userStore.user;
const { canInviteUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) =>
x.activationStatus === EmployeeActivationStatus.Pending &&
x.status === EmployeeStatus.Active &&
x.id !== id
);
return users.length > 0;
}
const users = this.selection.filter(
(x) =>
x.activationStatus === EmployeeActivationStatus.Pending &&
x.status === EmployeeStatus.Active &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner
);
const users = this.selection.filter((x) => canInviteUser(x));
return users.length > 0;
}
get getUsersToInviteIds() {
const { id, isOwner, isAdmin } = this.peopleStore.authStore.userStore.user;
const { canRemoveUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) =>
x.activationStatus === EmployeeActivationStatus.Pending &&
x.status === EmployeeStatus.Active &&
x.id !== id
);
const users = this.selection.filter((x) => canRemoveUser(x));
return users.map((u) => u.id);
}
const users = this.selection.filter(
(x) =>
x.activationStatus === EmployeeActivationStatus.Pending &&
x.status === EmployeeStatus.Active &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner
);
return users.map((u) => u.id);
return users.length > 0 ? users.map((u) => u.id) : false;
}
get hasUsersToRemove() {
const { id, isOwner, isAdmin } = this.peopleStore.authStore.userStore.user;
const { canRemoveUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) => x.status === EmployeeStatus.Disabled && x.id !== id
);
const users = this.selection.filter((x) => canRemoveUser(x));
return users.length > 0;
}
if (isAdmin && !isOwner) {
const users = this.selection.filter(
(x) =>
x.status === EmployeeStatus.Disabled &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner
);
return users.length > 0;
}
return false;
return users.length > 0;
}
get getUsersToRemoveIds() {
const { id, isOwner, isAdmin } = this.peopleStore.authStore.userStore.user;
const { canRemoveUser } = this.peopleStore.accessRightsStore;
if (isOwner) {
const users = this.selection.filter(
(x) => x.status === EmployeeStatus.Disabled && x.id !== id
);
const users = this.selection.filter((x) => canRemoveUser(x));
return users.map((u) => u.id);
}
if (isAdmin && !isOwner) {
const users = this.selection.filter(
(x) =>
x.status === EmployeeStatus.Disabled &&
x.id !== id &&
!x.isAdmin &&
!x.isOwner
);
return users.map((u) => u.id);
}
return false;
return users.length > 0 ? users : false;
}
}

View File

@ -13,6 +13,8 @@ class SettingsSetupStore {
selectionStore = null;
authStore = null;
isInit = false;
logoutVisible = false;
logoutAllVisible = false;
viewAs = isMobile ? "row" : "table";
security = {
@ -339,6 +341,26 @@ class SettingsSetupStore {
this.setCommonThirdPartyList(res);
};
getAllSessions = () => {
return api.settings.getAllActiveSessions();
};
removeAllSessions = () => {
return api.settings.removeAllActiveSessions();
};
removeAllExecptThis = () => {
return api.settings.removeAllExceptThisSession();
};
removeSession = (id) => {
return api.settings.removeActiveSession(id);
};
setLogoutVisible = (visible) => (this.logoutVisible = visible);
setLogoutAllVisible = (visible) => (this.logoutAllVisible = visible);
}
export default SettingsSetupStore;

View File

@ -30,9 +30,14 @@ import selectFileDialogStore from "./SelectFileDialogStore";
import TagsStore from "./TagsStore";
import PeopleStore from "./PeopleStore";
import OformsStore from "./OformsStore";
import AccessRightsStore from "./AccessRightsStore";
const oformsStore = new OformsStore(authStore);
const selectedFolderStore = new SelectedFolderStore(authStore.settingsStore);
const accessRightsStore = new AccessRightsStore(authStore, selectedFolderStore);
const paymentStore = new PaymentStore();
const wizardStore = new WizardStore();
const setupStore = new SettingsSetupStore();
@ -46,13 +51,12 @@ const ssoStore = new SsoFormStore();
const peopleStore = new PeopleStore(
authStore,
authStore.infoPanelStore,
setupStore
setupStore,
accessRightsStore
);
const tagsStore = new TagsStore();
const selectedFolderStore = new SelectedFolderStore(authStore.settingsStore);
const treeFoldersStore = new TreeFoldersStore(selectedFolderStore);
const settingsStore = new SettingsStore(thirdPartyStore, treeFoldersStore);
@ -61,7 +65,8 @@ const filesStore = new FilesStore(
selectedFolderStore,
treeFoldersStore,
settingsStore,
thirdPartyStore
thirdPartyStore,
accessRightsStore
);
const mediaViewerDataStore = new MediaViewerDataStore(
@ -97,7 +102,8 @@ const filesActionsStore = new FilesActionsStore(
selectedFolderStore,
settingsStore,
dialogsStore,
mediaViewerDataStore
mediaViewerDataStore,
accessRightsStore
);
const contextOptionsStore = new ContextOptionsStore(
@ -167,6 +173,8 @@ const store = {
tagsStore,
peopleStore,
accessRightsStore,
};
export default store;

View File

@ -135,6 +135,7 @@ class Filter {
break;
case "manager":
dtoFilter.employeeType = 1;
dtoFilter.isadministrator = "false";
break;
case "user":
dtoFilter.employeeType = 2;

View File

@ -664,3 +664,32 @@ export function getPortalQuota() {
url: `/settings/quota`,
});
}
export function getAllActiveSessions() {
return request({
method: "get",
url: "/security/activeconnections",
});
}
export function removeAllActiveSessions() {
return request({
method: "put",
url: "/security/activeconnections/logoutallchangepassword",
});
}
export function removeAllExceptThisSession() {
return request({
method: "put",
url: "/security/activeconnections/logoutallexceptthis",
});
}
export function removeActiveSession(eventId) {
return request({
method: "put",
url: `/security/activeconnections/logout/${eventId}`,
data: { eventId },
});
}

View File

@ -186,6 +186,7 @@ const FilterBlockItem = ({
<StyledFilterBlockItemTagText
noSelect={true}
isSelected={item.isSelected}
truncate
>
{item.label}
</StyledFilterBlockItemTagText>

View File

@ -106,6 +106,9 @@ const StyledFilterBlockItem = styled.div`
props.withoutHeader ? "0" : props.isFirst ? "12px 0 0 0" : "16px 0 0 0"};
padding: 0 15px 0 16px;
max-width: 480px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: start;
@ -165,6 +168,8 @@ const StyledFilterBlockItemTag = styled.div`
height: 28px;
max-height: 28px;
max-width: 100%;
display: flex;
flex-direction: row;
align-items: center;

View File

@ -23,6 +23,8 @@ import InfoPanel from "./sub-components/info-panel";
import SubInfoPanelBody from "./sub-components/info-panel-body";
import SubInfoPanelHeader from "./sub-components/info-panel-header";
import SubSectionFooter from "./sub-components/section-footer";
import ReactResizeDetector from "react-resize-detector";
import FloatingButton from "../FloatingButton";
import { inject, observer } from "mobx-react";
@ -50,6 +52,11 @@ function SectionBody() {
}
SectionBody.displayName = "SectionBody";
function SectionFooter() {
return null;
}
SectionFooter.displayName = "SectionFooter";
function SectionPaging() {
return null;
}
@ -69,6 +76,7 @@ class Section extends React.Component {
static SectionHeader = SectionHeader;
static SectionFilter = SectionFilter;
static SectionBody = SectionBody;
static SectionFooter = SectionFooter;
static SectionPaging = SectionPaging;
static InfoPanelBody = InfoPanelBody;
@ -159,6 +167,7 @@ class Section extends React.Component {
let sectionFilterContent = null;
let sectionPagingContent = null;
let sectionBodyContent = null;
let sectionFooterContent = null;
let infoPanelBodyContent = null;
let infoPanelHeaderContent = null;
@ -173,13 +182,15 @@ class Section extends React.Component {
case SectionFilter.displayName:
sectionFilterContent = child;
break;
case SectionPaging.displayName:
sectionPagingContent = child;
break;
case SectionBody.displayName:
sectionBodyContent = child;
break;
case SectionFooter.displayName:
sectionFooterContent = child;
break;
case InfoPanelBody.displayName:
infoPanelBodyContent = child;
break;
@ -297,6 +308,12 @@ class Section extends React.Component {
: null}
</SubSectionBodyContent>
<SubSectionFooter>
{sectionFooterContent
? sectionFooterContent.props.children
: null}
</SubSectionFooter>
{isSectionPagingAvailable && (
<SubSectionPaging>
{sectionPagingContent
@ -353,6 +370,7 @@ class Section extends React.Component {
<></>
)}
</SectionContainer>
{isInfoPanelAvailable && (
<InfoPanel viewAs={viewAs}>
<SubInfoPanelHeader>
@ -443,6 +461,7 @@ Section.SectionHeader = SectionHeader;
Section.SectionFilter = SectionFilter;
Section.SectionBody = SectionBody;
Section.SectionPaging = SectionPaging;
Section.SectionFooter = SectionFooter;
export default inject(({ auth }) => {
const { infoPanelStore, settingsStore } = auth;

View File

@ -0,0 +1,14 @@
import React from "react";
import styled from "styled-components";
const StyledSectionFooter = styled.div`
margin-top: 40px;
`;
const SectionFooter = ({ children }) => {
return <StyledSectionFooter>{children}</StyledSectionFooter>;
};
SectionFooter.displayName = "SectionFooter";
export default SectionFooter;

View File

@ -63,6 +63,15 @@ const StyledDropdownIcon = styled(ReactSVG)`
align-items: center;
pointer-events: none;
svg {
path:first-child {
stroke: ${(props) => props.theme.tag.color};
}
path:last-child {
fill: ${(props) => props.theme.tag.color};
}
}
`;
const StyledDropdownText = styled(Text)`

View File

@ -2983,6 +2983,11 @@ const Base = {
},
},
activeSessions: {
color: "#333",
borderColor: "#eceef1",
},
formWrapper: {
background: white,
boxShadow: "0px 5px 20px rgba(4, 15, 27, 0.07)",

View File

@ -2988,6 +2988,11 @@ const Dark = {
},
},
activeSessions: {
borderColor: "#858585",
color: "#eeeeee",
},
formWrapper: {
background: black,
boxShadow: "0px 5px 20px rgba(0, 0, 0, 0.16);",

View File

@ -120,6 +120,9 @@ public class FolderDtoHelper : FileEntryDtoHelper
FolderType.CustomRoom => RoomType.CustomRoom,
_ => null,
};
result.ParentId = folder.ProviderEntry && folder.RootFolderType is FolderType.VirtualRooms ? await _globalFolderHelper.GetFolderVirtualRooms<T>() :
folder.ProviderEntry && folder.RootFolderType is FolderType.VirtualRooms ? await _globalFolderHelper.GetFolderVirtualRooms<T>() : folder.ParentId;
}
if (folder.RootFolderType == FolderType.USER

View File

@ -27,7 +27,7 @@
namespace ASC.Web.Files;
[Scope]
public class FilesSpaceUsageStatManager : SpaceUsageStatManager
public class FilesSpaceUsageStatManager : SpaceUsageStatManager, IUserSpaceUsage
{
private readonly IDbContextFactory<FilesDbContext> _dbContextFactory;
private readonly TenantManager _tenantManager;
@ -37,6 +37,9 @@ public class FilesSpaceUsageStatManager : SpaceUsageStatManager
private readonly CommonLinkUtility _commonLinkUtility;
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly PathProvider _pathProvider;
private readonly IDaoFactory _daoFactory;
private readonly GlobalFolder _globalFolder;
private readonly FileMarker _fileMarker;
public FilesSpaceUsageStatManager(
IDbContextFactory<FilesDbContext> dbContextFactory,
@ -46,7 +49,10 @@ public class FilesSpaceUsageStatManager : SpaceUsageStatManager
DisplayUserSettingsHelper displayUserSettingsHelper,
CommonLinkUtility commonLinkUtility,
GlobalFolderHelper globalFolderHelper,
PathProvider pathProvider)
PathProvider pathProvider,
IDaoFactory daoFactory,
GlobalFolder globalFolder,
FileMarker fileMarker)
{
_dbContextFactory = dbContextFactory;
_tenantManager = tenantManager;
@ -56,6 +62,9 @@ public class FilesSpaceUsageStatManager : SpaceUsageStatManager
_commonLinkUtility = commonLinkUtility;
_globalFolderHelper = globalFolderHelper;
_pathProvider = pathProvider;
_daoFactory = daoFactory;
_globalFolder = globalFolder;
_fileMarker = fileMarker;
}
public override ValueTask<List<UsageSpaceStatItem>> GetStatDataAsync()
@ -107,31 +116,8 @@ public class FilesSpaceUsageStatManager : SpaceUsageStatManager
.ToListAsync();
}
}
[Scope]
public class FilesUserSpaceUsage : IUserSpaceUsage
{
private readonly IDbContextFactory<FilesDbContext> _dbContextFactory;
private readonly TenantManager _tenantManager;
private readonly GlobalFolder _globalFolder;
private readonly FileMarker _fileMarker;
private readonly IDaoFactory _daoFactory;
public FilesUserSpaceUsage(
IDbContextFactory<FilesDbContext> dbContextFactory,
TenantManager tenantManager,
GlobalFolder globalFolder,
FileMarker fileMarker,
IDaoFactory daoFactory)
{
_dbContextFactory = dbContextFactory;
_tenantManager = tenantManager;
_globalFolder = globalFolder;
_fileMarker = fileMarker;
_daoFactory = daoFactory;
}
public async Task<long> GetUserSpaceUsageAsync(Guid userId)
{
var tenantId = _tenantManager.GetCurrentTenant().Id;
@ -143,4 +129,15 @@ public class FilesUserSpaceUsage : IUserSpaceUsage
.Where(r => r.TenantId == tenantId && r.CreateBy == userId && (r.ParentId == my || r.ParentId == trash))
.SumAsync(r => r.ContentLength);
}
public async Task RecalculateUserQuota(int TenantId, Guid userId)
{
_tenantManager.SetCurrentTenant(TenantId);
var size = await GetUserSpaceUsageAsync(userId);
_tenantManager.SetTenantQuotaRow(
new TenantQuotaRow { Tenant = TenantId, Path = $"/{FileConstant.ModuleId}/", Counter = size, Tag = WebItemManager.DocumentsProductID.ToString(), UserId = userId, LastModified = DateTime.UtcNow },
false);
}
}

View File

@ -31,7 +31,7 @@ public class ProductEntryPoint : Product
{
internal const string ProductPath = "/products/files/";
//public FilesSpaceUsageStatManager FilesSpaceUsageStatManager { get; }
private readonly FilesSpaceUsageStatManager _filesSpaceUsageStatManager;
private readonly CoreBaseSettings _coreBaseSettings;
private readonly AuthContext _authContext;
private readonly UserManager _userManager;
@ -42,7 +42,7 @@ public class ProductEntryPoint : Product
public ProductEntryPoint() { }
public ProductEntryPoint(
// FilesSpaceUsageStatManager filesSpaceUsageStatManager,
FilesSpaceUsageStatManager filesSpaceUsageStatManager,
CoreBaseSettings coreBaseSettings,
AuthContext authContext,
UserManager userManager,
@ -50,7 +50,7 @@ public class ProductEntryPoint : Product
// SubscriptionManager subscriptionManager
)
{
// FilesSpaceUsageStatManager = filesSpaceUsageStatManager;
_filesSpaceUsageStatManager = filesSpaceUsageStatManager;
_coreBaseSettings = coreBaseSettings;
_authContext = authContext;
_userManager = userManager;
@ -83,7 +83,7 @@ public class ProductEntryPoint : Product
LargeIconFileName = "images/files.svg",
DefaultSortOrder = 10,
//SubscriptionManager = SubscriptionManager,
//SpaceUsageStatManager = FilesSpaceUsageStatManager,
SpaceUsageStatManager = _filesSpaceUsageStatManager,
AdminOpportunities = adminOpportunities,
UserOpportunities = userOpportunities,
CanNotBeDisabled = true,

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