diff --git a/build/install/OneClickInstall/install-Debian/install-app.sh b/build/install/OneClickInstall/install-Debian/install-app.sh index fae2e47e03..8fc9a7d282 100644 --- a/build/install/OneClickInstall/install-Debian/install-app.sh +++ b/build/install/OneClickInstall/install-Debian/install-app.sh @@ -31,14 +31,11 @@ if [ "$DOCUMENT_SERVER_INSTALLED" = "false" ]; then echo ${package_sysname}-documentserver $DS_COMMON_NAME/ds-port select $DS_PORT | sudo debconf-set-selections echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-pwd select $DS_DB_PWD | sudo debconf-set-selections - echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-user $DS_DB_USER | sudo debconf-set-selections - echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-name $DS_DB_NAME | sudo debconf-set-selections - echo ${package_sysname}-documentserver-de $DS_COMMON_NAME/jwt-enabled select ${DS_JWT_ENABLED} | sudo debconf-set-selections - echo ${package_sysname}-documentserver-de $DS_COMMON_NAME/jwt-secret select ${DS_JWT_SECRET} | sudo debconf-set-selections - echo ${package_sysname}-documentserver-de $DS_COMMON_NAME/jwt-header select ${DS_JWT_HEADER} | sudo debconf-set-selections - echo ${package_sysname}-documentserver-ee $DS_COMMON_NAME/jwt-enabled select ${DS_JWT_ENABLED} | sudo debconf-set-selections - echo ${package_sysname}-documentserver-ee $DS_COMMON_NAME/jwt-secret select ${DS_JWT_SECRET} | sudo debconf-set-selections - echo ${package_sysname}-documentserver-ee $DS_COMMON_NAME/jwt-header select ${DS_JWT_HEADER} | sudo debconf-set-selections + echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-user select $DS_DB_USER | sudo debconf-set-selections + echo ${package_sysname}-documentserver $DS_COMMON_NAME/db-name select $DS_DB_NAME | sudo debconf-set-selections + echo ${package_sysname}-documentserver $DS_COMMON_NAME/jwt-enabled select ${DS_JWT_ENABLED} | sudo debconf-set-selections + echo ${package_sysname}-documentserver $DS_COMMON_NAME/jwt-secret select ${DS_JWT_SECRET} | sudo debconf-set-selections + echo ${package_sysname}-documentserver $DS_COMMON_NAME/jwt-header select ${DS_JWT_HEADER} | sudo debconf-set-selections apt-get install -yq ${package_sysname}-documentserver elif [ "$UPDATE" = "true" ] && [ "$DOCUMENT_SERVER_INSTALLED" = "true" ]; then @@ -65,55 +62,17 @@ else systemctl reload nginx fi -APPSERVER_INSTALLED_VERSION=$(apt-cache policy ${product} | awk 'NR==2{print $2}') -APPSERVER_LATEST_VERSION=$(apt-cache policy ${product} | awk 'NR==3{print $2}') -if [ "$APPSERVER_INSTALLED_VERSION" != "$APPSERVER_LATEST_VERSION" ]; then - APPSERVER_NEED_UPDATE="true" -fi - if [ "$APPSERVER_INSTALLED" = "false" ]; then + echo ${product} ${product}/db-pwd select $MYSQL_SERVER_PASS | sudo debconf-set-selections + echo ${product} ${product}/db-user select $MYSQL_SERVER_USER | sudo debconf-set-selections + echo ${product} ${product}/db-name select $MYSQL_SERVER_DB_NAME | sudo debconf-set-selections + apt-get install -y ${product} || true #Fix error 'Failed to fetch' apt-get install -y ${product} -elif [ "$APPSERVER_NEED_UPDATE" = "true" ]; then - ENVIRONMENT="$(cat /lib/systemd/system/${product}-api.service | grep -oP 'ENVIRONMENT=\K.*')" - USER_CONNECTIONSTRING=$(json -f /etc/onlyoffice/${product}/appsettings.$ENVIRONMENT.json ConnectionStrings.default.connectionString) - MYSQL_SERVER_HOST=$(echo $USER_CONNECTIONSTRING | grep -oP 'Server=\K.*' | grep -o '^[^;]*') - MYSQL_SERVER_DB_NAME=$(echo $USER_CONNECTIONSTRING | grep -oP 'Database=\K.*' | grep -o '^[^;]*') - MYSQL_SERVER_USER=$(echo $USER_CONNECTIONSTRING | grep -oP 'User ID=\K.*' | grep -o '^[^;]*') - MYSQL_SERVER_PORT=$(echo $USER_CONNECTIONSTRING | grep -oP 'Port=\K.*' | grep -o '^[^;]*') - MYSQL_SERVER_PASS=$(echo $USER_CONNECTIONSTRING | grep -oP 'Password=\K.*' | grep -o '^[^;]*') - +elif [ "$UPDATE" = "true" ] && [ "$APPSERVER_INSTALLED" = "true" ]; then apt-get install -o DPkg::options::="--force-confnew" -y --only-upgrade ${product} elasticsearch=${ELASTIC_VERSION} fi -if [ "${APPSERVER_INSTALLED}" = "false" ] || [ "${APPSERVER_NEED_UPDATE}" = "true" ]; then -expect << EOF - set timeout -1 - log_user 1 - - if { "${UPDATE}" == "true" } { - spawn ${product}-configuration.sh -e ${ENVIRONMENT} - } else { - spawn ${product}-configuration.sh - } - - expect -re "Database host:" - send "\025$MYSQL_SERVER_HOST\r" - - expect -re "Database name:" - send "\025$MYSQL_SERVER_DB_NAME\r" - - expect -re "Database user:" - send "\025$MYSQL_SERVER_USER\r" - - expect -re "Database password:" - send "\025$MYSQL_SERVER_PASS\r" - - expect eof -EOF - APPSERVER_INSTALLED="true"; -fi - echo "" echo "$RES_INSTALL_SUCCESS" echo "$RES_QUESTIONS" diff --git a/build/install/OneClickInstall/install-Debian/install-preq.sh b/build/install/OneClickInstall/install-Debian/install-preq.sh index 2e8f22a354..ca18596d0a 100644 --- a/build/install/OneClickInstall/install-Debian/install-preq.sh +++ b/build/install/OneClickInstall/install-Debian/install-preq.sh @@ -65,7 +65,7 @@ if [ "$(ls "$PRODUCT_DIR/services/kafka" 2> /dev/null)" == "" ]; then KAFKA_ARCHIVE=$(curl https://downloads.apache.org/kafka/$KAFKA_VERSION/ | grep -Eo "kafka_2.[0-9][0-9]-$KAFKA_VERSION.tgz" | tail -1) curl https://downloads.apache.org/kafka/$KAFKA_VERSION/$KAFKA_ARCHIVE -O tar xzf $KAFKA_ARCHIVE --strip 1 && rm -rf $KAFKA_ARCHIVE - chown -R kafka ${PRODUCT_DIR}/services/kafka + chown -R kafka ${PRODUCT_DIR}/services/kafka/ cd - fi @@ -83,6 +83,7 @@ Restart=on-abnormal [Install] WantedBy=multi-user.target END +systemctl start zookeeper fi if [ ! -e /lib/systemd/system/kafka.service ]; then @@ -99,6 +100,7 @@ Restart=on-abnormal [Install] WantedBy=multi-user.target END +systemctl start kafka fi if ! dpkg -l | grep -q "mysql-server"; then diff --git a/build/install/common/appserver-configuration.sh b/build/install/common/appserver-configuration.sh index 16a447a4d3..4cdf8c1b98 100644 --- a/build/install/common/appserver-configuration.sh +++ b/build/install/common/appserver-configuration.sh @@ -97,6 +97,13 @@ while [ "$1" != "" ]; do fi ;; + -ess | --elasticsheme ) + if [ "$2" != "" ]; then + ELK_SHEME=$2 + shift + fi + ;; + -esh | --elastichost ) if [ "$2" != "" ]; then ELK_HOST=$2 @@ -106,7 +113,7 @@ while [ "$1" != "" ]; do -esp | --elasticport ) if [ "$2" != "" ]; then - ELK_HOST=$2 + ELK_PORT=$2 shift fi ;; @@ -118,6 +125,34 @@ while [ "$1" != "" ]; do fi ;; + -mysqlh | --mysqlhost ) + if [ "$2" != "" ]; then + DB_HOST=$2 + shift + fi + ;; + + -mysqld | --mysqldatabase ) + if [ "$2" != "" ]; then + DB_NAME=$2 + shift + fi + ;; + + -mysqlu | --mysqluser ) + if [ "$2" != "" ]; then + DB_USER=$2 + shift + fi + ;; + + -mysqlp | --mysqlpassword ) + if [ "$2" != "" ]; then + DB_PWD=$2 + shift + fi + ;; + -? | -h | --help ) echo " Usage: bash ${PRODUCT}-configuration.sh [PARAMETER] [[PARAMETER], ...]" echo @@ -132,6 +167,10 @@ while [ "$1" != "" ]; do echo " -zkp, --zookeeperport zookeeper port (default 2181)" echo " -esh, --elastichost elasticsearch ip" echo " -esp, --elasticport elasticsearch port (default 9200)" + echo " -mysqlh, --mysqlhost mysql server host" + echo " -mysqld, --mysqldatabase ${PRODUCT} database name" + echo " -mysqlu, --mysqluser ${PRODUCT} database user" + echo " -mysqlp, --mysqlpassword ${PRODUCT} database password" echo " -e, --environment environment (default 'production')" echo " -?, -h, --help this help" echo @@ -203,24 +242,10 @@ input_db_params(){ local def_DB_NAME=$(echo $user_connectionString | grep -oP 'Database=\K.*' | grep -o '^[^;]*') local def_DB_USER=$(echo $user_connectionString | grep -oP 'User ID=\K.*' | grep -o '^[^;]*') - read -e -p "Database host: " -i "$DB_HOST" DB_HOST - read -e -p "Database name: " -i "$DB_NAME" DB_NAME - read -e -p "Database user: " -i "$DB_USER" DB_USER - read -e -p "Database password: " -s DB_PWD - - if [ -z $DB_HOST ]; then - DB_HOST="${def_DB_HOST}"; - fi - - if [ -z $DB_NAME ]; then - DB_NAME="${def_DB_NAME}"; - fi - - if [ -z $DB_USER ]; then - DB_USER="${def_DB_USER}"; - fi - - echo + if [ -z $def_DB_HOST ] && [ -z $DB_HOST ]; then read -e -p "Database host: " -i "$DB_HOST" DB_HOST; fi + if [ -z $def_DB_NAME ] && [ -z $DB_NAME ]; then read -e -p "Database name: " -i "$DB_NAME" DB_NAME; fi + if [ -z $def_DB_USER ] && [ -z $DB_USER ]; then read -e -p "Database user: " -i "$DB_USER" DB_USER; fi + if [ -z $DB_PWD ]; then read -e -p "Database password: " -i "$DB_PWD" DB_PWD; fi } establish_mysql_conn(){ @@ -575,10 +600,6 @@ elif command -v apt >/dev/null 2>&1; then DIST="Debian" PACKAGE_MANAGER="dpkg -l" MYSQL_PACKAGE="mysql" - mkdir -p /var/log/onlyoffice/appserver/ /etc/onlyoffice/appserver/.private/ - chown -R onlyoffice:onlyoffice /var/www/appserver/ /var/log/onlyoffice/appserver/ /etc/onlyoffice/appserver/ - chown -R kafka /var/www/appserver/services/kafka/ - systemctl restart kafka zookeeper fi install_json diff --git a/build/install/deb/debian/appserver-common.dirs b/build/install/deb/debian/appserver-common.dirs new file mode 100644 index 0000000000..478b64dce0 --- /dev/null +++ b/build/install/deb/debian/appserver-common.dirs @@ -0,0 +1,2 @@ +/var/log/onlyoffice/appserver +/etc/onlyoffice/appserver/.private diff --git a/build/install/deb/debian/appserver-common.postinst b/build/install/deb/debian/appserver-common.postinst index ad41d9a135..1c95e29968 100644 --- a/build/install/deb/debian/appserver-common.postinst +++ b/build/install/deb/debian/appserver-common.postinst @@ -18,3 +18,4 @@ if ! cat /etc/passwd | grep -q "nginx:"; then fi usermod -aG onlyoffice,nginx onlyoffice +chown onlyoffice:onlyoffice /var/log/onlyoffice/appserver /var/www/appserver /etc/onlyoffice/appserver diff --git a/build/install/deb/debian/config b/build/install/deb/debian/config new file mode 100644 index 0000000000..01f694b330 --- /dev/null +++ b/build/install/deb/debian/config @@ -0,0 +1,24 @@ +#!/bin/sh -e + +set -e + +. /usr/share/debconf/confmodule + +db_input medium appserver/environment || true +db_input medium appserver/host || true +db_input medium appserver/port || true +db_input medium appserver/kafka-host || true +db_input medium appserver/kafka-port || true +db_input medium appserver/zookeeper-host || true +db_input medium appserver/zookeeper-port || true +db_input medium appserver/elasticsearch-sheme || true +db_input medium appserver/elasticsearch-host || true +db_input medium appserver/elasticsearch-port || true + +db_input medium appserver/db-host || true +db_input medium appserver/db-name || true +db_input medium appserver/db-user || true +db_go + +db_input critical appserver/db-pwd || true +db_go diff --git a/build/install/deb/debian/configure b/build/install/deb/debian/configure deleted file mode 100755 index 53d227fe25..0000000000 --- a/build/install/deb/debian/configure +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e - -set -e diff --git a/build/install/deb/debian/postinst b/build/install/deb/debian/postinst new file mode 100644 index 0000000000..4d8dade9df --- /dev/null +++ b/build/install/deb/debian/postinst @@ -0,0 +1,65 @@ +#!/bin/sh -e + +set -e + +. /usr/share/debconf/confmodule + +case "$1" in + configure) + db_get appserver/environment || true + ENVIRONMENT="$RET" + db_get appserver/host || true + APP_HOST="$RET" + db_get appserver/port || true + APP_PORT="$RET" + + db_get appserver/db-host || true + DB_HOST="$RET" + db_get appserver/db-name || true + DB_NAME="$RET" + db_get appserver/db-user || true + DB_USER="$RET" + db_get appserver/db-pwd || true + DB_PWD="$RET" + + db_get appserver/kafka-host || true + KAFKA_HOST="$RET" + db_get appserver/kafka-port || true + KAFKA_PORT="$RET" + + db_get appserver/zookeeper-host || true + ZOOKEEPER_HOST="$RET" + db_get appserver/zookeeper-port || true + ZOOKEEPER_PORT="$RET" + + db_get appserver/elasticsearch-sheme || true + ELK_SHEME="$RET" + db_get appserver/elasticsearch-host || true + ELK_HOST="$RET" + db_get appserver/elasticsearch-port || true + ELK_PORT="$RET" + + db_get onlyoffice/db-host || true + DOCUMENT_SERVER_HOST="$RET" + db_get onlyoffice/ds-port || true + DOCUMENT_SERVER_PORT="$RET" + + bash /usr/bin/appserver-configuration.sh -e $ENVIRONMENT -mysqlh $DB_HOST -mysqld $DB_NAME -mysqlu $DB_USER -mysqlp $DB_PWD -ash $APP_HOST -asp $APP_PORT \ + -dsh $DOCUMENT_SERVER_HOST -dsp $DOCUMENT_SERVER_PORT -kh $KAFKA_HOST -kp $KAFKA_PORT -zkh $ZOOKEEPER_HOST -zkp $ZOOKEEPER_PORT -ess $ELK_SHEME -esh $ELK_HOST -esp $ELK_PORT + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/build/install/deb/debian/rules b/build/install/deb/debian/rules index 990b6895c6..f2a3cf2632 100755 --- a/build/install/deb/debian/rules +++ b/build/install/deb/debian/rules @@ -38,8 +38,13 @@ override_dh_auto_build: sed -i "s@var/www@var/www/${PRODUCT}@g" ${SRC_PATH}/config/nginx/*.conf sed -i "s@var/www@var/www/${PRODUCT}@g" ${SRC_PATH}/config/nginx/includes/*.conf +override_dh_fixperms: + dh_fixperms + override_dh_auto_install: dh_installinit + dh_systemd_enable + dh_systemd_start --no-start override_dh_strip: # dh_strip --exclude=/site-packages/ diff --git a/build/install/deb/debian/templates b/build/install/deb/debian/templates index 40229d7550..ec3b0d5fbc 100644 --- a/build/install/deb/debian/templates +++ b/build/install/deb/debian/templates @@ -1,3 +1,18 @@ +Template: appserver/environment +Type: string +Default: production +Description: Select environment for AppServer configuration: + +Template: appserver/host +Type: string +Default: localhost +Description: AppServer host: + +Template: appserver/port +Type: string +Default: 80 +Description: AppServer port: + Template: appserver/db-host Type: string Default: localhost @@ -17,23 +32,39 @@ Type: string Default: onlyoffice Description: MySQL database name: -Template: appserver/remove-db -Type: boolean -Default: false -Description: Remove database? - This operation will remove the database which contain all data. It is recommended to take backup before removing the database. - -Template: appserver/ds-jwt-enabled -Type: boolean -Default: false -Description: To enabled Document Server JWT?: - -Template: appserver/ds-jwt-secret +Template: appserver/kafka-host Type: string -Default: {{package_sysname}} -Description: Document Server JWT Secret: +Default: localhost +Description: Kafka host: -Template: appserver/ds-jwt-secret-header +Template: appserver/kafka-port Type: string -Default: AuthorizationJwt -Description: Document Server Secret Header: +Default: 9092 +Description: Kafka port: + +Template: appserver/zookeeper-host +Type: string +Default: localhost +Description: Zookeeper host: + +Template: appserver/zookeeper-port +Type: string +Default: 2181 +Description: Zookeeper port: + +Template: appserver/elasticsearch-sheme +Type: select +Choices: http, https +Default: http +Description: Elasticsearch sheme: + +Template: appserver/elasticsearch-host +Type: string +Default: localhost +Description: Elasticsearch host: + +Template: appserver/elasticsearch-port +Type: string +Default: 9200 +Description: Elasticsearch port: + diff --git a/common/ASC.Api.Core/Model/EmployeeWraperFull.cs b/common/ASC.Api.Core/Model/EmployeeWraperFull.cs index ddc5f1095c..8110809f39 100644 --- a/common/ASC.Api.Core/Model/EmployeeWraperFull.cs +++ b/common/ASC.Api.Core/Model/EmployeeWraperFull.cs @@ -173,6 +173,26 @@ namespace ASC.Web.Api.Models return lambda; } + public EmployeeWraperFull GetSimple(UserInfo userInfo) + { + var result = new EmployeeWraperFull + { + FirstName = userInfo.FirstName, + LastName = userInfo.LastName, + }; + + FillGroups(result, userInfo); + + var photoData = UserPhotoManager.GetUserPhotoData(userInfo.ID, UserPhotoManager.BigFotoSize); + + if (photoData != null) + { + result.Avatar = "data:image/png;base64," + Convert.ToBase64String(photoData); + } + + return result; + } + public EmployeeWraperFull GetFull(UserInfo userInfo) { var result = new EmployeeWraperFull @@ -223,23 +243,7 @@ namespace ASC.Web.Api.Models } FillConacts(result, userInfo); - - if (Context.Check("groups") || Context.Check("department")) - { - var groups = UserManager.GetUserGroups(userInfo.ID) - .Select(x => new GroupWrapperSummary(x, UserManager)) - .ToList(); - - if (groups.Count > 0) - { - result.Groups = groups; - result.Department = string.Join(", ", result.Groups.Select(d => d.Name.HtmlEncode())); - } - else - { - result.Department = ""; - } - } + FillGroups(result, userInfo); var userInfoLM = userInfo.LastModified.GetHashCode(); @@ -269,6 +273,28 @@ namespace ASC.Web.Api.Models return result; } + private void FillGroups(EmployeeWraperFull result, UserInfo userInfo) + { + if (!Context.Check("groups") && !Context.Check("department")) + { + return; + } + + var groups = UserManager.GetUserGroups(userInfo.ID) + .Select(x => new GroupWrapperSummary(x, UserManager)) + .ToList(); + + if (groups.Count > 0) + { + result.Groups = groups; + result.Department = string.Join(", ", result.Groups.Select(d => d.Name.HtmlEncode())); + } + else + { + result.Department = ""; + } + } + private void FillConacts(EmployeeWraperFull employeeWraperFull, UserInfo userInfo) { if (userInfo.ContactsList == null) return; diff --git a/common/services/ASC.ElasticSearch/Engine/BaseIndexer.cs b/common/services/ASC.ElasticSearch/Engine/BaseIndexer.cs index b07b2e50e2..1e9e85a881 100644 --- a/common/services/ASC.ElasticSearch/Engine/BaseIndexer.cs +++ b/common/services/ASC.ElasticSearch/Engine/BaseIndexer.cs @@ -89,7 +89,7 @@ namespace ASC.ElasticSearch private bool IsExist { get; set; } private Client Client { get; } private ILog Log { get; } - private TenantManager TenantManager { get; } + protected TenantManager TenantManager { get; } private BaseIndexerHelper BaseIndexerHelper { get; } private Settings Settings { get; } private IServiceProvider ServiceProvider { get; } @@ -116,7 +116,8 @@ namespace ASC.ElasticSearch internal void Index(T data, bool immediately = true) { - CreateIfNotExist(data); + if (!BeforeIndex(data)) return; + Client.Instance.Index(data, idx => GetMeta(idx, data, immediately)); } @@ -124,7 +125,7 @@ namespace ASC.ElasticSearch { if (data.Count == 0) return; - CreateIfNotExist(data[0]); + if (!CheckExist(data[0])) return; if (data[0] is ISearchItemDocument) { @@ -135,7 +136,9 @@ namespace ASC.ElasticSearch for (var i = 0; i < data.Count; i++) { var t = data[i]; - var runBulk = i == data.Count - 1; + var runBulk = i == data.Count - 1; + + BeforeIndex(t); if (!(t is ISearchItemDocument wwd) || wwd.Document == null || string.IsNullOrEmpty(wwd.Document.Data)) { @@ -207,14 +210,19 @@ namespace ASC.ElasticSearch } } else - { + { + foreach (var item in data) + { + BeforeIndex(item); + } + Client.Instance.Bulk(r => r.IndexMany(data, GetMeta)); } } internal async Task IndexAsync(List data, bool immediately = true) { - CreateIfNotExist(data[0]); + if (!CheckExist(data[0])) return; if (data is ISearchItemDocument) { @@ -227,6 +235,8 @@ namespace ASC.ElasticSearch var t = data[i]; var runBulk = i == data.Count - 1; + await BeforeIndexAsync(t); + var wwd = t as ISearchItemDocument; if (wwd == null || wwd.Document == null || string.IsNullOrEmpty(wwd.Document.Data)) @@ -298,31 +308,36 @@ namespace ASC.ElasticSearch } else { + foreach (var item in data) + { + await BeforeIndexAsync(item); + } + await Client.Instance.BulkAsync(r => r.IndexMany(data, GetMeta)); } } internal void Update(T data, bool immediately = true, params Expression>[] fields) { - CreateIfNotExist(data); + if (!CheckExist(data)) return; Client.Instance.Update(DocumentPath.Id(data), r => GetMetaForUpdate(r, data, immediately, fields)); } internal void Update(T data, UpdateAction action, Expression> fields, bool immediately = true) { - CreateIfNotExist(data); + if (!CheckExist(data)) return; Client.Instance.Update(DocumentPath.Id(data), r => GetMetaForUpdate(r, data, action, fields, immediately)); } internal void Update(T data, Expression, Selector>> expression, int tenantId, bool immediately = true, params Expression>[] fields) { - CreateIfNotExist(data); + if (!CheckExist(data)) return; Client.Instance.UpdateByQuery(GetDescriptorForUpdate(data, expression, tenantId, immediately, fields)); } internal void Update(T data, Expression, Selector>> expression, int tenantId, UpdateAction action, Expression> fields, bool immediately = true) { - CreateIfNotExist(data); + if (!CheckExist(data)) return; Client.Instance.UpdateByQuery(GetDescriptorForUpdate(data, expression, tenantId, action, fields, immediately)); } @@ -389,8 +404,7 @@ namespace ASC.ElasticSearch Log.DebugFormat("Delete {0}", Wrapper.IndexName); Client.Instance.Indices.Delete(Wrapper.IndexName); - BaseIndexerHelper.Clear(Wrapper); - CreateIfNotExist(Wrapper); + BaseIndexerHelper.Clear(Wrapper); } internal IReadOnlyCollection Select(Expression, Selector>> expression, bool onlyId = false) @@ -411,6 +425,16 @@ namespace ASC.ElasticSearch return result.Documents; } + protected virtual bool BeforeIndex(T data) + { + return CheckExist(data); + } + + protected virtual Task BeforeIndexAsync(T data) + { + return Task.FromResult(CheckExist(data)); + } + public void CreateIfNotExist(T data) { try diff --git a/frontend.code-workspace b/frontend.code-workspace index 0d1f961bba..020c6a8b85 100644 --- a/frontend.code-workspace +++ b/frontend.code-workspace @@ -41,6 +41,10 @@ "cSpell.words": ["appserver", "browserslist", "debuginfo", "doceditor"] }, "extensions": { - "recommendations": ["folke.vscode-monorepo-workspace"] + "recommendations": [ + "folke.vscode-monorepo-workspace", + "orta.vscode-jest", + "firsttris.vscode-jest-runner" + ] } } diff --git a/packages/asc-web-common/api/client.js b/packages/asc-web-common/api/client.js index ba404f0b4e..fdb4c2c46b 100644 --- a/packages/asc-web-common/api/client.js +++ b/packages/asc-web-common/api/client.js @@ -81,6 +81,7 @@ export const request = function (options) { switch (error.response?.status) { case 401: if (options.skipUnauthorized) return Promise.resolve(); + if (options.skipLogout) return Promise.reject(errorText || error); request({ method: "post", diff --git a/packages/asc-web-common/api/people/index.js b/packages/asc-web-common/api/people/index.js index 20d2c291a9..f9323a7d44 100644 --- a/packages/asc-web-common/api/people/index.js +++ b/packages/asc-web-common/api/people/index.js @@ -42,6 +42,23 @@ export function getUser(userName = null) { return user; }); } + +export function getUserFromConfirm(userId, confirmKey = null) { + const options = { + method: "get", + url: `/people/${userId}.json`, + }; + + if (confirmKey) options.headers = { confirm: confirmKey }; + + return request(options).then((user) => { + if (user && user.displayName) { + user.displayName = Encoder.htmlDecode(user.displayName); + } + return user; + }); +} + export function getUserPhoto(userId) { return request({ method: "get", diff --git a/packages/asc-web-common/api/portal/index.js b/packages/asc-web-common/api/portal/index.js index 2b9d7a896c..446fae4fb4 100644 --- a/packages/asc-web-common/api/portal/index.js +++ b/packages/asc-web-common/api/portal/index.js @@ -54,3 +54,11 @@ export function getInvitationLinks() { } ); } + +export function setPortalRename(alias) { + return request({ + method: "put", + url: "/portal/portalrename.json", + data: { alias }, + }); +} diff --git a/packages/asc-web-common/api/settings/index.js b/packages/asc-web-common/api/settings/index.js index 185114b96b..5055dba1dc 100644 --- a/packages/asc-web-common/api/settings/index.js +++ b/packages/asc-web-common/api/settings/index.js @@ -25,6 +25,82 @@ export function getPortalPasswordSettings(confirmKey = null) { return request(options); } +export function setPortalPasswordSettings( + minLength, + upperCase, + digits, + specSymbols +) { + return request({ + method: "put", + url: "/settings/security/password.json", + data: { minLength, upperCase, digits, specSymbols }, + }); +} + +export function setMailDomainSettings(data) { + return request({ + method: "post", + url: "/settings/maildomainsettings.json", + data, + }); +} + +export function setDNSSettings(dnsName, enable) { + return request({ + method: "post", + url: "/settings/maildomainsettings.json", + data: { dnsName, enable }, + }); +} + +export function setIpRestrictions(data) { + return request({ + method: "put", + url: "/settings/iprestrictions.json", + data, + }); +} + +export function setIpRestrictionsEnable(data) { + return request({ + method: "put", + url: "/settings/iprestrictions/settings.json", + data, + }); +} + +export function setMessageSettings(turnOn) { + return request({ + method: "post", + url: "/settings/messagesettings.json", + data: { turnOn }, + }); +} + +export function setCookieSettings(lifeTime) { + return request({ + method: "put", + url: "/settings/cookiesettings.json", + data: { lifeTime }, + }); +} + +export function setLifetimeAuditSettings(data) { + return request({ + method: "post", + url: "/security/audit/settings/lifetime.json", + data, + }); +} + +export function getAuditTrailReport() { + return request({ + method: "post", + url: "/security/audit/login/report.json", + }); +} + export function getPortalTimezones(confirmKey = null) { const options = { method: "get", @@ -79,6 +155,24 @@ export function getLogoUrls() { }); } +export function setWhiteLabelSettings(data) { + const options = { + method: "post", + url: "/settings/whitelabel/save.json", + data, + }; + + return request(options); +} + +export function restoreWhiteLabelSettings(isDefault) { + return request({ + method: "put", + url: "/settings/whitelabel/restore.json", + data: { isDefault }, + }); +} + export function getCustomSchemaList() { return request({ method: "get", @@ -332,6 +426,14 @@ export function getBuildVersion() { return request(options); } +export function getCapabilities() { + const options = { + method: "get", + url: "/capabilities", + }; + return request(options); +} + export function getTipsSubscription() { const options = { method: "get", diff --git a/packages/asc-web-common/api/user/index.js b/packages/asc-web-common/api/user/index.js index 3f8da8671f..6c1b25a306 100644 --- a/packages/asc-web-common/api/user/index.js +++ b/packages/asc-web-common/api/user/index.js @@ -10,6 +10,7 @@ export function login(userName, passwordHash, session) { return request({ method: "post", url: "/authentication.json", + skipLogout: true, data, }); } diff --git a/packages/asc-web-common/components/AdvancedSelector/AdvancedSelector.js b/packages/asc-web-common/components/AdvancedSelector/AdvancedSelector.js index fa268ed392..7808e19d19 100644 --- a/packages/asc-web-common/components/AdvancedSelector/AdvancedSelector.js +++ b/packages/asc-web-common/components/AdvancedSelector/AdvancedSelector.js @@ -1,15 +1,10 @@ import React from "react"; import PropTypes from "prop-types"; + import Selector from "./sub-components/Selector"; -import utils from "@appserver/components/utils"; import Backdrop from "@appserver/components/backdrop"; -import DropDown from "@appserver/components/drop-down"; import Aside from "@appserver/components/aside"; -import throttle from "lodash/throttle"; -const { desktop } = utils.device; - -const displayTypes = ["dropdown", "aside", "auto"]; const sizes = ["compact", "full"]; class AdvancedSelector extends React.Component { @@ -17,75 +12,15 @@ class AdvancedSelector extends React.Component { super(props); this.ref = React.createRef(); - this.state = { - displayType: this.getTypeByWidth(), - }; - - this.throttledResize = throttle(this.resize, 300); } - componentDidMount() { - if (this.props.isOpen) { - window.addEventListener("resize", this.throttledResize); - } - } - - resize = () => { - if (this.props.displayType !== "auto") return; - - const type = this.getTypeByWidth(); - - if (type === this.state.displayType) return; - - this.setState({ displayType: type }); - }; - onClose = (e) => { //console.log("onClose"); //this.setState({ isOpen: false }); this.props.onCancel && this.props.onCancel(e); }; - componentDidUpdate(prevProps) { - if (this.props.isOpen !== prevProps.isOpen) { - //console.log(`ADSelector componentDidUpdate isOpen=${this.props.isOpen}`); - if (this.props.isOpen) { - this.resize(); - window.addEventListener("resize", this.throttledResize); - } else { - this.throttledResize.cancel(); - window.removeEventListener("resize", this.throttledResize); - } - } - - if (this.props.displayType !== prevProps.displayType) { - //console.log(`ADSelector componentDidUpdate displayType=${this.props.displayType}`); - this.setState({ displayType: this.getTypeByWidth() }); - } - } - - componentWillUnmount() { - if (this.throttledResize) { - this.throttledResize && this.throttledResize.cancel(); - window.removeEventListener("resize", this.throttledResize); - } - } - - getTypeByWidth = () => { - const displayType = - this.props.displayType !== "auto" - ? this.props.displayType - : window.innerWidth < desktop.match(/\d+/)[0] - ? "aside" - : "dropdown"; - - //console.log("AdvancedSelector2 displayType", displayType); - - return displayType; - }; - render() { - const { displayType } = this.state; const { isOpen, id, @@ -96,24 +31,10 @@ class AdvancedSelector extends React.Component { smallSectionWidth, } = this.props; - //console.log(`AdvancedSelector render() isOpen=${isOpen} displayType=${displayType}`); - return (
- {displayType === "dropdown" ? ( - - - - ) : withoutAside ? ( - + {withoutAside ? ( + ) : ( <> )} @@ -134,7 +55,7 @@ class AdvancedSelector extends React.Component { AdvancedSelector.propTypes = { id: PropTypes.string, - className: PropTypes.oneOf([PropTypes.string, PropTypes.array]), + className: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), style: PropTypes.object, options: PropTypes.array, selectedOptions: PropTypes.array, @@ -147,7 +68,6 @@ AdvancedSelector.propTypes = { buttonLabel: PropTypes.string, size: PropTypes.oneOf(sizes), - displayType: PropTypes.oneOf(displayTypes), maxHeight: PropTypes.number, @@ -178,7 +98,6 @@ AdvancedSelector.defaultProps = { selectAllLabel: "Select all", allowGroupSelection: false, allowAnyClickClose: true, - displayType: "auto", options: [], isDefaultDisplayDropDown: true, }; diff --git a/packages/asc-web-common/components/AdvancedSelector/sub-components/Body.js b/packages/asc-web-common/components/AdvancedSelector/sub-components/Body.js index f0e8eeae8a..f39134f51c 100644 --- a/packages/asc-web-common/components/AdvancedSelector/sub-components/Body.js +++ b/packages/asc-web-common/components/AdvancedSelector/sub-components/Body.js @@ -8,9 +8,9 @@ class Body extends React.Component { } render() { - const { children, displayType, className, style } = this.props; + const { children, className, style } = this.props; return ( - + {children} ); @@ -21,7 +21,6 @@ Body.propTypes = { children: PropTypes.any, className: PropTypes.string, style: PropTypes.object, - displayType: PropTypes.oneOf(["dropdown", "aside"]), }; export default Body; diff --git a/packages/asc-web-common/components/AdvancedSelector/sub-components/Column.js b/packages/asc-web-common/components/AdvancedSelector/sub-components/Column.js index 96e54926c6..fc8da79502 100644 --- a/packages/asc-web-common/components/AdvancedSelector/sub-components/Column.js +++ b/packages/asc-web-common/components/AdvancedSelector/sub-components/Column.js @@ -8,14 +8,9 @@ class Column extends React.Component { } render() { - const { children, displayType, className, style, size } = this.props; + const { children, className, style, size } = this.props; return ( - + {children} ); @@ -26,7 +21,6 @@ Column.propTypes = { children: PropTypes.any, className: PropTypes.string, style: PropTypes.object, - displayType: PropTypes.oneOf(["dropdown", "aside"]), size: PropTypes.oneOf(["compact", "full"]), }; diff --git a/packages/asc-web-common/components/AdvancedSelector/sub-components/Footer.js b/packages/asc-web-common/components/AdvancedSelector/sub-components/Footer.js index 7a0c1576cd..0e1e93ebb6 100644 --- a/packages/asc-web-common/components/AdvancedSelector/sub-components/Footer.js +++ b/packages/asc-web-common/components/AdvancedSelector/sub-components/Footer.js @@ -24,7 +24,7 @@ const Footer = (props) => {
); }, - [ - isMultiSelect, - onOptionChange, - onLinkClick, - displayType, - getOptionTooltipContent, - ] + [isMultiSelect, onOptionChange, onLinkClick] ); const renderOptionLoader = useCallback( @@ -385,7 +322,9 @@ const Selector = (props) => { marginRight: "10px", }} /> - {loadingLabel} + + {loadingLabel} + ); @@ -407,9 +346,6 @@ const Selector = (props) => { const isChecked = isOptionChecked(option); let tooltipProps = {}; - if (displayType === "dropdown") - tooltipProps = { "data-for": "user", "data-tip": index }; - ReactTooltip.rebuild(); return renderOptionItem(index, style, option, isChecked, tooltipProps); @@ -421,7 +357,6 @@ const Selector = (props) => { loadingLabel, options, isOptionChecked, - displayType, isMultiSelect, onOptionChange, onLinkClick, @@ -429,119 +364,6 @@ const Selector = (props) => { ] ); - const isGroupChecked = useCallback( - (group) => { - const selectedGroup = selectedGroupList.find((g) => g.key === group.key); - return !!selectedGroup; - }, - [selectedGroupList] - ); - - const isGroupIndeterminate = useCallback( - (group) => { - const selectedGroup = selectedGroupList.find((g) => g.key === group.key); - return ( - selectedGroup && - selectedGroup.selected > 0 && - group.total !== selectedGroup.selected - ); - }, - [selectedGroupList] - ); - - const getGroupSelected = useCallback( - (group) => { - const selectedGroup = selectedGroupList.find((g) => g.key === group.key); - return isGroupIndeterminate(group) - ? selectedGroup.selected - : isGroupChecked(group) - ? group.total - : 0; - }, - [selectedGroupList] - ); - - const getGroupLabel = useCallback( - (group) => { - const selected = getGroupSelected(group); - return isMultiSelect && allowGroupSelection - ? `${group.label} (${group.total}/${selected})` - : group.label; - }, - [isMultiSelect, allowGroupSelection] - ); - - const getSelectorGroups = useCallback( - (groups) => { - return groups.map((group) => { - return { - ...group, - label: getGroupLabel(group), - }; - }); - }, - [groups] - ); - - const onLinkGroupClick = useCallback( - (e) => { - const index = e.target.dataset.index; - if (!index) return; - - const group = groups[index]; - - if (!group) return; - - onGroupSelect(group); - }, - [groups, currentGroup] - ); - - // eslint-disable-next-line react/prop-types - const renderGroup = useCallback( - ({ index, style }) => { - const group = groups[index]; - - const isChecked = isGroupChecked(group); - const isIndeterminate = isGroupIndeterminate(group); - const isSelected = currentGroup.key === group.key; - const label = getGroupLabel(group); - - return ( - - {isMultiSelect && allowGroupSelection && ( - - )} - {label} - - ); - }, - [ - groups, - currentGroup, - isMultiSelect, - selectedGroupList, - allowGroupSelection, - ] - ); - const hasSelected = useCallback(() => { return selectedOptionList.length > 0 || selectedGroupList.length > 0; }, [selectedOptionList, selectedGroupList]); @@ -561,18 +383,171 @@ const Selector = (props) => { currentGroup: currentGroup ? currentGroup.key : null, }; - //setLastIndex(startIndex); - - //console.log("loadMoreItems", options); - loadNextPage && loadNextPage(options); }, [isNextPageLoading, searchValue, currentGroup, options] ); + const getGroupSelectedOptions = useCallback( + (group) => { + const selectedGroup = selectedOptionList.filter( + (o) => o.groups && o.groups.indexOf(group) > -1 + ); + + if (group === "all") { + selectedGroup.push(...selectedOptionList); + } + + return selectedGroup; + }, + [selectedOptionList] + ); + + const onGroupClick = useCallback( + (index) => { + const group = groups[index]; + + setGroupHeader({ ...group }); + + onGroupChanged && onGroupChanged(group); + setCurrentGroup(group); + }, + [groups, onGroupChanged] + ); + + const renderGroup = useCallback( + ({ index, style }) => { + const group = groups[index]; + + const selectedOption = getGroupSelectedOptions(group.id); + + const isIndeterminate = selectedOption.length > 0; + + let label = group.label; + + if (isMultiSelect && selectedOption.length > 0) { + label = `${group.label} (${selectedOption.length})`; + } + + return ( +
onGroupClick(index)} + > +
+ + + {label} + +
+ {isMultiSelect && ( + + )} +
+ ); + }, + [ + isMultiSelect, + groups, + currentGroup, + selectedGroupList, + selectedOptionList, + getGroupSelectedOptions, + ] + ); + + const renderGroupsList = useCallback(() => { + if (groups.length === 0) return renderOptionLoader(); + return ( + + {({ width, height }) => ( + + {renderGroup} + + )} + + ); + }, [isMultiSelect, groups, selectedOptionList, getGroupSelectedOptions]); + + const renderGroupHeader = useCallback(() => { + const selectedOption = getGroupSelectedOptions(groupHeader.id); + + const isIndeterminate = selectedOption.length > 0; + + let label = groupHeader.label; + + if (isMultiSelect && selectedOption.length > 0) { + label = `${groupHeader.label} (${selectedOption.length})`; + } + return ( + <> +
+
+ + + {label} + +
+ {isMultiSelect && ( + + )} +
+
+ + ); + }, [isMultiSelect, groupHeader, selectedOptionList, getGroupSelectedOptions]); + + const onArrowClickAction = useCallback(() => { + if (groupHeader && groups.length !== 1) { + setGroupHeader(null); + + onGroupChanged && onGroupChanged([]); + setCurrentGroup([]); + return; + } + onArrowClick && onArrowClick(); + }, [groups, groupHeader, onArrowClick, onGroupChanged]); + return ( { hasSelected={hasSelected()} className="selector-wrapper" > - +
+ + + {headerLabel.replace("()", "")} + +
+
{ onChange={onSearchChange} onClearSearch={onSearchReset} /> - {displayType === "aside" && groups && groups.length > 0 && ( - <> - - {isMultiSelect && - allowGroupSelection && - options && - options.length > 0 && ( - - )} - - )}
- - {({ width, height }) => ( - - {({ onItemsRendered, ref }) => ( - - {renderOption} - - )} - - )} - - - {!hasNextPage && itemCount === 0 && ( -
- - {!searchValue ? emptyOptionsLabel : emptySearchOptionsLabel} - -
+ {!groupHeader && !searchValue && groups ? ( + renderGroupsList() + ) : ( + <> + {!searchValue && renderGroupHeader()} + {!hasNextPage && itemCount === 0 ? ( +
+ + {!searchValue ? emptyOptionsLabel : emptySearchOptionsLabel} + +
+ ) : ( + + {({ width, height }) => ( + + {({ onItemsRendered, ref }) => ( + + {renderOption} + + )} + + )} + + )} + )} + {getOptionTooltipContent && ( { )}
- {displayType === "dropdown" && groups && groups.length > 0 && ( - <> -
- -
- - {groupsHeaderLabel} - -
- - - {({ height, width }) => ( - - {renderGroup} - - )} - - -
- - )}