Merge branch 'release/rc-v1.2.0' of github.com:ONLYOFFICE/DocSpace into release/rc-v1.2.0

This commit is contained in:
Elyor Djalilov 2022-12-27 15:23:11 +05:00
commit 596e20631c
55 changed files with 1518 additions and 1353 deletions

View File

@ -73,7 +73,7 @@ public class MigrationCreator
_moduleProvider = moduleProvider;
}
public async Task<string> Create(int tenant, string userName, string toRegion)
public string Create(int tenant, string userName, string toRegion)
{
Init(tenant, userName, toRegion);
@ -83,7 +83,7 @@ public class MigrationCreator
using (var writer = new ZipWriteOperator(_tempStream, path))
{
DoMigrationDb(id, writer);
await DoMigrationStorage(id, writer);
DoMigrationStorage(id, writer);
}
return fileName;
}
@ -209,13 +209,13 @@ public class MigrationCreator
data.Rows[0]["name"] = "";
}
private async Task DoMigrationStorage(Guid id, IDataWriteOperator writer)
private void DoMigrationStorage(Guid id, IDataWriteOperator writer)
{
Console.WriteLine($"start backup storage");
var fileGroups = await GetFilesGroup(id);
var fileGroups = GetFilesGroup(id);
foreach (var group in fileGroups)
{
Console.WriteLine($"start backup fileGroup: {group}");
Console.WriteLine($"start backup fileGroup: {group.Key}");
foreach (var file in group)
{
var storage = _storageFactory.GetStorage(_tenant, group.Key);
@ -227,7 +227,7 @@ public class MigrationCreator
writer.WriteEntry(file1.GetZipKey(), fileStream);
}, file, 5);
}
Console.WriteLine($"end backup fileGroup: {group}");
Console.WriteLine($"end backup fileGroup: {group.Key}");
}
var restoreInfoXml = new XElement(
@ -244,9 +244,9 @@ public class MigrationCreator
Console.WriteLine($"end backup storage");
}
private async Task<List<IGrouping<string, BackupFileInfo>>> GetFilesGroup(Guid id)
private List<IGrouping<string, BackupFileInfo>> GetFilesGroup(Guid id)
{
var files = (await GetFilesToProcess(id)).ToList();
var files = GetFilesToProcess(id).ToList();
var backupsContext = _dbFactory.CreateDbContext<BackupsContext>();
var exclude = backupsContext.Backups.AsQueryable().Where(b => b.TenantId == _tenant && b.StorageType == 0 && b.StoragePath != null).ToList();
@ -254,39 +254,60 @@ public class MigrationCreator
return files.GroupBy(file => file.Module).ToList();
}
protected async Task<IEnumerable<BackupFileInfo>> GetFilesToProcess(Guid id)
private IEnumerable<BackupFileInfo> GetFilesToProcess(Guid id)
{
var files = new List<BackupFileInfo>();
foreach (var module in _storageFactoryConfig.GetModuleList().Where(m => m == "files"))
{
var store = _storageFactory.GetStorage(_tenant, module);
var domains = _storageFactoryConfig.GetDomainList(module).ToArray();
foreach (var domain in domains)
{
files.AddRange(await store.ListFilesRelativeAsync(domain, "\\", "*.*", true).Select(path => new BackupFileInfo(domain, module, path, _tenant)).ToListAsync());
}
files.AddRange(await
store.ListFilesRelativeAsync(string.Empty, "\\", "*.*", true)
.Where(path => domains.All(domain => !path.Contains(domain + "/")))
.Select(path => new BackupFileInfo(string.Empty, module, path, _tenant))
.ToListAsync());
}
var filesDbContext = _dbFactory.CreateDbContext<FilesDbContext>();
files = files.Where(f => UserIsFileOwner(id, f, filesDbContext)).ToList();
var filesDbContext = _dbFactory.CreateDbContext<FilesDbContext>();
var module = _storageFactoryConfig.GetModuleList().Where(m => m == "files").Single();
var store = _storageFactory.GetStorage(_tenant, module);
var dbFiles = filesDbContext.Files.Where(q => q.CreateBy == id && q.TenantId == _tenant).ToList();
var tasks = new List<Task>(20);
foreach (var dbFile in dbFiles)
{
if (tasks.Count != 20)
{
tasks.Add(FindFiles(files, store, dbFile, module));
}
else
{
Task.WaitAll(tasks.ToArray());
tasks.Clear();
}
}
Task.WaitAll(tasks.ToArray());
return files.Distinct();
}
private bool UserIsFileOwner(Guid id, BackupFileInfo fileInfo, FilesDbContext filesDbContext)
{
var stringId = id.ToString();
return filesDbContext.Files.Any(
f => f.CreateBy == id &&
f.TenantId == _tenant &&
fileInfo.Path.Contains("\\file_" + f.Id + "\\"));
private async Task FindFiles(List<BackupFileInfo> list, IDataStore store, DbFile dbFile, string module)
{
var files = await store.ListFilesRelativeAsync(string.Empty, $"\\{GetUniqFileDirectory(dbFile.Id)}", "*.*", true)
.Select(path => new BackupFileInfo(string.Empty, module, path, _tenant))
.ToListAsync();
list.AddRange(files);
if (files.Any())
{
Console.WriteLine($"file {dbFile.Id} found");
}
else
{
Console.WriteLine($"file {dbFile.Id} not found");
}
}
private string GetUniqFileDirectory(int fileId)
{
if (fileId == 0)
{
throw new ArgumentNullException("fileIdObject");
}
return string.Format("folder_{0}/file_{1}", (fileId / 1000 + 1) * 1000, fileId);
}
}

View File

@ -89,7 +89,7 @@ builder.WebHost.ConfigureServices((hostContext, services) =>
var app = builder.Build();
Console.WriteLine("backup start");
var migrationCreator = app.Services.GetService<MigrationCreator>();
var fileName = await migrationCreator.Create(param.Tenant, param.UserName, param.ToRegion);
var fileName = migrationCreator.Create(param.Tenant, param.UserName, param.ToRegion);
Console.WriteLine("backup was success");
Console.WriteLine("restore start");
var migrationRunner = app.Services.GetService<MigrationRunner>();

View File

@ -24,7 +24,7 @@ map $request_uri $header_x_frame_options {
map $request_uri $cache_control {
default "no-cache, no-store, must-revalidate";
~*\/(storage\/room_logos\/root\/|storage\/userPhotos\/root\/) "public, no-transform, max-age=15, s-maxage=3153600";
~*\/(storage\/room_logos\/root\/|storage\/userPhotos\/root\/) "private";
~*\/(api\/2\.0.*|storage|login\.ashx|products\/.+\/httphandlers\/filehandler\.ashx|ChunkedUploader.ashx|ThirdPartyAppHandler|apisystem|sh|remoteEntry\.js|debuginfo\.md) "no-cache, no-store, must-revalidate";
~*\/(locales.*\.json) "public, no-transform";
~*\.(ogg|ogv|svg|svgz|eot|otf|woff|woff2|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|md|css|js)$ "public, no-transform, max-age=0, s-maxage=3153600";

View File

@ -9,7 +9,7 @@ import Filter from "@docspace/common/api/people/filter";
import { getUserList } from "@docspace/common/api/people";
import Loaders from "@docspace/common/components/Loaders";
import { getUserRole } from "SRC_DIR/helpers/people-helpers";
import { getUserRole } from "@docspace/common/utils";
let timer = null;

View File

@ -3,7 +3,7 @@ import { inject, observer } from "mobx-react";
import toastr from "@docspace/components/toast/toastr";
import FilesListBody from "./FilesListBody";
import axios from "axios";
import { combineUrl, getFolderOptions } from "@docspace/common/utils";
import { getFolder } from "@docspace/common/api/files";
class FilesListWrapper extends React.Component {
constructor(props) {
@ -30,7 +30,7 @@ class FilesListWrapper extends React.Component {
if (folderId !== prevProps.folderId) {
if (isNextPageLoading) {
this.source.cancel();
this.abortController.abort();
this._isLoadNextPage = false;
this.setState({
@ -66,36 +66,26 @@ class FilesListWrapper extends React.Component {
this._isLoadNextPage = true;
this.setState({ isNextPageLoading: true }, async () => {
try {
this.CancelToken = axios.CancelToken;
this.source = this.CancelToken.source();
const options = getFolderOptions(folderId, this.newFilter);
this.abortController = new AbortController();
const response = await axios
.get(
combineUrl(
window.DocSpaceConfig?.proxy?.url,
window.DocSpaceConfig?.api?.prefix,
options.url
),
{
cancelToken: this.source.token,
const data = await getFolder(
folderId,
this.newFilter,
this.abortController.signal
).catch((err) => {
if (axios.isCancel(err)) {
console.log("Request canceled", err.message);
} else {
const errorBody = err.response;
if (errorBody.data && errorBody.data.error) {
toastr.error(errorBody.data.error.message);
}
)
.catch((thrown) => {
if (axios.isCancel(thrown)) {
console.log("Request canceled", thrown.message);
} else {
const errorBody = thrown.response;
}
return;
});
if (errorBody.data && errorBody.data.error) {
toastr.error(errorBody.data.error.message);
}
}
return;
});
if (!response) return;
const data = response.data.response;
if (!data) return;
if (page === 0 && folderSelection) {
setFolderTitle(data.current.title);

View File

@ -3,7 +3,7 @@ import {
StyledCircle,
StyledCircleWrap,
StyledLoadingButton,
} from "./StyledLoadingButton";
} from "@docspace/common/components/StyledLoadingButton";
import { ColorTheme, ThemeType } from "@docspace/common/components/ColorTheme";

View File

@ -3,9 +3,6 @@ import {
EmployeeActivationStatus,
EmployeeStatus,
} from "@docspace/common/constants";
import { isAdmin } from "@docspace/common/utils";
import { id } from "PACKAGE_FILE";
//const { isAdmin } = utils;
export const getUserStatus = (user) => {
if (
@ -25,16 +22,6 @@ export const getUserStatus = (user) => {
}
};
export const getUserRole = (user) => {
if (user.isOwner) return "owner";
else if (isAdmin(user, id))
//TODO: Change to People Product Id const
return "admin";
//TODO: Need refactoring
else if (user.isVisitor) return "user";
else return "manager";
};
export const getUserContactsPattern = () => {
return {
contact: [

View File

@ -61,34 +61,6 @@ const StyledInfoPanelHeader = styled.div`
}
`;
const StyledInfoPanelToggleWrapper = styled.div`
display: flex;
@media ${tablet} {
display: none;
}
align-items: center;
justify-content: center;
padding-right: 20px;
.info-panel-toggle-bg {
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: ${(props) =>
props.theme.infoPanel.sectionHeaderToggleBgActive};
path {
fill: ${(props) => props.theme.infoPanel.sectionHeaderToggleIconActive};
}
}
`;
StyledInfoPanelHeader.defaultProps = { theme: Base };
StyledInfoPanelToggleWrapper.defaultProps = { theme: Base };
export { StyledInfoPanelHeader, StyledInfoPanelToggleWrapper };
export { StyledInfoPanelHeader };

View File

@ -1132,7 +1132,7 @@ const SectionFilterContent = ({
?.getItem(`${COLUMNS_SIZE_INFO_PANEL}=${userId}`)
?.split(" ");
if (availableSort?.includes("Author") && !isPersonalRoom) {
if (availableSort?.includes("Author")) {
const idx = availableSort.findIndex((x) => x === "Author");
const hide =
infoPanelVisible &&

View File

@ -5,6 +5,7 @@ const WhiteLabelWrapper = styled.div`
.subtitle {
margin-top: 5px;
margin-bottom: 20px;
color: ${(props) => props.theme.client.settings.common.descriptionColor};
}
.header-container {

View File

@ -19,7 +19,7 @@ import {
generateLogo,
getLogoOptions,
uploadLogo,
} from "../../../utils/generateLogo";
} from "../../../utils/whiteLabelHelper";
import isEqual from "lodash/isEqual";
const WhiteLabel = (props) => {
@ -131,7 +131,6 @@ const WhiteLabel = (props) => {
};
const onSave = async () => {
console.log(logoUrlsWhiteLabel);
let logosArr = [];
for (let i = 0; i < logoUrlsWhiteLabel.length; i++) {
@ -174,9 +173,7 @@ const WhiteLabel = (props) => {
<LoaderWhiteLabel />
) : (
<WhiteLabelWrapper>
<Text className="subtitle" color="#657077">
{t("BrandingSubtitle")}
</Text>
<Text className="subtitle">{t("BrandingSubtitle")}</Text>
<div className="header-container">
<Text fontSize="16px" fontWeight="700">

View File

@ -6,9 +6,10 @@ import SelectUsersCountContainer from "./sub-components/SelectUsersCountContaine
import TotalTariffContainer from "./sub-components/TotalTariffContainer";
import toastr from "@docspace/components/toast/toastr";
import axios from "axios";
import { combineUrl } from "@docspace/common/utils";
//import { combineUrl } from "@docspace/common/utils";
import ButtonContainer from "./sub-components/ButtonContainer";
import { Trans } from "react-i18next";
import { getPaymentLink } from "@docspace/common/api/portal";
const StyledBody = styled.div`
border-radius: 12px;
@ -99,16 +100,9 @@ const PriceCalculation = ({
CancelToken = axios.CancelToken;
source = CancelToken.source();
await axios
.put(
combineUrl(window.DocSpaceConfig?.proxy?.url, "/portal/payment/url"),
{ quantity: { admin: value } },
{
cancelToken: source.token,
}
)
.then((response) => {
setPaymentLink(response.data.response);
await getPaymentLink(value, source.token)
.then((link) => {
setPaymentLink(link);
setIsLoading(false);
})
.catch((thrown) => {

View File

@ -1,54 +1,5 @@
import styled from "styled-components";
import { isMobile } from "react-device-detect";
import { Base } from "@docspace/components/themes";
const StyledBodyPreparationPortal = styled.div`
margin-bottom: 24px;
width: 100%;
max-width: ${(props) => (props.errorMessage ? "560px" : "480px")};
box-sizing: border-box;
align-items: center;
.preparation-portal_progress {
display: flex;
margin-bottom: 16px;
position: relative;
.preparation-portal_progress-bar {
padding: 2px;
box-sizing: border-box;
border-radius: 6px;
width: 100%;
height: 24px;
background-color: ${(props) =>
props.theme.preparationPortalProgress.backgroundColor};
}
.preparation-portal_progress-line {
border-radius: 5px;
width: ${(props) => props.percent}%;
height: 20px;
transition-property: width;
transition-duration: 0.9s;
}
.preparation-portal_percent {
position: absolute;
font-size: 14px;
font-weight: 600;
color: ${(props) =>
props.percent > 50
? props.theme.preparationPortalProgress.colorPercentBig
: props.theme.preparationPortalProgress.colorPercentSmall};
top: 2px;
left: calc(50% - 9px);
}
}
.preparation-portal_text {
text-align: center;
color: ${(props) => props.theme.text.disableColor};
}
`;
StyledBodyPreparationPortal.defaultProps = { theme: Base };
const StyledPreparationPortal = styled.div`
width: 100%;
@ -68,4 +19,4 @@ const StyledPreparationPortal = styled.div`
}
`;
export { StyledBodyPreparationPortal, StyledPreparationPortal };
export { StyledPreparationPortal };

View File

@ -2,10 +2,7 @@ import React from "react";
import ErrorContainer from "@docspace/common/components/ErrorContainer";
import { withTranslation } from "react-i18next";
import {
StyledPreparationPortal,
StyledBodyPreparationPortal,
} from "./StyledPreparationPortal";
import { StyledPreparationPortal } from "./StyledPreparationPortal";
import Text from "@docspace/components/text";
import { getRestoreProgress } from "@docspace/common/api/portal";
import { observer, inject } from "mobx-react";

View File

@ -7,7 +7,7 @@ import Avatar from "@docspace/components/avatar";
import Text from "@docspace/components/text";
import IconButton from "@docspace/components/icon-button";
import { getUserRole } from "SRC_DIR/helpers/people-helpers";
import { getUserRole } from "@docspace/common/utils";
import LanguagesCombo from "./languagesCombo";
import TimezoneCombo from "./timezoneCombo";

View File

@ -2,7 +2,6 @@ import styled, { css } from "styled-components";
import Row from "@docspace/components/row";
import { tablet } from "@docspace/components/utils/device";
import { Base } from "@docspace/components/themes";
import VersionSvg from "@docspace/client/public/images/versionrevision_active.react.svg";
const StyledBody = styled.div`
height: 100%;
@ -279,32 +278,4 @@ const StyledVersionRow = styled(Row)`
StyledVersionRow.defaultProps = { theme: Base };
const StyledVersionSvg = styled(VersionSvg)`
path {
fill: ${(props) =>
!props.$isVersion
? props.theme.filesVersionHistory.badge.defaultFill
: props.index === 0
? props.theme.filesVersionHistory.badge.fill
: props.theme.filesVersionHistory.badge.badgeFill};
stroke: ${(props) =>
!props.$isVersion
? props.theme.filesVersionHistory.badge.stroke
: props.index === 0
? props.theme.filesVersionHistory.badge.fill
: props.theme.filesVersionHistory.badge.badgeFill};
stroke-dasharray: ${(props) => (props.$isVersion ? "2 0" : "3 1")};
stroke-linejoin: ${(props) => (props.$isVersion ? "unset" : "round")};
${(props) =>
props.$isVersion &&
css`
stroke-width: 2;
`}
}
`;
StyledVersionSvg.defaultProps = { theme: Base };
export { StyledBody, StyledVersionRow, StyledVersionList, StyledVersionSvg };
export { StyledBody, StyledVersionRow, StyledVersionList };

View File

@ -1,8 +1,7 @@
import React from "react";
import Text from "@docspace/components/text";
import { StyledVersionSvg } from "@docspace/client/src/pages/VersionHistory/Section/Body/StyledVersionHistory";
import { ColorTheme, ThemeType } from "@docspace/common/components/ColorTheme";
import VersionMarkIcon from "@docspace/common/components/VersionMarkIcon";
const VersionBadge = ({
className,
isVersion,
@ -22,7 +21,7 @@ const VersionBadge = ({
index={index}
{...rest}
>
<StyledVersionSvg $isVersion={isVersion} theme={theme} index={index} />
<VersionMarkIcon $isVersion={isVersion} theme={theme} index={index} />
<Text
className="version_badge-text"

View File

@ -106,6 +106,9 @@ class FilesStore {
isErrorRoomNotAvailable = false;
roomsController = null;
filesController = null;
constructor(
authStore,
selectedFolderStore,
@ -126,6 +129,9 @@ class FilesStore {
this.thirdPartyStore = thirdPartyStore;
this.accessRightsStore = accessRightsStore;
this.roomsController = new AbortController();
this.filesController = new AbortController();
const { socketHelper, withPaging } = authStore.settingsStore;
socketHelper.on("s:modify-folder", async (opt) => {
@ -805,6 +811,11 @@ class FilesStore {
) => {
const { setSelectedNode } = this.treeFoldersStore;
if (this.isLoading) {
this.roomsController.abort();
this.roomsController = new AbortController();
}
this.scrollToTop();
const filterData = filter ? filter.clone() : FilesFilter.getDefault();
@ -836,7 +847,7 @@ class FilesStore {
setSelectedNode([folderId + ""]);
return api.files
.getFolder(folderId, filterData)
.getFolder(folderId, filterData, this.filesController.signal)
.then(async (data) => {
filterData.total = data.total;
@ -988,6 +999,11 @@ class FilesStore {
) => {
const { setSelectedNode, roomsFolderId } = this.treeFoldersStore;
if (this.isLoading) {
this.filesController.abort();
this.filesController = new AbortController();
}
const filterData = !!filter ? filter.clone() : RoomsFilter.getDefault();
const filterStorageItem = localStorage.getItem(
@ -1011,7 +1027,7 @@ class FilesStore {
const request = () =>
api.rooms
.getRooms(filterData)
.getRooms(filterData, this.roomsController.signal)
.then(async (data) => {
if (!folderId) setSelectedNode([data.current.id + ""]);

View File

@ -4,7 +4,6 @@ import uniqueid from "lodash/uniqueId";
import sumBy from "lodash/sumBy";
import { ConflictResolveType } from "@docspace/common/constants";
import {
getFolder,
getFileInfo,
getFolderInfo,
getProgress,

View File

@ -3,8 +3,7 @@ import axios from "axios";
import FilesFilter from "./filter";
import { FolderType, RoomSearchArea } from "../../constants";
import find from "lodash/find";
import { getFolderOptions, decodeDisplayName } from "../../utils";
import { Encoder } from "../../utils/encoder";
import { decodeDisplayName } from "../../utils";
import { getRooms } from "../rooms";
import RoomsFilter from "../rooms/filter";
@ -51,8 +50,22 @@ export function getFolderPath(folderId) {
return request(options);
}
export function getFolder(folderId, filter) {
const options = getFolderOptions(folderId, filter);
export function getFolder(folderId, filter, signal) {
if (folderId && typeof folderId === "string") {
folderId = encodeURIComponent(folderId.replace(/\\\\/g, "\\"));
}
const params =
filter && filter instanceof FilesFilter
? `${folderId}?${filter.toApiUrlParams()}`
: folderId;
const options = {
method: "get",
url: `/files/${params}`,
signal,
};
return request(options).then((res) => {
res.files = decodeDisplayName(res.files);
res.folders = decodeDisplayName(res.folders);

View File

@ -239,13 +239,14 @@ export function getPaymentAccount() {
return request({ method: "get", url: "/portal/payment/account" });
}
export function getPaymentLink(adminCount, currency) {
export function getPaymentLink(adminCount, cancelToken) {
return request({
method: "put",
url: `/portal/payment/url`,
data: {
quantity: { admin: adminCount },
},
cancelToken,
});
}

View File

@ -2,10 +2,11 @@ import { request } from "../client";
import { decodeDisplayName } from "../../utils";
import { FolderType } from "../../constants";
export function getRooms(filter) {
export function getRooms(filter, signal) {
const options = {
method: "get",
url: `/files/rooms?${filter.toApiUrlParams()}`,
signal,
};
return request(options).then((res) => {

View File

@ -1,6 +1,6 @@
import styled, { css } from "styled-components";
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
import StyledIcon from "@docspace/client/src/components/StyledIcon";
import StyledIcon from "../../StyledIcon";
import Base from "@docspace/components/themes/base";
const getDefaultStyles = ({

View File

@ -1,6 +1,6 @@
import styled, { css } from "styled-components";
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
import StyledPinIcon from "@docspace/client/src/components/StyledPinIcon";
import StyledPinIcon from "../../StyledPinIcon";
import Base from "@docspace/components/themes/base";
const getDefaultStyles = ({ $currentColorScheme, theme }) =>

View File

@ -1,5 +1,5 @@
import styled, { css } from "styled-components";
import StyledWrapper from "@docspace/client/src/components/IndicatorLoader/StyledWrapper";
import StyledWrapper from "../../StyledWrapper";
import Base from "@docspace/components/themes/base";
const getDefaultStyles = ({ $currentColorScheme, theme }) =>

View File

@ -1,5 +1,5 @@
import styled, { css } from "styled-components";
import { StyledInfoPanelToggleWrapper } from "@docspace/client/src/pages/Home/InfoPanel/Header/styles/common";
import StyledInfoPanelToggleWrapper from "../../StyledInfoPanelToggleWrapper";
import Base from "@docspace/components/themes/base";
const getDefaultStyles = ({ $currentColorScheme }) =>

View File

@ -1,5 +1,5 @@
import styled, { css } from "styled-components";
import { LoginContainer } from "@docspace/login/src/client/components/StyledLogin";
import LoginContainer from "../../LoginContainer";
const getDefaultStyles = ({ $currentColorScheme }) =>
$currentColorScheme &&

View File

@ -3,7 +3,7 @@ import {
StyledCircle,
StyledLoadingButton,
StyledCircleWrap,
} from "@docspace/client/src/components/panels/UploadPanel/SubComponents/StyledLoadingButton";
} from "../../StyledLoadingButton";
import { Base } from "@docspace/components/themes";
const getDefaultStyles = ({ $currentColorScheme, theme }) =>

View File

@ -1,5 +1,5 @@
import styled, { css } from "styled-components";
import { StyledBodyPreparationPortal } from "@docspace/client/src/pages/PreparationPortal/StyledPreparationPortal";
import StyledBodyPreparationPortal from "../../StyledBodyPreparationPortal";
import Base from "@docspace/components/themes/base";
const getDefaultStyles = ({ $currentColorScheme, theme }) =>

View File

@ -1,10 +1,10 @@
import styled, { css } from "styled-components";
import { StyledVersionSvg } from "@docspace/client/src/pages/VersionHistory/Section/Body/StyledVersionHistory";
import Box from "@docspace/components/box";
import VersionMarkIcon from "../../VersionMarkIcon";
const getDefaultStyles = ({ $currentColorScheme, $isVersion, theme, index }) =>
$currentColorScheme &&
css`
${StyledVersionSvg} {
${VersionMarkIcon} {
path {
fill: ${!$isVersion
? theme.filesVersionHistory.badge.defaultFill

View File

@ -0,0 +1,266 @@
import styled from "styled-components";
import { tablet, hugeMobile } from "@docspace/components/utils/device";
const LoginContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin: 56px auto 0 auto;
max-width: 960px;
.remember-wrapper {
max-width: 142px;
display: flex;
flex-direction: row;
align-items: center;
}
.buttonWrapper {
margin-bottom: 8px;
width: 100%;
}
@media ${tablet} {
max-width: 480px;
}
@media ${hugeMobile} {
margin: 0 auto 0 auto;
max-width: 311px;
}
.socialButton {
min-height: 40px;
}
.or-label,
.login-or-access-text {
min-height: 18px;
}
.login-or-access-text {
text-transform: lowercase;
}
.recover-link {
min-height: 19px;
}
.greeting-title {
width: 100%;
padding-bottom: 32px;
min-height: 32px;
color: ${(props) => props.theme.login.headerColor};
@media ${hugeMobile} {
padding-top: 32px;
}
}
.more-label {
padding-top: 18px;
}
.or-label {
color: ${(props) => props.theme.login.orTextColor};
margin: 0 32px;
}
.line {
display: flex;
width: 320px;
align-items: center;
color: ${(props) => props.theme.login.orLineColor};
padding: 32px 0;
@media ${tablet} {
width: 480px;
}
@media ${hugeMobile} {
width: 311px;
}
}
.line:before,
.line:after {
content: "";
flex-grow: 1;
background: ${(props) => props.theme.login.orLineColor};
height: 1px;
font-size: 0px;
line-height: 0px;
margin: 0px;
}
.code-input-container {
margin-top: 32px;
text-align: center;
}
.not-found-code {
margin-top: 32px;
}
.code-input-bar {
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
margin-top: 16px;
padding: 14px 12px;
text-align: center;
font-weight: 600;
font-size: 11px;
line-height: 12px;
color: #333;
svg {
margin: 8px;
}
}
.code-input-bar.warning {
background: #f7e6be;
margin-bottom: 16px;
}
.code-input-bar.error {
background: #f7cdbe;
}
.auth-form-container {
width: 320px;
@media ${tablet} {
width: 100%;
}
@media ${hugeMobile} {
margin: 32px 0 0 0;
width: 100%;
}
.field-body {
height: 48px;
input,
.password-input > div {
background: ${(props) => props.theme.input.backgroundColor};
color: ${(props) => props.theme.input.color};
border-color: ${(props) => props.theme.input.borderColor};
}
}
.login-forgot-wrapper {
margin-bottom: 14px;
.login-checkbox-wrapper {
display: flex;
//align-items: center;
.login-checkbox {
display: flex;
align-items: flex-start;
svg {
margin-right: 8px !important;
rect {
fill: ${(props) => props.theme.checkbox.fillColor};
stroke: ${(props) => props.theme.checkbox.borderColor};
}
path {
fill: ${(props) => props.theme.checkbox.arrowColor};
}
}
.help-button {
svg {
path {
fill: ${(props) => props.theme.login.helpButton};
}
}
}
.checkbox-text {
color: ${(props) => props.theme.checkbox.arrowColor};
}
label {
justify-content: center;
}
}
.remember-helper-wrapper {
display: flex;
gap: 4px;
}
}
.login-link {
line-height: 18px;
margin-left: auto;
}
}
.login-button {
margin-top: 8px;
}
.login-button-dialog {
margin-right: 8px;
}
.login-bottom-border {
width: 100%;
height: 1px;
background: #eceef1;
}
.login-bottom-text {
margin: 0 8px;
}
.login-or-access {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
& > :first-child {
margin-top: 24px;
}
}
}
.logo-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 46px;
padding-bottom: 64px;
svg {
path:last-child {
fill: ${(props) => props.theme.client.home.logoColor};
}
}
@media ${hugeMobile} {
display: none;
}
}
.workspace-title {
color: ${(props) => props.theme.login.titleColor};
margin-bottom: 16px;
@media ${hugeMobile} {
margin-top: 32px;
}
}
.code-description {
color: ${(props) => props.theme.login.textColor};
}
`;
export default LoginContainer;

View File

@ -0,0 +1,52 @@
import styled from "styled-components";
import { Base } from "@docspace/components/themes";
const StyledBodyPreparationPortal = styled.div`
margin-bottom: 24px;
width: 100%;
max-width: ${(props) => (props.errorMessage ? "560px" : "480px")};
box-sizing: border-box;
align-items: center;
.preparation-portal_progress {
display: flex;
margin-bottom: 16px;
position: relative;
.preparation-portal_progress-bar {
padding: 2px;
box-sizing: border-box;
border-radius: 6px;
width: 100%;
height: 24px;
background-color: ${(props) =>
props.theme.preparationPortalProgress.backgroundColor};
}
.preparation-portal_progress-line {
border-radius: 5px;
width: ${(props) => props.percent}%;
height: 20px;
transition-property: width;
transition-duration: 0.9s;
}
.preparation-portal_percent {
position: absolute;
font-size: 14px;
font-weight: 600;
color: ${(props) =>
props.percent > 50
? props.theme.preparationPortalProgress.colorPercentBig
: props.theme.preparationPortalProgress.colorPercentSmall};
top: 2px;
left: calc(50% - 9px);
}
}
.preparation-portal_text {
text-align: center;
color: ${(props) => props.theme.text.disableColor};
}
`;
StyledBodyPreparationPortal.defaultProps = { theme: Base };
export default StyledBodyPreparationPortal;

View File

@ -0,0 +1,31 @@
import styled from "styled-components";
import { tablet } from "@docspace/components/utils/device";
const StyledInfoPanelToggleWrapper = styled.div`
display: flex;
@media ${tablet} {
display: none;
}
align-items: center;
justify-content: center;
padding-right: 20px;
.info-panel-toggle-bg {
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: ${(props) =>
props.theme.infoPanel.sectionHeaderToggleBgActive};
path {
fill: ${(props) => props.theme.infoPanel.sectionHeaderToggleIconActive};
}
}
`;
export default StyledInfoPanelToggleWrapper;

View File

@ -1,8 +1,15 @@
import styled, { css, keyframes } from "styled-components";
import globalColors from "@docspace/components/utils/globalColors";
import { Base } from "@docspace/components/themes";
const backgroundColor = "none";
const rotate360 = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const StyledCircleWrap = styled.div`
width: 16px;
@ -12,15 +19,25 @@ const StyledCircleWrap = styled.div`
cursor: pointer;
`;
const rotate360 = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
StyledCircleWrap.defaultProps = { theme: Base };
const StyledLoadingButton = styled.div`
width: 12px;
height: 12px;
border-radius: 50%;
text-align: center;
line-height: 12px;
background: ${(props) =>
props.theme.filesPanels.upload.loadingButton.background};
position: absolute;
margin: 2px;
color: ${(props) => props.theme.filesPanels.upload.loadingButton.color};
font-size: 16px;
font-weight: bold;
`;
StyledLoadingButton.defaultProps = { theme: Base };
const StyledCircle = styled.div`
.circle__mask,
.circle__fill {
@ -70,23 +87,4 @@ const StyledCircle = styled.div`
}
`;
StyledCircleWrap.defaultProps = { theme: Base };
const StyledLoadingButton = styled.div`
width: 12px;
height: 12px;
border-radius: 50%;
text-align: center;
line-height: 12px;
background: ${(props) =>
props.theme.filesPanels.upload.loadingButton.background};
position: absolute;
margin: 2px;
color: ${(props) => props.theme.filesPanels.upload.loadingButton.color};
font-size: 16px;
font-weight: bold;
`;
StyledLoadingButton.defaultProps = { theme: Base };
export { StyledCircle, StyledCircleWrap, StyledLoadingButton };
export { StyledCircle, StyledLoadingButton, StyledCircleWrap };

View File

@ -1,4 +1,4 @@
import styled, { css } from "styled-components";
import styled from "styled-components";
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
import IconButton from "@docspace/components/icon-button";

View File

@ -0,0 +1,30 @@
import styled, { css } from "styled-components";
import VersionSvg from "@docspace/client/public/images/versionrevision_active.react.svg";
const VersionMarkIcon = styled(VersionSvg)`
path {
fill: ${(props) =>
!props.$isVersion
? props.theme.filesVersionHistory.badge.defaultFill
: props.index === 0
? props.theme.filesVersionHistory.badge.fill
: props.theme.filesVersionHistory.badge.badgeFill};
stroke: ${(props) =>
!props.$isVersion
? props.theme.filesVersionHistory.badge.stroke
: props.index === 0
? props.theme.filesVersionHistory.badge.fill
: props.theme.filesVersionHistory.badge.badgeFill};
stroke-dasharray: ${(props) => (props.$isVersion ? "2 0" : "3 1")};
stroke-linejoin: ${(props) => (props.$isVersion ? "unset" : "round")};
${(props) =>
props.$isVersion &&
css`
stroke-width: 2;
`}
}
`;
export default VersionMarkIcon;

View File

@ -11,7 +11,7 @@
},
"dependencies": {
"@babel/runtime": "^7.15.4",
"axios": "^0.21.4",
"axios": "^0.22.0",
"cross-fetch": "3.1.5",
"fast-deep-equal": "^3.1.3",
"global": "^4.4.0",

View File

@ -1,8 +1,7 @@
import { makeAutoObservable } from "mobx";
import { getUserRole } from "@docspace/client/src/helpers/people-helpers";
import { getUserById } from "@docspace/common/api/people";
import { combineUrl } from "@docspace/common/utils";
import { combineUrl, getUserRole } from "@docspace/common/utils";
import { FolderType } from "@docspace/common/constants";
import config from "PACKAGE_FILE";
import Filter from "../api/people/filter";

View File

@ -4,7 +4,7 @@ import { isMobile } from "react-device-detect";
import TopLoaderService from "@docspace/components/top-loading-indicator";
import { Encoder } from "./encoder";
import FilesFilter from "../api/files/filter";
import combineUrlFunc from "./combineUrl";
export const toUrlParams = (obj, skipNull) => {
let str = "";
for (var key in obj) {
@ -147,7 +147,7 @@ export function isMe(user, userName) {
);
}
export function isAdmin(currentUser, currentProductId) {
export function isAdmin(currentUser) {
return (
currentUser.isAdmin ||
currentUser.isOwner ||
@ -155,7 +155,17 @@ export function isAdmin(currentUser, currentProductId) {
);
}
import combineUrlFunc from "./combineUrl";
export const getUserRole = (user) => {
if (user.isOwner) return "owner";
else if (isAdmin(user))
//TODO: Change to People Product Id const
return "admin";
//TODO: Need refactoring
else if (user.isVisitor) return "user";
else return "manager";
};
export const combineUrl = combineUrlFunc;
@ -163,8 +173,8 @@ export function getCookie(name) {
let matches = document.cookie.match(
new RegExp(
"(?:^|; )" +
name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") +
"=([^;]*)"
name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") +
"=([^;]*)"
)
);
return matches ? decodeURIComponent(matches[1]) : undefined;
@ -326,24 +336,6 @@ export function convertLanguage(key) {
return key;
}
export function getFolderOptions(folderId, filter) {
if (folderId && typeof folderId === "string") {
folderId = encodeURIComponent(folderId.replace(/\\\\/g, "\\"));
}
const params =
filter && filter instanceof FilesFilter
? `${folderId}?${filter.toApiUrlParams()}`
: folderId;
const options = {
method: "get",
url: `/files/${params}`,
};
return options;
}
export function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
@ -357,7 +349,7 @@ export function isElementInViewport(el) {
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}

View File

@ -104,7 +104,8 @@ function Editor({
);
}
const errorText = typeof error === "string" ? error : error.errorMessage;
toastr.error(errorText);
errorText && toastr.error(errorText);
}
}, [mfReady, error]);

View File

@ -6,13 +6,13 @@ import Link from "@docspace/components/link";
import CodeInput from "@docspace/components/code-input";
import { Trans } from "react-i18next";
import { ReactSVG } from "react-svg";
import { LoginContainer, LoginFormWrapper } from "./StyledLogin";
import { LoginFormWrapper } from "./StyledLogin";
import BarLogo from "../../../../../public/images/danger.alert.react.svg";
import { Dark, Base } from "@docspace/components/themes";
import { getBgPattern } from "@docspace/common/utils";
import { useMounted } from "../helpers/useMounted";
import useIsomorphicLayoutEffect from "../hooks/useIsomorphicLayoutEffect";
import LoginContainer from '@docspace/common/components/LoginContainer'
interface IBarProp {
t: TFuncType;

View File

@ -1,5 +1,5 @@
import styled, { css } from "styled-components";
import { tablet, hugeMobile } from "@docspace/components/utils/device";
import { tablet } from "@docspace/components/utils/device";
export const ButtonsWrapper = styled.div`
display: flex;
@ -12,266 +12,7 @@ export const ButtonsWrapper = styled.div`
}
`;
export const LoginContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin: 56px auto 0 auto;
max-width: 960px;
.remember-wrapper {
max-width: 142px;
display: flex;
flex-direction: row;
align-items: center;
}
.buttonWrapper {
margin-bottom: 8px;
width: 100%;
}
@media ${tablet} {
max-width: 480px;
}
@media ${hugeMobile} {
margin: 0 auto 0 auto;
max-width: 311px;
}
.socialButton {
min-height: 40px;
}
.or-label,
.login-or-access-text {
min-height: 18px;
}
.login-or-access-text{
text-transform: lowercase;
}
.recover-link {
min-height: 19px;
}
.greeting-title {
width: 100%;
padding-bottom: 32px;
min-height: 32px;
color: ${(props) => props.theme.login.headerColor};
@media ${hugeMobile} {
padding-top: 32px;
}
}
.more-label {
padding-top: 18px;
}
.or-label {
color: ${(props) => props.theme.login.orTextColor};
margin: 0 32px;
}
.line {
display: flex;
width: 320px;
align-items: center;
color: ${(props) => props.theme.login.orLineColor};
padding: 32px 0;
@media ${tablet} {
width: 480px;
}
@media ${hugeMobile} {
width: 311px;
}
}
.line:before,
.line:after {
content: "";
flex-grow: 1;
background: ${(props) => props.theme.login.orLineColor};
height: 1px;
font-size: 0px;
line-height: 0px;
margin: 0px;
}
.code-input-container {
margin-top: 32px;
text-align: center;
}
.not-found-code {
margin-top: 32px;
}
.code-input-bar {
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
margin-top: 16px;
padding: 14px 12px;
text-align: center;
font-weight: 600;
font-size: 11px;
line-height: 12px;
color: #333;
svg {
margin: 8px;
}
}
.code-input-bar.warning {
background: #f7e6be;
margin-bottom: 16px;
}
.code-input-bar.error {
background: #f7cdbe;
}
.auth-form-container {
width: 320px;
@media ${tablet} {
width: 100%;
}
@media ${hugeMobile} {
margin: 32px 0 0 0;
width: 100%;
}
.field-body{
height: 48px;
input, .password-input > div {
background: ${(props) => props.theme.input.backgroundColor};
color: ${(props) => props.theme.input.color};
border-color: ${(props) => props.theme.input.borderColor};
}
}
.login-forgot-wrapper {
margin-bottom: 14px;
.login-checkbox-wrapper {
display: flex;
//align-items: center;
.login-checkbox {
display: flex;
align-items: flex-start;
svg{
margin-right: 8px !important;
rect{
fill: ${(props) => props.theme.checkbox.fillColor};
stroke: ${(props) => props.theme.checkbox.borderColor};
}
path{
fill: ${(props) => props.theme.checkbox.arrowColor};
}
}
.help-button{
svg {
path {
fill: ${(props) => props.theme.login.helpButton};
}
}
}
.checkbox-text{
color: ${(props) => props.theme.checkbox.arrowColor};
}
label {
justify-content: center;
}
}
.remember-helper-wrapper {
display: flex;
gap: 4px;
}
}
.login-link {
line-height: 18px;
margin-left: auto;
}
}
.login-button {
margin-top: 8px;
}
.login-button-dialog {
margin-right: 8px;
}
.login-bottom-border {
width: 100%;
height: 1px;
background: #eceef1;
}
.login-bottom-text {
margin: 0 8px;
}
.login-or-access {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
& > :first-child {
margin-top: 24px;
}
}
}
.logo-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 46px;
padding-bottom: 64px;
svg {
path:last-child {
fill: ${(props) => props.theme.client.home.logoColor};
}
}
@media ${hugeMobile} {
display: none;
}
}
.workspace-title{
color: ${(props) => props.theme.login.titleColor};
margin-bottom: 16px;
@media ${hugeMobile} {
margin-top: 32px;
}
}
.code-description{
color: ${(props) => props.theme.login.textColor};
}
`;
interface ILoginFormWrapperProps {
enabledJoin?: boolean;

View File

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

View File

@ -2486,12 +2486,13 @@ public class FileStorageService<T> //: IFileStorageService
return _fileSharing.GetSharedInfoShortFolderAsync(folderId);
}
public async Task SetAceObjectAsync(AceCollection<T> aceCollection, bool notify)
public async Task<string> SetAceObjectAsync(AceCollection<T> aceCollection, bool notify)
{
var fileDao = GetFileDao();
var folderDao = GetFolderDao();
var entries = new List<FileEntry<T>>();
string warning = null;
foreach (var fileId in aceCollection.Files)
{
@ -2507,7 +2508,9 @@ public class FileStorageService<T> //: IFileStorageService
{
try
{
var changed = await _fileSharingAceHelper.SetAceObjectAsync(aceCollection.Aces, entry, notify, aceCollection.Message, aceCollection.AdvancedSettings);
var (changed, warningMessage) = await _fileSharingAceHelper.SetAceObjectAsync(aceCollection.Aces, entry, notify, aceCollection.Message, aceCollection.AdvancedSettings);
warning ??= warningMessage;
if (changed)
{
foreach (var ace in aceCollection.Aces)
@ -2533,6 +2536,8 @@ public class FileStorageService<T> //: IFileStorageService
throw GenerateException(e);
}
}
return warning;
}
public async Task RemoveAceAsync(List<T> filesId, List<T> foldersId)
@ -2602,7 +2607,7 @@ public class FileStorageService<T> //: IFileStorageService
try
{
var changed = await _fileSharingAceHelper.SetAceObjectAsync(aces, room, false, null, null);
var (changed, _) = await _fileSharingAceHelper.SetAceObjectAsync(aces, room, false, null, null);
if (changed)
{
_filesMessageService.Send(room, GetHttpHeaders(), MessageAction.RoomInvintationUpdateAccess, room.Title, GetAccessString(share));
@ -2633,7 +2638,7 @@ public class FileStorageService<T> //: IFileStorageService
try
{
var changed = await _fileSharingAceHelper.SetAceObjectAsync(aces, file, false, null, null);
var (changed, _) = await _fileSharingAceHelper.SetAceObjectAsync(aces, file, false, null, null);
if (changed)
{
_filesMessageService.Send(file, GetHttpHeaders(), MessageAction.FileExternalLinkAccessUpdated, file.Title, GetAccessString(share));
@ -2767,16 +2772,18 @@ public class FileStorageService<T> //: IFileStorageService
try
{
var aces = new List<AceWrapper>
{
new AceWrapper
{
new AceWrapper
{
Access = FileShare.Read,
Id = recipient.Id,
SubjectGroup = false,
}
};
Access = FileShare.Read,
Id = recipient.Id,
SubjectGroup = false,
}
};
showSharingSettings |= await _fileSharingAceHelper.SetAceObjectAsync(aces, file, false, null, null);
var (changed, _) = await _fileSharingAceHelper.SetAceObjectAsync(aces, file, false, null, null);
showSharingSettings |= changed;
if (showSharingSettings)
{
foreach (var ace in aces)

View File

@ -30,14 +30,14 @@ public static class DocSpaceHelper
{
public static HashSet<FileShare> PaidRights { get; } = new HashSet<FileShare> { FileShare.RoomAdmin };
private static readonly HashSet<FileShare> _fillingFormRoomConstraints
= new HashSet<FileShare> { FileShare.RoomAdmin, FileShare.FillForms, FileShare.Read, FileShare.None };
private static readonly HashSet<FileShare> _collaborationRoomConstraints
= new HashSet<FileShare> { FileShare.RoomAdmin, FileShare.Editing, FileShare.Read, FileShare.None };
private static readonly HashSet<FileShare> _reviewRoomConstraints
= new HashSet<FileShare> { FileShare.RoomAdmin, FileShare.Review, FileShare.Comment, FileShare.Read, FileShare.None };
private static readonly HashSet<FileShare> _viewOnlyRoomConstraints
= new HashSet<FileShare> { FileShare.RoomAdmin, FileShare.Read, FileShare.None };
private static readonly List<FileShare> _fillingFormRoomRoles
= new List<FileShare> { FileShare.RoomAdmin, FileShare.FillForms, FileShare.Read, FileShare.None };
private static readonly List<FileShare> _collaborationRoomRoles
= new List<FileShare> { FileShare.RoomAdmin, FileShare.Editing, FileShare.Read, FileShare.None };
private static readonly List<FileShare> _reviewRoomRoles
= new List<FileShare> { FileShare.RoomAdmin, FileShare.Review, FileShare.Comment, FileShare.Read, FileShare.None };
private static readonly List<FileShare> _viewOnlyRoomRoles
= new List<FileShare> { FileShare.RoomAdmin, FileShare.Read, FileShare.None };
public static bool IsRoom(FolderType folderType)
{
@ -54,21 +54,29 @@ public static class DocSpaceHelper
return room != null && room.Private;
}
public static bool ValidateShare(FolderType folderType, FileShare fileShare, bool isUser)
public static bool ValidateShare(FolderType folderType, FileShare fileShare)
{
if (isUser && PaidRights.Contains(fileShare))
{
return false;
}
return folderType switch
{
FolderType.CustomRoom => true,
FolderType.FillingFormsRoom => _fillingFormRoomConstraints.Contains(fileShare),
FolderType.EditingRoom => _collaborationRoomConstraints.Contains(fileShare),
FolderType.ReviewRoom => _reviewRoomConstraints.Contains(fileShare),
FolderType.ReadOnlyRoom => _viewOnlyRoomConstraints.Contains(fileShare),
FolderType.FillingFormsRoom => _fillingFormRoomRoles.Contains(fileShare),
FolderType.EditingRoom => _collaborationRoomRoles.Contains(fileShare),
FolderType.ReviewRoom => _reviewRoomRoles.Contains(fileShare),
FolderType.ReadOnlyRoom => _viewOnlyRoomRoles.Contains(fileShare),
_ => false
};
}
public static FileShare GetHighFreeRole(FolderType folderType)
{
return folderType switch
{
FolderType.CustomRoom => FileShare.Editing,
FolderType.FillingFormsRoom => _fillingFormRoomRoles[1],
FolderType.EditingRoom => _collaborationRoomRoles[1],
FolderType.ReviewRoom => _reviewRoomRoles[1],
FolderType.ReadOnlyRoom => _viewOnlyRoomRoles[1],
_ => FileShare.None
};
}
}

View File

@ -780,6 +780,15 @@ namespace ASC.Files.Core.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to The role is only available to a paid user.
/// </summary>
public static string ErrorMessage_PaidRole {
get {
return ResourceManager.GetString("ErrorMessage_PaidRole", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You don&apos;t have permission to copy to this folder.
/// </summary>

View File

@ -357,6 +357,9 @@
<data name="ErrorMessage_InvintationLink" xml:space="preserve">
<value>The invitation link is invalid or it's validity has expired</value>
</data>
<data name="ErrorMessage_PaidRole" xml:space="preserve">
<value>The role is only available to a paid user</value>
</data>
<data name="ErrorMessage_SecurityException_CopyToFolder" xml:space="preserve">
<value>You don't have permission to copy to this folder</value>
</data>

View File

@ -46,6 +46,7 @@ public class FileSharingAceHelper<T>
private readonly StudioNotifyService _studioNotifyService;
private readonly UsersInRoomChecker _usersInRoomChecker;
private readonly UserManagerWrapper _userManagerWrapper;
private readonly CountRoomAdminChecker _countRoomAdminChecker;
private readonly ILogger _logger;
public FileSharingAceHelper(
@ -65,7 +66,8 @@ public class FileSharingAceHelper<T>
StudioNotifyService studioNotifyService,
ILoggerProvider loggerProvider,
UsersInRoomChecker usersInRoomChecker,
UserManagerWrapper userManagerWrapper)
UserManagerWrapper userManagerWrapper,
CountRoomAdminChecker countRoomAdminChecker)
{
_fileSecurity = fileSecurity;
_coreBaseSettings = coreBaseSettings;
@ -84,9 +86,10 @@ public class FileSharingAceHelper<T>
_usersInRoomChecker = usersInRoomChecker;
_logger = loggerProvider.CreateLogger("ASC.Files");
_userManagerWrapper = userManagerWrapper;
_countRoomAdminChecker = countRoomAdminChecker;
}
public async Task<bool> SetAceObjectAsync(List<AceWrapper> aceWrappers, FileEntry<T> entry, bool notify, string message, AceAdvancedSettingsWrapper advancedSettings)
public async Task<(bool, string)> SetAceObjectAsync(List<AceWrapper> aceWrappers, FileEntry<T> entry, bool notify, string message, AceAdvancedSettingsWrapper advancedSettings)
{
if (entry == null)
{
@ -108,18 +111,27 @@ public class FileSharingAceHelper<T>
var recipients = new Dictionary<Guid, FileShare>();
var usersWithoutRight = new List<Guid>();
var changed = false;
string warning = null;
var shares = (await _fileSecurity.GetSharesAsync(entry)).ToList();
var i = 1;
foreach (var w in aceWrappers.OrderByDescending(ace => ace.SubjectGroup))
{
if (entry is Folder<T> folder && DocSpaceHelper.IsRoom(folder.FolderType) &&
!DocSpaceHelper.ValidateShare(folder.FolderType, w.Access, _userManager.IsUser(w.Id)))
!DocSpaceHelper.ValidateShare(folder.FolderType, w.Access))
{
continue;
}
if (!await ProcessEmailAceAsync(w))
if (_userManager.IsUser(w.Id) && DocSpaceHelper.PaidRights.Contains(w.Access))
{
throw new InvalidOperationException(FilesCommonResource.ErrorMessage_PaidRole);
};
var (success, msg) = await ProcessEmailAceAsync(w, entry);
warning ??= msg;
if (!success)
{
continue;
}
@ -253,7 +265,7 @@ public class FileSharingAceHelper<T>
await _fileMarker.RemoveMarkAsNewAsync(entry, userId);
}
return changed;
return (changed, warning);
}
public async Task RemoveAceAsync(FileEntry<T> entry)
@ -279,28 +291,49 @@ public class FileSharingAceHelper<T>
await _fileMarker.RemoveMarkAsNewAsync(entry);
}
private async Task<bool> ProcessEmailAceAsync(AceWrapper ace)
private async Task<(bool, string)> ProcessEmailAceAsync(AceWrapper ace, FileEntry<T> entry)
{
if (string.IsNullOrEmpty(ace.Email))
{
return true;
return (true, null);
}
var type = DocSpaceHelper.PaidRights.Contains(ace.Access) ? EmployeeType.RoomAdmin : EmployeeType.User;
UserInfo user = null;
var room = entry is Folder<T> folder && DocSpaceHelper.IsRoom(folder.FolderType) ? folder : null;
if (room == null)
{
return (false, null);
}
UserInfo user;
try
{
user = await _userManagerWrapper.AddInvitedUserAsync(ace.Email, type);
if (DocSpaceHelper.PaidRights.Contains(ace.Access))
{
await _countRoomAdminChecker.CheckAppend();
user = await _userManagerWrapper.AddInvitedUserAsync(ace.Email, EmployeeType.RoomAdmin);
}
else
{
user = await _userManagerWrapper.AddInvitedUserAsync(ace.Email, EmployeeType.User);
}
}
catch
catch(TenantQuotaException e)
{
return false;
ace.Access = DocSpaceHelper.GetHighFreeRole(room.FolderType);
user = await _userManagerWrapper.AddInvitedUserAsync(ace.Email, EmployeeType.User);
ace.Id = user.Id;
return (true, e.Message);
}
catch(Exception e)
{
return (false, e.Message);
}
ace.Id = user.Id;
return true;
return (true, null);
}
}

View File

@ -317,9 +317,11 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
/// Room security info
/// </returns>
[HttpPut("rooms/{id}/share")]
public async Task<IEnumerable<FileShareDto>> SetRoomSecurityAsync(T id, RoomInvitationRequestDto inDto)
public async Task<RoomSecurityDto> SetRoomSecurityAsync(T id, RoomInvitationRequestDto inDto)
{
ErrorIfNotDocSpace();
ErrorIfNotDocSpace();
var result = new RoomSecurityDto();
if (inDto.Invitations != null && inDto.Invitations.Any())
{
@ -333,10 +335,12 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
Message = inDto.Message
};
await _fileStorageService.SetAceObjectAsync(aceCollection, inDto.Notify);
}
result.Warning = await _fileStorageService.SetAceObjectAsync(aceCollection, inDto.Notify);
}
result.Members = await GetRoomSecurityInfoAsync(id).ToListAsync();
return await GetRoomSecurityInfoAsync(id).ToListAsync();
return result;
}
/// <summary>

View File

@ -2293,7 +2293,7 @@ namespace ASC.Web.Core.PublicResources {
}
/// <summary>
/// Looks up a localized string similar to The number of managers should not exceed {0}.
/// Looks up a localized string similar to The number of admins should not exceed {0}.
/// </summary>
public static string TariffsFeature_manager_exception {
get {

View File

@ -889,7 +889,7 @@
<value>The number of rooms should not exceed {0}</value>
</data>
<data name="TariffsFeature_manager_exception" xml:space="preserve">
<value>The number of managers should not exceed {0}</value>
<value>The number of admins should not exceed {0}</value>
</data>
<data name="TariffsFeature_total_size_exception" xml:space="preserve">
<value>The used storage size should not exceed {0}</value>
@ -930,4 +930,4 @@
<data name="ConsumersTelegramInstructionV11" xml:space="preserve">
<value>To receive portal notifications via Telegram, enable Telegram bot. You can create a new bot using BotFather in Telegram Desktop. To use this bot, portal users need to enable Telegram notifications on their Profile Page. {0}Paste bots username and the token you received in the fields below.</value>
</data>
</root>
</root>

1544
yarn.lock

File diff suppressed because it is too large Load Diff