Merge branch 'release/v1.1.0' into bugfix/file-operations

This commit is contained in:
Alexey Safronov 2023-06-05 11:31:17 +04:00
commit 5296f8db73
44 changed files with 1944 additions and 1110 deletions

161
.github/workflows/build_packages.yml vendored Normal file
View File

@ -0,0 +1,161 @@
name: Build packages
on:
push:
branches:
- release/*
- develop
- hotfix/*
paths:
- build/install/deb**
- build/install/rpm**
workflow_dispatch:
env:
BRANCH_NAME: $(echo ${GITHUB_REF#refs/heads/})
PRODUCT_LOW: $(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' )
PRODUCT: "${{ github.event.repository.name }}"
BUILD_NUMBER: "${{ github.run_number }}"
jobs:
build_deb:
name: DEB packages
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Import GPG
uses: crazy-max/ghaction-import-gpg@v5
id: gpg_step
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PRIVATE_KEY_PASS }}
- name: Get files from repository
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Prepare build
id: get_vars
run: |
wget -O - https://dl.yarnpkg.com/debian/pubkey.gpg | \
sudo gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/yarnkey.gpg --import
sudo chmod 644 /usr/share/keyrings/yarnkey.gpg
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian/ stable main" | \
sudo tee /etc/apt/sources.list.d/yarn.list
wget https://packages.microsoft.com/config/$(lsb_release -is | \
tr [:upper:] [:lower:])/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
wget -O - https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y dotnet-sdk-7.0 yarn nodejs dh-make rename dpkg-sig lintian
sudo npm install -g json
echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT
echo "VERSION=$(echo "${{ github.ref }}" | grep -oP '\d+\.\d+\.\d+' || echo "1.0.0")" >> $GITHUB_OUTPUT
- name: Build
shell: bash
run: |
cd build/install/deb/
rename -f -v "s/product([^\/]*)$/${{ env.PRODUCT_LOW }}\$1/g" debian/* ../common/* ../common/logrotate/*
find ../ -type f -exec sed -i "s/{{product}}/${{ env.PRODUCT_LOW }}/g" {} ';'
sed -i "s/{{package_header_tag_version}}/${{ steps.get_vars.outputs.VERSION }}.$BUILD_NUMBER/g" \
debian/changelog debian/control
dpkg-buildpackage -uc -k${{ steps.gpg_step.outputs.fingerprint }}
- name: Upload to Nexus
run: |
for file in /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/*.deb; do
echo $file
curl --verbose --user ${{ secrets.REPO_LOGIN }}:${{ secrets.REPO_PASS }} -H "Content-Type: multipart/form-data" \
--data-binary "@$file" ${{ secrets.REPO_URL_4TESTING_DEB }}
done
- name: Lint
run: |
lintian --suppress-tags=mismatched-override --profile debian /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/*.deb \
| tee -a file
if grep -qE '^(W:|E:)' file; then echo \
"::warning Noticedeb=lintian::$(cat file | awk '/^W:/ { ws += 1 } /^E:/ { es += 1 } END { print "Warnings:", ws, "Errors:", es }')"; fi
build_rpm:
name: RPM packages
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Import GPG
uses: crazy-max/ghaction-import-gpg@v5
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PRIVATE_KEY_PASS }}
- name: Get files from repository
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Prepare build
id: get_vars
run: |
wget -O - https://dl.yarnpkg.com/debian/pubkey.gpg | sudo gpg --no-default-keyring --keyring \
gnupg-ring:/usr/share/keyrings/yarnkey.gpg --import
sudo chmod 644 /usr/share/keyrings/yarnkey.gpg
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian/ stable main" | \
sudo tee /etc/apt/sources.list.d/yarn.list
wget https://packages.microsoft.com/config/$(lsb_release -is | \
tr [:upper:] [:lower:])/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
wget -O - https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y dotnet-sdk-7.0 yarn nodejs dh-make rename python3-pip python3-rpm
sudo npm install -g json
sudo pip install rpmlint
echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT
echo "VERSION=$(echo "${ GITHUB_REF##*/ }" | grep -oP '\d+\.\d+\.\d+' || echo "1.0.0")" >> $GITHUB_OUTPUT
- name: Build
run: |
cd build/install/rpm/SPECS
mv ./SOURCES/product.rpmlintrc ./SOURCES/${{ env.PRODUCT_LOW }}.rpmlintrc
wget https://github.com/ONLYOFFICE/${{ env.PRODUCT }}/archive/${{ env.BRANCH_NAME }}.tar.gz \
-O ./SOURCES/${{ env.PRODUCT }}-$(echo ${{ env.BRANCH_NAME }} | tr '/' '-').tar.gz
wget https://github.com/ONLYOFFICE/document-templates/archive/main/community-server.tar.gz \
-O ./SOURCES/document-templates-main-community-server.tar.gz
wget https://github.com/ONLYOFFICE/dictionaries/archive/master.tar.gz \
-O ./SOURCES/dictionaries-master.tar.gz
sed -i -e '/BuildRequires/d' product.spec
rpmbuild -D "packager Ascensio System SIA <support@onlyoffice.com>" -D "GIT_BRANCH $(echo ${{ env.BRANCH_NAME }} \
| tr '/' '-')" -D "_topdir $(pwd)" -D "version ${{ steps.get_vars.outputs.VERSION }}" \
-D "release ${{ env.BUILD_NUMBER }}" -ba product.spec
- name: Sign
run: |
cat << EOF >> $HOME/.rpmmacros
%_signature gpg
%_gpg_name ${{ secrets.GPG_KEY_NAME }}
%_gpg_path $HOME/.gnupg
%__gpg /usr/bin/gpg
EOF
gpg --export --armor --output onlyoffice-gpgkey.pub
rpm --import onlyoffice-gpgkey.pub
rpm --addsign /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/rpm/SPECS/RPMS/x86_64/*.rpm
- name: Upload
run: |
for file in /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/rpm/SPECS/RPMS/x86_64/*.rpm; do
curl --verbose --user ${{ secrets.REPO_LOGIN }}:${{ secrets.REPO_PASS }} \
--upload-file "$file" ${{ secrets.REPO_URL_4TESTING_RPM }}
done
- name: Rpmlint
run: |
for package in /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/rpm/SPECS/RPMS/x86_64/*.rpm
do rpmlint --ignore-unused-rpmlintrc --rpmlintrc \
/home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/rpm/SPECS/SOURCES/${{ env.PRODUCT_LOW }}.rpmlintrc $package \
| tee -a file2
done
if grep -qE '^(W:|E:)' file2; then echo \
"::warning NoticeRpm=rpmLint::$(cat file2 | awk '/W:/ { ws += 1 } /E:/ { es += 1 } END { print "Warnings:", ws, "Errors:", es }')" ; fi

View File

@ -552,14 +552,14 @@ install_docker_compose () {
install_service python3 install_service python3
fi fi
if command_exists apt-get; then py3_version=$(python3 -c 'import sys; print(sys.version_info.minor)')
apt-get -y update -qq if [[ $py3_version -lt 6 ]]; then
apt-get -y -q install python3-pip curl -O https://bootstrap.pypa.io/pip/3.$py3_version/get-pip.py
elif command_exists yum; then else
curl -O https://bootstrap.pypa.io/get-pip.py curl -O https://bootstrap.pypa.io/get-pip.py
python3 get-pip.py || true fi
rm get-pip.py python3 get-pip.py
fi rm get-pip.py
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
python3 -m pip install docker-compose python3 -m pip install docker-compose
@ -571,21 +571,6 @@ install_docker_compose () {
fi fi
} }
install_jq () {
if command_exists apt-get; then
apt-get -y update
apt-get -y -q install jq
elif command_exists yum; then
rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-$REV.noarch.rpm || true
yum -y install jq
fi
if ! command_exists jq; then
echo "command jq not found"
exit 1;
fi
}
check_ports () { check_ports () {
RESERVED_PORTS=(3306 8092); RESERVED_PORTS=(3306 8092);
ARRAY_PORTS=(); ARRAY_PORTS=();
@ -779,10 +764,6 @@ get_available_version () {
install_curl; install_curl;
fi fi
if ! command_exists jq ; then
install_jq
fi
CREDENTIALS=""; CREDENTIALS="";
AUTH_HEADER=""; AUTH_HEADER="";
TAGS_RESP=""; TAGS_RESP="";
@ -953,6 +934,13 @@ download_files () {
install_service svn subversion install_service svn subversion
fi fi
if ! command_exists jq ; then
if command_exists yum; then
rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-$REV.noarch.rpm
fi
install_service jq
fi
svn export --force https://github.com/${PACKAGE_SYSNAME}/${PRODUCT}/branches/${GIT_BRANCH}/build/install/docker/ ${BASE_DIR} svn export --force https://github.com/${PACKAGE_SYSNAME}/${PRODUCT}/branches/${GIT_BRANCH}/build/install/docker/ ${BASE_DIR}
reconfigure STATUS ${STATUS} reconfigure STATUS ${STATUS}
@ -965,8 +953,8 @@ download_files () {
} }
reconfigure () { reconfigure () {
local VARIABLE_NAME=$1 local VARIABLE_NAME="$1"
local VARIABLE_VALUE=$2 local VARIABLE_VALUE="$2"
if [[ -n ${VARIABLE_VALUE} ]]; then if [[ -n ${VARIABLE_VALUE} ]]; then
sed -i "s~${VARIABLE_NAME}=.*~${VARIABLE_NAME}=${VARIABLE_VALUE}~g" $BASE_DIR/.env sed -i "s~${VARIABLE_NAME}=.*~${VARIABLE_NAME}=${VARIABLE_VALUE}~g" $BASE_DIR/.env

View File

@ -7,7 +7,7 @@ privacy-breach-generic var/www/{{product}}/services/*/node_modules/*
# Ignoring node_modules errors due to lack of ability to influence them # Ignoring node_modules errors due to lack of ability to influence them
script-not-executable var/www/{{product}}/services/*/node_modules/* script-not-executable var/www/{{product}}/services/*/node_modules/*
# Ignoring node_modules errors due to lack of ability to influence them # Ignoring node_modules errors due to lack of ability to influence them
unusual-interpreter var/www/{{product}}/services/*/node_modules/* unusual-interpreter */node_modules/*
# The use of the /var/www directory is caused by its past history as the default document root # The use of the /var/www directory is caused by its past history as the default document root
dir-or-file-in-var-www dir-or-file-in-var-www
@ -44,3 +44,12 @@ untranslatable-debconf-templates
# We use this to protect sensitive information (ie passwords) in the config file # We use this to protect sensitive information (ie passwords) in the config file
non-standard-file-perm non-standard-file-perm
# There are instances where temporary or future code sections need to be retained for documentation or future development purposes
no-code-sections
# Ignoring errors due to lack of ability to influence them
library-not-linked-against-libc
# Some file triggers a privacy concern, specifically references an image files .png
privacy-breach-generic

View File

@ -7,9 +7,6 @@ addFilter(r' W: non-standard-(uid|gid)')
# This is necessary to ensure that all child packages are updated correctly # This is necessary to ensure that all child packages are updated correctly
addFilter(r'W: requires-on-release') addFilter(r'W: requires-on-release')
# The signature of packages occurs in further stages
addFilter(r'E: no-signature')
# The basic documentation comes with the common package # The basic documentation comes with the common package
addFilter(r'W: no-documentation') addFilter(r'W: no-documentation')
@ -45,3 +42,18 @@ addFilter(r'non-standard-executable-perm')
# We use this to protect sensitive information (ie passwords) in the config file # We use this to protect sensitive information (ie passwords) in the config file
addFilter(r'non-readable') addFilter(r'non-readable')
# No one license from allowed pull AGPLv3, AGPLv3+ worked
addFilter(r'W: invalid-license AGPLv3')
# Сertain services require write access to the log directory. These services are launched under a user account that is different from the root user.
addFilter(r'logrotate-user-writable-log-dir')
# The use of the /var/www directory is caused by its past history as the default document root
addFilter(r'W: non-standard-dir-in-var www')
# Shared libraries centos7-librdkafka.so, libgrpc_csharp_ext.x64.so location of which is hardcoded
addFilter(r'W: binary-or-shlib-calls-gethostbyname')
# There are the same files, however on a different languages
addFilter(r'files-duplicate')

View File

@ -1,4 +1,5 @@
%package backup %package backup
Packager: %{packager}
Summary: Backup Summary: Backup
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -8,6 +9,7 @@ AutoReqProv: no
Backup Backup
%package common %package common
Packager: %{packager}
Summary: Common Summary: Common
Group: Applications/Internet Group: Applications/Internet
Requires: logrotate Requires: logrotate
@ -15,6 +17,7 @@ Requires: logrotate
Common Common
%package files-services %package files-services
Packager: %{packager}
Summary: Files-services Summary: Files-services
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -25,6 +28,7 @@ AutoReqProv: no
Files-services Files-services
%package notify %package notify
Packager: %{packager}
Summary: Notify Summary: Notify
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -34,6 +38,7 @@ AutoReqProv: no
Notify Notify
%package files %package files
Packager: %{packager}
Summary: Files Summary: Files
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -43,6 +48,7 @@ AutoReqProv: no
Files Files
%package proxy %package proxy
Packager: %{packager}
Summary: Proxy Summary: Proxy
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -53,6 +59,7 @@ AutoReqProv: no
Proxy Proxy
%package studio-notify %package studio-notify
Packager: %{packager}
Summary: Studio-notify Summary: Studio-notify
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -62,6 +69,7 @@ AutoReqProv: no
Studio-notify Studio-notify
%package people-server %package people-server
Packager: %{packager}
Summary: People-server Summary: People-server
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -71,6 +79,7 @@ AutoReqProv: no
People-server People-server
%package socket %package socket
Packager: %{packager}
Summary: Socket Summary: Socket
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -80,6 +89,7 @@ AutoReqProv: no
Socket Socket
%package studio %package studio
Packager: %{packager}
Summary: Studio Summary: Studio
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -89,6 +99,7 @@ AutoReqProv: no
Studio Studio
%package api %package api
Packager: %{packager}
Summary: Api Summary: Api
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -98,6 +109,7 @@ AutoReqProv: no
Api Api
%package api-system %package api-system
Packager: %{packager}
Summary: Api-system Summary: Api-system
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -107,6 +119,7 @@ AutoReqProv: no
Api-system Api-system
%package ssoauth %package ssoauth
Packager: %{packager}
Summary: Ssoauth Summary: Ssoauth
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -116,6 +129,7 @@ AutoReqProv: no
Ssoauth Ssoauth
%package clear-events %package clear-events
Packager: %{packager}
Summary: Clear-events Summary: Clear-events
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -125,6 +139,7 @@ AutoReqProv: no
Clear-events Clear-events
%package backup-background %package backup-background
Packager: %{packager}
Summary: Backup-background Summary: Backup-background
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -134,6 +149,7 @@ AutoReqProv: no
Backup-background Backup-background
%package radicale %package radicale
Packager: %{packager}
Summary: Radicale Summary: Radicale
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -143,6 +159,7 @@ AutoReqProv: no
Radicale Radicale
%package doceditor %package doceditor
Packager: %{packager}
Summary: Doceditor Summary: Doceditor
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -152,6 +169,7 @@ AutoReqProv: no
Doceditor Doceditor
%package migration-runner %package migration-runner
Packager: %{packager}
Summary: Migration-runner Summary: Migration-runner
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -161,6 +179,7 @@ AutoReqProv: no
Migration-runner Migration-runner
%package login %package login
Packager: %{packager}
Summary: Login Summary: Login
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release
@ -170,6 +189,7 @@ AutoReqProv: no
Login Login
%package healthchecks %package healthchecks
Packager: %{packager}
Summary: Healthchecks Summary: Healthchecks
Group: Applications/Internet Group: Applications/Internet
Requires: %name-common = %version-%release Requires: %name-common = %version-%release

View File

@ -14,6 +14,7 @@ AutoReqProv: no
URL: http://onlyoffice.com URL: http://onlyoffice.com
Vendor: Ascensio System SIA Vendor: Ascensio System SIA
Packager: %{packager}
License: AGPLv3 License: AGPLv3
Source0: https://github.com/ONLYOFFICE/%{product}/archive/%GIT_BRANCH.tar.gz#/%{sourcename}.tar.gz Source0: https://github.com/ONLYOFFICE/%{product}/archive/%GIT_BRANCH.tar.gz#/%{sourcename}.tar.gz

View File

@ -434,7 +434,15 @@ public class DiscDataStore : BaseStorage
} }
var entries = Directory.GetFiles(targetDir, "*.*", SearchOption.AllDirectories); var entries = Directory.GetFiles(targetDir, "*.*", SearchOption.AllDirectories);
var size = entries.Select(entry => _crypt.GetFileSize(entry)).Sum(); var size = entries.Where(r =>
{
if (QuotaController == null || string.IsNullOrEmpty(QuotaController.ExcludePattern))
{
return true;
}
return !Path.GetFileName(r).StartsWith(QuotaController.ExcludePattern);
}
).Select(_crypt.GetFileSize).Sum();
var subDirs = Directory.GetDirectories(targetDir, "*", SearchOption.AllDirectories).ToList(); var subDirs = Directory.GetDirectories(targetDir, "*", SearchOption.AllDirectories).ToList();
subDirs.Reverse(); subDirs.Reverse();

View File

@ -457,7 +457,15 @@ public class GoogleCloudStorage : BaseStorage
await foreach (var obj in objToDel) await foreach (var obj in objToDel)
{ {
await storage.DeleteObjectAsync(_bucket, obj.Name); await storage.DeleteObjectAsync(_bucket, obj.Name);
await QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
if (QuotaController != null)
{
if (string.IsNullOrEmpty(QuotaController.ExcludePattern) ||
!Path.GetFileName(obj.Name).StartsWith(QuotaController.ExcludePattern))
{
await QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
}
}
} }
} }

View File

@ -36,4 +36,6 @@ public interface IQuotaController
void QuotaUsedSet(string module, string domain, string dataTag, long size); void QuotaUsedSet(string module, string domain, string dataTag, long size);
void QuotaUsedCheck(long size); void QuotaUsedCheck(long size);
string ExcludePattern { get; set; }
} }

View File

@ -189,10 +189,10 @@ public class RackspaceCloudStorage : BaseStorage
return SaveAsync(domain, path, stream, string.Empty, string.Empty, ACL.Auto, contentEncoding, cacheDays); return SaveAsync(domain, path, stream, string.Empty, string.Empty, ACL.Auto, contentEncoding, cacheDays);
} }
private bool EnableQuotaCheck(string domain) private bool EnableQuotaCheck(string domain)
{ {
return (QuotaController != null) && !domain.EndsWith("_temp"); return (QuotaController != null) && !domain.EndsWith("_temp");
} }
public async Task<Uri> SaveAsync(string domain, string path, Stream stream, string contentType, public async Task<Uri> SaveAsync(string domain, string path, Stream stream, string contentType,
string contentDisposition, ACL acl, string contentEncoding = null, int cacheDays = 5, string contentDisposition, ACL acl, string contentEncoding = null, int cacheDays = 5,
@ -200,7 +200,7 @@ public class RackspaceCloudStorage : BaseStorage
{ {
var buffered = _tempStream.GetBuffered(stream); var buffered = _tempStream.GetBuffered(stream);
if (EnableQuotaCheck(domain)) if (EnableQuotaCheck(domain))
{ {
QuotaController.QuotaUsedCheck(buffered.Length); QuotaController.QuotaUsedCheck(buffered.Length);
} }
@ -482,7 +482,15 @@ public class RackspaceCloudStorage : BaseStorage
foreach (var obj in objToDel) foreach (var obj in objToDel)
{ {
client.DeleteObject(_private_container, obj.Name); client.DeleteObject(_private_container, obj.Name);
await QuotaUsedDelete(domain, obj.Bytes);
if (QuotaController != null)
{
if (string.IsNullOrEmpty(QuotaController.ExcludePattern) ||
!Path.GetFileName(obj.Name).StartsWith(QuotaController.ExcludePattern))
{
await QuotaUsedDelete(domain, obj.Bytes);
}
}
} }
} }
@ -755,7 +763,7 @@ public class RackspaceCloudStorage : BaseStorage
var obj = client.ListObjects(_private_container, null, null, null, prefix, _region).FirstOrDefault(); var obj = client.ListObjects(_private_container, null, null, null, prefix, _region).FirstOrDefault();
var lastModificationDate = obj == null ? throw new FileNotFoundException("File not found" + prefix) : obj.LastModified.UtcDateTime; var lastModificationDate = obj == null ? throw new FileNotFoundException("File not found" + prefix) : obj.LastModified.UtcDateTime;
var etag = '"' + lastModificationDate.Ticks.ToString("X8", CultureInfo.InvariantCulture) + '"'; var etag = '"' + lastModificationDate.Ticks.ToString("X8", CultureInfo.InvariantCulture) + '"';

View File

@ -495,7 +495,14 @@ public class S3Storage : BaseStorage
await client.DeleteObjectAsync(deleteRequest); await client.DeleteObjectAsync(deleteRequest);
await QuotaUsedDelete(domain, s3Object.Size); if (QuotaController != null)
{
if (string.IsNullOrEmpty(QuotaController.ExcludePattern) ||
!Path.GetFileName(s3Object.Key).StartsWith(QuotaController.ExcludePattern))
{
await QuotaUsedDelete(domain, s3Object.Size);
}
}
} }
} }

View File

@ -50,6 +50,7 @@ public class TenantQuotaController : IQuotaController
private readonly TenantQuotaFeatureChecker<MaxTotalSizeFeature, long> _maxTotalSizeChecker; private readonly TenantQuotaFeatureChecker<MaxTotalSizeFeature, long> _maxTotalSizeChecker;
private Lazy<long> _lazyCurrentSize; private Lazy<long> _lazyCurrentSize;
private long _currentSize; private long _currentSize;
public string ExcludePattern { get; set; }
public TenantQuotaController(TenantManager tenantManager, AuthContext authContext, TenantQuotaFeatureChecker<MaxFileSizeFeature, long> maxFileSizeChecker, TenantQuotaFeatureChecker<MaxTotalSizeFeature, long> maxTotalSizeChecker) public TenantQuotaController(TenantManager tenantManager, AuthContext authContext, TenantQuotaFeatureChecker<MaxFileSizeFeature, long> maxFileSizeChecker, TenantQuotaFeatureChecker<MaxTotalSizeFeature, long> maxTotalSizeChecker)
{ {
@ -58,13 +59,14 @@ public class TenantQuotaController : IQuotaController
_maxTotalSizeChecker = maxTotalSizeChecker; _maxTotalSizeChecker = maxTotalSizeChecker;
_authContext = authContext; _authContext = authContext;
} }
public void Init(int tenant) public void Init(int tenant, string excludePattern = null)
{ {
_tenant = tenant; _tenant = tenant;
_lazyCurrentSize = new Lazy<long>(() => _tenantManager.FindTenantQuotaRows(tenant) _lazyCurrentSize = new Lazy<long>(() => _tenantManager.FindTenantQuotaRows(tenant)
.Where(r => UsedInQuota(r.Tag)) .Where(r => UsedInQuota(r.Tag))
.Sum(r => r.Counter)); .Sum(r => r.Counter));
ExcludePattern = excludePattern;
} }
public void QuotaUsedAdd(string module, string domain, string dataTag, long size, bool quotaCheckFileSize = true) public void QuotaUsedAdd(string module, string domain, string dataTag, long size, bool quotaCheckFileSize = true)

View File

@ -394,6 +394,15 @@ public class FactoryIndexer<T> : IFactoryIndexer where T : class, ISearchItem
Logger.ErrorUpdate(e); Logger.ErrorUpdate(e);
} }
} }
public Task<bool> UpdateAsync(T data, UpdateAction action, Expression<Func<T, IList>> field, bool immediately = true)
{
var t = _serviceProvider.GetService<T>();
return !Support(t)
? Task.FromResult(false)
: Queue(() => _indexer.Update(data, action, field, immediately));
}
public void Update(T data, Expression<Func<Selector<T>, Selector<T>>> expression, bool immediately = true, params Expression<Func<T, object>>[] fields) public void Update(T data, Expression<Func<Selector<T>, Selector<T>>> expression, bool immediately = true, params Expression<Func<T, object>>[] fields)
{ {

View File

@ -47,7 +47,7 @@
"oidc": { "oidc": {
"authority": "" "authority": ""
}, },
"server-root": "" "server-root": ""
}, },
"license": { "license": {
"file": { "file": {
@ -296,7 +296,8 @@
"book-training-email": "training@onlyoffice.com", "book-training-email": "training@onlyoffice.com",
"documentation-email": "documentation@onlyoffice.com", "documentation-email": "documentation@onlyoffice.com",
"max-upload-size": 5242880, "max-upload-size": 5242880,
"zendesk-key": "" "zendesk-key": "",
"samesite": ""
}, },
"ConnectionStrings": { "ConnectionStrings": {
"default": { "default": {
@ -342,7 +343,8 @@
"storageBucket": "", "storageBucket": "",
"messagingSenderId": "", "messagingSenderId": "",
"appId": "", "appId": "",
"measurementId": "" "measurementId": "",
"databaseURL": ""
}, },
"debug-info": { "debug-info": {
"enabled": "true" "enabled": "true"

View File

@ -1,6 +1,6 @@
{ {
"name": "docspace", "name": "docspace",
"version": "1.0.1", "version": "1.1.0",
"private": true, "private": true,
"workspaces": { "workspaces": {
"packages": [ "packages": [

View File

@ -1,6 +1,6 @@
{ {
"name": "@docspace/client", "name": "@docspace/client",
"version": "1.0.1", "version": "1.1.0",
"private": true, "private": true,
"homepage": "", "homepage": "",
"scripts": { "scripts": {

View File

@ -487,6 +487,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
"/files/trash/filter", "/files/trash/filter",
"/accounts", "/accounts",
"/accounts/changeOwner",
"/accounts/filter", "/accounts/filter",
"/accounts/create/:type", "/accounts/create/:type",

View File

@ -0,0 +1,165 @@
import FileReactSvgUrl from "PUBLIC_DIR/images/icons/24/file.svg?url";
import DownloadReactSvgUrl from "PUBLIC_DIR/images/download.react.svg?url";
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import { isMobileOnly } from "react-device-detect";
import ModalDialog from "@docspace/components/modal-dialog";
import Text from "@docspace/components/text";
import Button from "@docspace/components/button";
import Textarea from "@docspace/components/textarea";
import IconButton from "@docspace/components/icon-button";
import toastr from "@docspace/components/toast/toastr";
import {
getCrashReport,
downloadJson,
getCurrentDate,
} from "SRC_DIR/helpers/crashReport";
const ModalDialogContainer = styled(ModalDialog)`
#modal-dialog {
width: auto;
max-width: 520px;
max-height: 560px;
}
.report-description {
margin-bottom: 16px;
}
.report-wrapper {
margin-top: 8px;
height: 48px;
display: flex;
gap: 16px;
align-items: center;
.report-filename {
display: flex;
}
.file-icon {
width: 24px;
user-select: none;
}
.icon-button {
cursor: pointer;
}
}
`;
const ReportDialog = (props) => {
const { t, ready } = useTranslation(["Common"]);
const { visible, onClose, error, user, version, FirebaseHelper } = props;
const [report, setReport] = useState({});
const [description, setDescription] = useState("");
useEffect(() => {
const report = getCrashReport(user.id, version, user.cultureName, error);
setReport(report);
console.log(report);
}, []);
const onChangeTextareaValue = (e) => {
setDescription(e.target.value);
};
const onClickDownload = () => {
downloadJson(report, fileTitle);
};
const onClickSend = async () => {
try {
const reportWithDescription = Object.assign(report, {
description: description,
});
await FirebaseHelper.sendCrashReport(reportWithDescription);
toastr.success(t("ErrorReportSuccess"));
onCloseAction();
} catch (e) {
console.error(e);
toastr.error(e);
}
};
const onCloseAction = () => {
setDescription("");
onClose();
};
const fileTitle = t("ErrorReport") + " " + getCurrentDate();
return (
<ModalDialogContainer
isLoading={!ready}
visible={visible}
onClose={onCloseAction}
displayType="modal"
>
<ModalDialog.Header>{t("ErrorReport")}</ModalDialog.Header>
<ModalDialog.Body>
<Text className="report-description" noSelect>
{t("ErrorReportDescription")}
</Text>
<Textarea
placeholder={t("RecoverDescribeYourProblemPlaceholder")}
value={description}
onChange={onChangeTextareaValue}
autoFocus
areaSelect
heightTextArea={72}
fontSize={13}
/>
<div className="report-wrapper">
<img src={FileReactSvgUrl} className="file-icon" />
<Text as="div" fontWeight={600} noSelect className="report-filename">
{fileTitle}
<Text fontWeight={600} noSelect color="#A3A9AE">
.json
</Text>
</Text>
<IconButton
className="icon-button"
iconName={DownloadReactSvgUrl}
size="16"
isfill={true}
onClick={onClickDownload}
/>
</div>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendButton"
label={t("SendButton")}
size="normal"
primary
scale={isMobileOnly}
onClick={onClickSend}
/>
<Button
key="CancelButton"
label={t("CancelButton")}
size="normal"
scale={isMobileOnly}
onClick={onCloseAction}
/>
</ModalDialog.Footer>
</ModalDialogContainer>
);
};
export default inject(({ auth }) => {
const { user } = auth.userStore;
const { firebaseHelper } = auth.settingsStore;
return {
user,
version: auth.version,
FirebaseHelper: firebaseHelper,
};
})(observer(ReportDialog));

View File

@ -29,6 +29,7 @@ import LogoutConnectionDialog from "./LogoutConnectionDialog";
import LogoutAllConnectionDialog from "./LogoutAllConnectionDialog"; import LogoutAllConnectionDialog from "./LogoutAllConnectionDialog";
import CreateRoomConfirmDialog from "./CreateRoomConfirmDialog"; import CreateRoomConfirmDialog from "./CreateRoomConfirmDialog";
import PortalRenamingDialog from "./PortalRenamingDialog"; import PortalRenamingDialog from "./PortalRenamingDialog";
import ReportDialog from "./ReportDialog";
export { export {
EmptyTrashDialog, EmptyTrashDialog,
@ -62,4 +63,5 @@ export {
InviteUsersWarningDialog, InviteUsersWarningDialog,
LogoutAllConnectionDialog, LogoutAllConnectionDialog,
PortalRenamingDialog, PortalRenamingDialog,
ReportDialog,
}; };

View File

@ -0,0 +1,37 @@
import saveAs from "file-saver";
export const getCrashReport = (userId, version, language, error) => {
const currentTime = new Date();
const reportTime = currentTime.toTimeString();
const lsObject = JSON.stringify(window.localStorage) || "";
const report = {
url: window.origin,
userId: userId,
version: version,
platform: navigator?.platform,
userAgent: navigator?.userAgent,
language: language || "en",
errorMessage: error?.message,
errorStack: error?.stack,
localStorage: lsObject,
reportTime: reportTime,
};
return report;
};
export const downloadJson = (json, fileName) => {
const cleanJson = JSON.stringify(json);
const data = new Blob([cleanJson], { type: "application/json" });
const url = window.URL.createObjectURL(data);
saveAs(url, `${fileName}.json`);
};
export const getCurrentDate = () => {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const day = now.getDate();
return `${day}.${month}.${year}`;
};

View File

@ -26,10 +26,16 @@ const PeopleSection = React.memo(() => {
/> />
<PrivateRoute <PrivateRoute
exact exact
withManager withManager
path={["/accounts"]} path={["/accounts"]}
component={HomeRedirectToFilter} component={HomeRedirectToFilter}
/> />
<PrivateRoute
exact
withManager
path={["/accounts/changeOwner"]}
component={RedirectWithChangeOwnerDialog}
/>
<PrivateRoute <PrivateRoute
path={"/accounts/filter"} path={"/accounts/filter"}
withManager withManager
@ -55,6 +61,20 @@ const HomeRedirectToFilter = (props) => {
return <Redirect to={`/accounts/filter?${urlFilter}`} />; return <Redirect to={`/accounts/filter?${urlFilter}`} />;
}; };
const RedirectWithChangeOwnerDialog = (props) => {
const filter = Filter.getDefault();
const urlFilter = filter.toUrlParams();
return (
<Redirect
to={{
pathname: "/accounts/filter",
search: `?${urlFilter}`,
state: { openChangeOwnerDialog: true },
}}
/>
);
};
const PeopleContent = (props) => { const PeopleContent = (props) => {
const { loadBaseInfo, isLoading, setFirstLoad } = props; const { loadBaseInfo, isLoading, setFirstLoad } = props;

View File

@ -44,6 +44,8 @@ const PureHome = ({
withPaging, withPaging,
onClickBack, onClickBack,
setPortalTariff, setPortalTariff,
setChangeOwnerDialogVisible,
}) => { }) => {
const { location } = history; const { location } = history;
const { pathname } = location; const { pathname } = location;
@ -73,6 +75,9 @@ const PureHome = ({
setIsLoading(false); setIsLoading(false);
setIsRefresh(false); setIsRefresh(false);
}); });
if (location?.state?.openChangeOwnerDialog)
setChangeOwnerDialogVisible(true);
} }
}, [pathname, location, setSelectedNode]); }, [pathname, location, setSelectedNode]);
@ -140,24 +145,15 @@ export default inject(
const { settingsStore, currentTariffStatusStore } = auth; const { settingsStore, currentTariffStatusStore } = auth;
const { setPortalTariff } = currentTariffStatusStore; const { setPortalTariff } = currentTariffStatusStore;
const { showCatalog, withPaging } = settingsStore; const { showCatalog, withPaging } = settingsStore;
const { const { usersStore, selectedGroupStore, loadingStore, viewAs } =
usersStore, peopleStore;
selectedGroupStore,
loadingStore,
viewAs,
} = peopleStore;
const { getUsersList } = usersStore; const { getUsersList } = usersStore;
const { selectedGroup } = selectedGroupStore; const { selectedGroup } = selectedGroupStore;
const { setSelectedNode } = treeFoldersStore; const { setSelectedNode } = treeFoldersStore;
const { onClickBack } = filesActionsStore; const { onClickBack } = filesActionsStore;
const { const { isLoading, setIsLoading, setIsRefresh, firstLoad, setFirstLoad } =
isLoading, loadingStore;
setIsLoading, const { setChangeOwnerDialogVisible } = peopleStore.dialogStore;
setIsRefresh,
firstLoad,
setFirstLoad,
} = loadingStore;
return { return {
setPortalTariff, setPortalTariff,
isAdmin: auth.isAdmin, isAdmin: auth.isAdmin,
@ -176,6 +172,7 @@ export default inject(
snackbarExist: auth.settingsStore.snackbarExist, snackbarExist: auth.settingsStore.snackbarExist,
withPaging, withPaging,
onClickBack, onClickBack,
setChangeOwnerDialogVisible,
}; };
} }
)(observer(withRouter(Home))); )(observer(withRouter(Home)));

View File

@ -1,23 +1,95 @@
import React from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import styled from "styled-components";
import ErrorContainer from "@docspace/common/components/ErrorContainer"; import { inject, observer } from "mobx-react";
import { I18nextProvider, useTranslation } from "react-i18next"; import { I18nextProvider, useTranslation } from "react-i18next";
import ErrorContainer from "@docspace/common/components/ErrorContainer";
import Link from "@docspace/components/link";
import { ZendeskAPI } from "@docspace/common/components/Zendesk";
import { ReportDialog } from "SRC_DIR/components/dialogs";
import DocspaceLogo from "SRC_DIR/DocspaceLogo";
import i18n from "./i18n"; import i18n from "./i18n";
const Error520 = ({ match }) => {
const StyledWrapper = styled.div`
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 16px;
.logo {
margin-bottom: 28px;
}
.link {
margin-top: 24px;
}
`;
const Error520 = ({ errorLog, currentColorScheme, FirebaseHelper }) => {
const { t } = useTranslation(["Common"]); const { t } = useTranslation(["Common"]);
const { error } = (match && match.params) || {};
const [reportDialogVisible, setReportDialogVisible] = useState(false);
const showDialog = () => {
setReportDialogVisible(true);
};
const closeDialog = () => {
setReportDialogVisible(false);
};
const onReloadClick = () => {
window.location.reload();
};
ZendeskAPI("webWidget", "show");
if (!FirebaseHelper.isEnabledDB)
return <ErrorContainer headerText={t("SomethingWentWrong")} />;
return ( return (
<ErrorContainer headerText={t("SomethingWentWrong")} bodyText={error} /> <StyledWrapper>
<DocspaceLogo className="logo" />
<ErrorContainer
isPrimaryButton={false}
headerText={t("SomethingWentWrong")}
customizedBodyText={t("SomethingWentWrongDescription")}
buttonText={t("SendReport")}
onClickButton={showDialog}
/>
<Link
className="link"
type="action"
isHovered
fontWeight={600}
onClick={onReloadClick}
color={currentColorScheme?.main?.accent}
>
{t("ReloadPage")}
</Link>
<ReportDialog
visible={reportDialogVisible}
onClose={closeDialog}
error={errorLog}
/>
</StyledWrapper>
); );
}; };
Error520.propTypes = { const Error520Wrapper = inject(({ auth }) => {
match: PropTypes.object, const { currentColorScheme, firebaseHelper } = auth.settingsStore;
};
export default () => ( return {
currentColorScheme,
FirebaseHelper: firebaseHelper,
};
})(observer(Error520));
export default (props) => (
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>
<Error520 /> <Error520Wrapper {...props} />
</I18nextProvider> </I18nextProvider>
); );

View File

@ -132,7 +132,12 @@ const FilesSection = React.memo(({ withMainButton }) => {
exact exact
restricted restricted
withManager withManager
path={["/accounts", "/accounts/filter", "/accounts/create/:type"]} path={[
"/accounts",
"/accounts/changeOwner",
"/accounts/filter",
"/accounts/create/:type",
]}
component={Accounts} component={Accounts}
/> />

View File

@ -90,11 +90,11 @@ const PersonalSettings = ({
hideAdminSettings={!showAdminSettings} hideAdminSettings={!showAdminSettings}
> >
<Box className="settings-section"> <Box className="settings-section">
{showTitle && ( {/* {showTitle && (
<Heading className="heading" level={2} size="xsmall"> <Heading className="heading" level={2} size="xsmall">
{t("Common:Common")} {t("Common:Common")}
</Heading> </Heading>
)} )} */}
<ToggleButton <ToggleButton
className="toggle-btn" className="toggle-btn"
label={thumbnailsSizeLabel} label={thumbnailsSizeLabel}
@ -124,6 +124,14 @@ const PersonalSettings = ({
isChecked={confirmDelete} isChecked={confirmDelete}
/> />
)} )}
{!isVisitor && (
<ToggleButton
className="toggle-btn"
label={t("UpdateOrCreate")}
onChange={onChangeUpdateIfExist}
isChecked={updateIfExist}
/>
)}
</Box> </Box>
{/* <Box className="settings-section"> {/* <Box className="settings-section">
@ -154,7 +162,7 @@ const PersonalSettings = ({
/> />
</Box> */} </Box> */}
{!isVisitor && ( {/* {!isVisitor && (
<Box className="settings-section"> <Box className="settings-section">
<Heading className="heading" level={2} size="xsmall"> <Heading className="heading" level={2} size="xsmall">
{t("StoringFileVersion")} {t("StoringFileVersion")}
@ -176,7 +184,7 @@ const PersonalSettings = ({
/> />
)} )}
</Box> </Box>
)} )} */}
</StyledSettings> </StyledSettings>
); );
}; };

View File

@ -63,25 +63,25 @@ const SectionBodyContent = ({ isErrorSettings, history, user }) => {
[setting, history] [setting, history]
); );
const showAdminSettings = user.isAdmin || user.isOwner; //const showAdminSettings = user.isAdmin || user.isOwner;
return isErrorSettings ? ( return isErrorSettings ? (
<Error520 /> <Error520 />
) : ( ) : (
<StyledContainer> <StyledContainer>
{!showAdminSettings ? ( {/* {!showAdminSettings ? ( */}
<PersonalSettings <PersonalSettings
t={t} t={t}
showTitle={true} showTitle={true}
showAdminSettings={showAdminSettings} showAdminSettings={false} //showAdminSettings
/> />
) : ( {/* ) : (
<Submenu <Submenu
data={data} data={data}
startSelect={setting === "common" ? commonSettings : adminSettings} startSelect={setting === "common" ? commonSettings : adminSettings}
onSelect={onSelect} onSelect={onSelect}
/> />
)} )} */}
</StyledContainer> </StyledContainer>
); );
}; };

View File

@ -261,6 +261,10 @@ const Wizard = (props) => {
} }
}; };
const onClickRetry = () => {
window.location.href = "/";
};
if (!isWizardLoaded) if (!isWizardLoaded)
return <Loader className="pageLoader" type="rombs" size="40px" />; return <Loader className="pageLoader" type="rombs" size="40px" />;
@ -270,7 +274,7 @@ const Wizard = (props) => {
headerText={t("Common:SomethingWentWrong")} headerText={t("Common:SomethingWentWrong")}
bodyText={t("ErrorInitWizard")} bodyText={t("ErrorInitWizard")}
buttonText={t("ErrorInitWizardButton")} buttonText={t("ErrorInitWizardButton")}
buttonUrl="/" onClickButton={onClickRetry}
/> />
); );

View File

@ -2163,6 +2163,8 @@ class FilesActionStore {
id = urlFilter.folder; id = urlFilter.folder;
} }
if (id === undefined) return;
setIsLoading(true); setIsLoading(true);
fetchFiles(id, null, true, false).finally(() => setIsLoading(false)); fetchFiles(id, null, true, false).finally(() => setIsLoading(false));

View File

@ -5,13 +5,13 @@ import Error520 from "client/Error520";
class ErrorBoundary extends React.Component { class ErrorBoundary extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { hasError: false }; this.state = { error: null };
} }
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
static getDerivedStateFromError(error) { static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI. // Update state so the next render will show the fallback UI.
return { hasError: true }; return { error: error ?? "Unhandled exception" };
} }
componentDidCatch(error, errorInfo) { componentDidCatch(error, errorInfo) {
@ -21,9 +21,9 @@ class ErrorBoundary extends React.Component {
} }
render() { render() {
if (this.state.hasError) { if (this.state.error) {
// You can render any custom fallback UI // You can render any custom fallback UI
return <Error520 />; return <Error520 errorLog={this.state.error} />;
} }
return this.props.children; return this.props.children;

View File

@ -12,9 +12,10 @@ const ErrorContainer = (props) => {
headerText, headerText,
bodyText, bodyText,
buttonText, buttonText,
buttonUrl, onClickButton,
children, children,
customizedBodyText, customizedBodyText,
isPrimaryButton,
...rest ...rest
} = props; } = props;
@ -354,16 +355,16 @@ const ErrorContainer = (props) => {
</Text> </Text>
)} )}
{buttonText && buttonUrl && ( {buttonText && onClickButton && (
<div id="button-container"> <div id="button-container">
<Button <Button
theme={rest?.theme} theme={rest?.theme}
id="button" id="button"
size="normal" size="normal"
scale scale
primary primary={isPrimaryButton}
label={buttonText} label={buttonText}
onClick={() => (window.location.href = buttonUrl)} onClick={onClickButton}
/> />
</div> </div>
)} )}
@ -372,11 +373,16 @@ const ErrorContainer = (props) => {
); );
}; };
ErrorContainer.defaultProps = {
isPrimaryButton: true,
};
ErrorContainer.propTypes = { ErrorContainer.propTypes = {
headerText: PropTypes.string, headerText: PropTypes.string,
bodyText: PropTypes.string, bodyText: PropTypes.string,
isPrimaryButton: PropTypes.bool,
buttonText: PropTypes.string, buttonText: PropTypes.string,
buttonUrl: PropTypes.string, onClickButton: PropTypes.func,
children: PropTypes.any, children: PropTypes.any,
}; };

View File

@ -16,6 +16,6 @@ storiesOf("Components| ErrorContainer", module)
"This page was removed, renamed or doesnt exist anymore." "This page was removed, renamed or doesnt exist anymore."
)} )}
buttonText={text("buttonText", "Return to homepage")} buttonText={text("buttonText", "Return to homepage")}
buttonUrl={text("buttonUrl", "/")} onClickButton={() => console.log("click")}
/> />
)); ));

View File

@ -15,7 +15,6 @@ describe("<ErrorContainer />", () => {
headerText="Some error has happened" headerText="Some error has happened"
bodyText="Try again later" bodyText="Try again later"
buttonText="Go back" buttonText="Go back"
buttonUrl="/page"
/> />
); );
@ -23,7 +22,6 @@ describe("<ErrorContainer />", () => {
expect(wrapper.prop("headerText")).toEqual("Some error has happened"); expect(wrapper.prop("headerText")).toEqual("Some error has happened");
expect(wrapper.prop("bodyText")).toEqual("Try again later"); expect(wrapper.prop("bodyText")).toEqual("Try again later");
expect(wrapper.prop("buttonText")).toEqual("Go back"); expect(wrapper.prop("buttonText")).toEqual("Go back");
expect(wrapper.prop("buttonUrl")).toEqual("/page");
}); });
it("accepts id", () => { it("accepts id", () => {

View File

@ -10,7 +10,7 @@ const StyledErrorContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin: 0; margin: 0 16px;
padding-top: 36px; padding-top: 36px;
border: 0; border: 0;
box-sizing: border-box; box-sizing: border-box;
@ -44,16 +44,14 @@ const StyledErrorContainer = styled.div`
} }
#button-container { #button-container {
margin: 0 0 auto 0; width: 320px;
margin-top: 24px;
} }
#button {
display: inline-block;
margin: 0 0 36px 0;
}
#customized-text { #customized-text {
color: ${(props) => props.theme.errorContainer.bodyText}; color: ${(props) => props.theme.errorContainer.bodyText};
} }
@media screen and (max-width: 960px) { @media screen and (max-width: 960px) {
body { body {
padding: 24px 24px 0 24px; padding: 24px 24px 0 24px;
@ -62,10 +60,6 @@ const StyledErrorContainer = styled.div`
#container { #container {
margin: 12px 0 48px 0; margin: 12px 0 48px 0;
} }
#button {
margin: 0 0 24px 0;
}
} }
@media screen and (max-width: 620px) { @media screen and (max-width: 620px) {
@ -88,13 +82,11 @@ const StyledErrorContainer = styled.div`
} }
#button-container { #button-container {
align-self: stretch; width: 100%;
margin: auto 0 0 0;
} }
#button { #button {
width: 100%; width: 100%;
margin: 0 0 18px 0;
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@docspace/common", "name": "@docspace/common",
"version": "1.0.1", "version": "1.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "echo 'skip it'", "build": "echo 'skip it'",

View File

@ -1,6 +1,7 @@
import firebase from "firebase/app"; import firebase from "firebase/app";
import "firebase/remote-config"; import "firebase/remote-config";
import "firebase/storage"; import "firebase/storage";
import "firebase/database";
import CampaignsCloudPngUrl from "PUBLIC_DIR/images/campaigns.cloud.png"; import CampaignsCloudPngUrl from "PUBLIC_DIR/images/campaigns.cloud.png";
import CampaignsDesktopPngUrl from "PUBLIC_DIR/images/campaigns.desktop.png"; import CampaignsDesktopPngUrl from "PUBLIC_DIR/images/campaigns.desktop.png";
@ -12,6 +13,7 @@ class FirebaseHelper {
remoteConfig = null; remoteConfig = null;
firebaseConfig = null; firebaseConfig = null;
firebaseStorage = null; firebaseStorage = null;
firebaseDB = null;
constructor(settings) { constructor(settings) {
this.firebaseConfig = settings; this.firebaseConfig = settings;
@ -28,6 +30,8 @@ class FirebaseHelper {
this.remoteConfig = firebase.remoteConfig(); this.remoteConfig = firebase.remoteConfig();
this.firebaseDB = firebase.database();
this.remoteConfig.settings = { this.remoteConfig.settings = {
fetchTimeoutMillis: 3600000, fetchTimeoutMillis: 3600000,
minimumFetchIntervalMillis: 3600000, minimumFetchIntervalMillis: 3600000,
@ -64,6 +68,10 @@ class FirebaseHelper {
); );
} }
get isEnabledDB() {
return this.isEnabled && this.config["databaseUrl"];
}
async checkMaintenance() { async checkMaintenance() {
if (!this.isEnabled) return Promise.reject("Not enabled"); if (!this.isEnabled) return Promise.reject("Not enabled");
@ -145,6 +153,16 @@ class FirebaseHelper {
const domain = this.config["authDomain"]; const domain = this.config["authDomain"];
return `https://${domain}/locales/${lng}/CampaignPersonal${banner}.json`; return `https://${domain}/locales/${lng}/CampaignPersonal${banner}.json`;
} }
async sendCrashReport(report) {
try {
const reportListRef = this.firebaseDB.ref("reports");
const neReportRef = reportListRef.push();
neReportRef.set(report);
} catch (error) {
return Promise.reject(error);
}
}
} }
export default FirebaseHelper; export default FirebaseHelper;

View File

@ -1,6 +1,6 @@
{ {
"name": "@docspace/components", "name": "@docspace/components",
"version": "1.0.1", "version": "1.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "echo 'skip it'", "build": "echo 'skip it'",

View File

@ -1,6 +1,6 @@
{ {
"name": "@docspace/editor", "name": "@docspace/editor",
"version": "1.0.1", "version": "1.1.0",
"private": true, "private": true,
"homepage": "/doceditor", "homepage": "/doceditor",
"scripts": { "scripts": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@docspace/login", "name": "@docspace/login",
"version": "1.0.1", "version": "1.1.0",
"private": true, "private": true,
"homepage": "/login", "homepage": "/login",
"scripts": { "scripts": {

View File

@ -46,6 +46,8 @@ internal class FileDao : AbstractDao, IFileDao<int>
private readonly IMapper _mapper; private readonly IMapper _mapper;
private readonly ThumbnailSettings _thumbnailSettings; private readonly ThumbnailSettings _thumbnailSettings;
private readonly IQuotaService _quotaService; private readonly IQuotaService _quotaService;
private readonly StorageFactory _storageFactory;
private readonly TenantQuotaController _tenantQuotaController;
public FileDao( public FileDao(
ILogger<FileDao> logger, ILogger<FileDao> logger,
@ -73,7 +75,9 @@ internal class FileDao : AbstractDao, IFileDao<int>
Settings settings, Settings settings,
IMapper mapper, IMapper mapper,
ThumbnailSettings thumbnailSettings, ThumbnailSettings thumbnailSettings,
IQuotaService quotaService) IQuotaService quotaService,
StorageFactory storageFactory,
TenantQuotaController tenantQuotaController)
: base( : base(
dbContextManager, dbContextManager,
userManager, userManager,
@ -102,6 +106,8 @@ internal class FileDao : AbstractDao, IFileDao<int>
_mapper = mapper; _mapper = mapper;
_thumbnailSettings = thumbnailSettings; _thumbnailSettings = thumbnailSettings;
_quotaService = quotaService; _quotaService = quotaService;
_storageFactory = storageFactory;
_tenantQuotaController = tenantQuotaController;
} }
public Task InvalidateCacheAsync(int fileId) public Task InvalidateCacheAsync(int fileId)
@ -559,7 +565,7 @@ internal class FileDao : AbstractDao, IFileDao<int>
} }
else else
{ {
if(uploadSession != null) if (uploadSession != null)
{ {
await _chunkedUploadSessionHolder.MoveAsync(uploadSession, GetUniqFilePath(file)); await _chunkedUploadSessionHolder.MoveAsync(uploadSession, GetUniqFilePath(file));
} }
@ -795,7 +801,10 @@ internal class FileDao : AbstractDao, IFileDao<int>
if (deleteFolder) if (deleteFolder)
{ {
await DeleteFolderAsync(fileId); var tenantId = _tenantManager.GetCurrentTenant().Id;
_tenantQuotaController.Init(tenantId, ThumbnailTitle);
var store = _storageFactory.GetStorage(tenantId, FileConstant.StorageModule, _tenantQuotaController);
await store.DeleteDirectoryAsync(GetUniqFileDirectory(fileId));
} }
if (toDeleteFile != null) if (toDeleteFile != null)
@ -919,7 +928,7 @@ internal class FileDao : AbstractDao, IFileDao<int>
.OrderByDescending(r => r.Level) .OrderByDescending(r => r.Level)
.ToListAsync(); .ToListAsync();
_factoryIndexer.Update(toUpdateFile, UpdateAction.Replace, w => w.Folders); _ = _factoryIndexer.UpdateAsync(toUpdateFile, UpdateAction.Replace, w => w.Folders);
} }
}); });
@ -1318,11 +1327,6 @@ internal class FileDao : AbstractDao, IFileDao<int>
} }
} }
private async Task DeleteFolderAsync(int fileId)
{
await _globalStore.GetStore().DeleteDirectoryAsync(GetUniqFileDirectory(fileId));
}
public Task<bool> IsExistOnStorageAsync(File<int> file) public Task<bool> IsExistOnStorageAsync(File<int> file)
{ {
return _globalStore.GetStore().IsFileAsync(string.Empty, GetUniqFilePath(file)); return _globalStore.GetStore().IsFileAsync(string.Empty, GetUniqFilePath(file));

View File

@ -104,6 +104,9 @@
"EnterName": "Enter name", "EnterName": "Enter name",
"Error": "Error", "Error": "Error",
"ErrorInternalServer": "Internal server error. Try again later.", "ErrorInternalServer": "Internal server error. Try again later.",
"ErrorReport": "Error report",
"ErrorReportDescription": "Open the report below to see what data is included. Error reports do not contain any personal data of the users. To help our team better understand the problem, describe it in the free form using the comment field",
"ErrorReportSuccess": "Error report was successfully sent",
"Exabyte": "EB", "Exabyte": "EB",
"FeedbackAndSupport": "Feedback & Support", "FeedbackAndSupport": "Feedback & Support",
"FillFormButton": "Fill in the form", "FillFormButton": "Fill in the form",
@ -198,6 +201,7 @@
"RecoverDescribeYourProblemPlaceholder": "Describe your problem", "RecoverDescribeYourProblemPlaceholder": "Describe your problem",
"RecoverTitle": "Access recovery", "RecoverTitle": "Access recovery",
"RegistrationEmail": "Your registration email address", "RegistrationEmail": "Your registration email address",
"ReloadPage": "Reload page",
"Rename": "Rename", "Rename": "Rename",
"RepeatInvitation": "Repeat invitation", "RepeatInvitation": "Repeat invitation",
"RequiredField": "Required field", "RequiredField": "Required field",
@ -220,8 +224,9 @@
"SelectDOCXFormat": "Select .DOCX file", "SelectDOCXFormat": "Select .DOCX file",
"SelectFile": "Select file", "SelectFile": "Select file",
"SendButton": "Send", "SendButton": "Send",
"Sending": "Sending...", "SendReport": "Send report",
"SendRequest": "Send request", "SendRequest": "Send request",
"Sending": "Sending...",
"Sessions": "Sessions", "Sessions": "Sessions",
"Settings": "Settings", "Settings": "Settings",
"SettingsDocSpace": "DocSpace Settings", "SettingsDocSpace": "DocSpace Settings",
@ -237,6 +242,7 @@
"Size": "Size", "Size": "Size",
"SizeImageLarge": "The image size is too large, please select another image.", "SizeImageLarge": "The image size is too large, please select another image.",
"SomethingWentWrong": "Something went wrong.", "SomethingWentWrong": "Something went wrong.",
"SomethingWentWrongDescription": "Click Send report to automatically generate a report and help us fix the error. None of your personal data will be used in the report",
"SortBy": "Sort by", "SortBy": "Sort by",
"SpacesInLocalPart": "Local part can't contain spaces", "SpacesInLocalPart": "Local part can't contain spaces",
"Standard": "Standard", "Standard": "Standard",

View File

@ -176,7 +176,8 @@ public class SettingsController : BaseSettingsController
StorageBucket = _configuration["firebase:storageBucket"] ?? "", StorageBucket = _configuration["firebase:storageBucket"] ?? "",
MessagingSenderId = _configuration["firebase:messagingSenderId"] ?? "", MessagingSenderId = _configuration["firebase:messagingSenderId"] ?? "",
AppId = _configuration["firebase:appId"] ?? "", AppId = _configuration["firebase:appId"] ?? "",
MeasurementId = _configuration["firebase:measurementId"] ?? "" MeasurementId = _configuration["firebase:measurementId"] ?? "",
DataBaseUrl = _configuration["firebase:databaseURL"] ?? ""
}; };
settings.HelpLink = _commonLinkUtility.GetHelpLink(_settingsManager, _additionalWhiteLabelSettingsHelper, true); settings.HelpLink = _commonLinkUtility.GetHelpLink(_settingsManager, _additionalWhiteLabelSettingsHelper, true);

View File

@ -35,5 +35,5 @@ public class FirebaseDto
public string MessagingSenderId { get; set; } public string MessagingSenderId { get; set; }
public string AppId { get; set; } public string AppId { get; set; }
public string MeasurementId { get; set; } public string MeasurementId { get; set; }
public string DataBaseUrl { get; set; }
} }

View File

@ -29,6 +29,7 @@ using ASC.Core.Data;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Constants = ASC.Core.Users.Constants; using Constants = ASC.Core.Users.Constants;
using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode;
namespace ASC.Web.Core; namespace ASC.Web.Core;
@ -52,6 +53,7 @@ public class CookiesManager
private readonly CoreBaseSettings _coreBaseSettings; private readonly CoreBaseSettings _coreBaseSettings;
private readonly DbLoginEventsManager _dbLoginEventsManager; private readonly DbLoginEventsManager _dbLoginEventsManager;
private readonly MessageService _messageService; private readonly MessageService _messageService;
private readonly SameSiteMode? _sameSiteMode;
public CookiesManager( public CookiesManager(
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
@ -61,7 +63,8 @@ public class CookiesManager
TenantManager tenantManager, TenantManager tenantManager,
CoreBaseSettings coreBaseSettings, CoreBaseSettings coreBaseSettings,
DbLoginEventsManager dbLoginEventsManager, DbLoginEventsManager dbLoginEventsManager,
MessageService messageService) MessageService messageService,
IConfiguration configuration)
{ {
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_userManager = userManager; _userManager = userManager;
@ -71,6 +74,11 @@ public class CookiesManager
_coreBaseSettings = coreBaseSettings; _coreBaseSettings = coreBaseSettings;
_dbLoginEventsManager = dbLoginEventsManager; _dbLoginEventsManager = dbLoginEventsManager;
_messageService = messageService; _messageService = messageService;
if (Enum.TryParse<SameSiteMode>(configuration["web:samesite"], out var sameSiteMode))
{
_sameSiteMode = sameSiteMode;
}
} }
public void SetCookies(CookiesType type, string value, bool session = false) public void SetCookies(CookiesType type, string value, bool session = false)
@ -89,10 +97,20 @@ public class CookiesManager
{ {
options.HttpOnly = true; options.HttpOnly = true;
if (_sameSiteMode.HasValue && _sameSiteMode.Value != SameSiteMode.None)
{
options.SameSite = _sameSiteMode.Value;
}
var urlRewriter = _httpContextAccessor.HttpContext.Request.Url(); var urlRewriter = _httpContextAccessor.HttpContext.Request.Url();
if (urlRewriter.Scheme == "https") if (urlRewriter.Scheme == "https")
{ {
options.Secure = true; options.Secure = true;
if (_sameSiteMode.HasValue && _sameSiteMode.Value == SameSiteMode.None)
{
options.SameSite = _sameSiteMode.Value;
}
} }
if (FromCors()) if (FromCors())

2187
yarn.lock

File diff suppressed because it is too large Load Diff