Merge branch 'develop' into feature/tg-reports

This commit is contained in:
Alexey Safronov 2023-09-28 11:26:19 +04:00
commit 114bca4692
52 changed files with 2080 additions and 882 deletions

View File

@ -46,7 +46,7 @@ if [ "$#" -ge "2" ]; then
if [ -f "${OPENRESTY}/onlyoffice-proxy-ssl.conf.template" ]; then
cp -f ${OPENRESTY}/onlyoffice-proxy-ssl.conf.template ${OPENRESTY}/onlyoffice-proxy.conf
ENVIRONMENT=$(grep -oP 'ENVIRONMENT=\K.*' /usr/lib/systemd/system/${PRODUCT}-api.service)
ENVIRONMENT=$(grep -oP 'ENVIRONMENT=\K.*' $(dirname $(dpkg-query -L ${PRODUCT}-api | grep systemd/system/))/${PRODUCT}-api.service)
sed -i "s/\(\"portal\":\).*/\1 \"https:\/\/${DOMAIN:-$(hostname --fqdn)}\"/" /etc/onlyoffice/docspace/appsettings.$ENVIRONMENT.json
sed -i "s~\(ssl_certificate \).*;~\1${CERTIFICATE_FILE};~g" ${OPENRESTY}/onlyoffice-proxy.conf
sed -i "s~\(ssl_certificate_key \).*;~\1${PRIVATEKEY_FILE};~g" ${OPENRESTY}/onlyoffice-proxy.conf

View File

@ -157,6 +157,7 @@ public class EmployeeFullDto : EmployeeDto
/// <summary>Portal used space</summary>
/// <type>System.Double, System</type>
public double UsedSpace { get; set; }
public bool? Shared { get; set; }
public static new EmployeeFullDto GetSample()
{
@ -257,6 +258,7 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
return lambda;
}
public async Task<EmployeeFullDto> GetSimple(UserInfo userInfo)
{
var result = new EmployeeFullDto
@ -279,7 +281,7 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
return result;
}
public async Task<EmployeeFullDto> GetFullAsync(UserInfo userInfo)
public async Task<EmployeeFullDto> GetFullAsync(UserInfo userInfo, bool? shared = null)
{
var currentType = await _userManager.GetUserTypeAsync(userInfo.Id);
@ -300,7 +302,8 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
IsOwner = userInfo.IsOwner(_context.Tenant),
IsCollaborator = currentType is EmployeeType.Collaborator,
IsLDAP = userInfo.IsLDAP(),
IsSSO = userInfo.IsSSO()
IsSSO = userInfo.IsSSO(),
Shared = shared,
};
await InitAsync(result, userInfo);
@ -373,6 +376,7 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
return result;
}
private async Task FillGroupsAsync(EmployeeFullDto result, UserInfo userInfo)
{
if (!_context.Check("groups") && !_context.Check("department"))

View File

@ -39,5 +39,9 @@
"SubmenuHistory": "History",
"SystemProperties": "System properties",
"UsersInRoom": "Users in room",
"Versions": "Versions"
"Versions": "Versions",
"Administration": "Administration",
"Users": "Users",
"ExpectUsers": "Expect users",
"InfoBanner": "The list of invited users includes the owner and/or admins of this DocSpace with full access to all rooms. The owner and/or administrator cannot be assigned other access rights. Once added to the room, they will be notified of all changes."
}

View File

@ -15,7 +15,7 @@ const DeleteLinkDialogComponent = (props) => {
setIsVisible,
tReady,
roomId,
setExternalLinks,
deleteExternalLink,
editExternalLink,
} = props;
@ -45,8 +45,8 @@ const DeleteLinkDialogComponent = (props) => {
newLink.access = 0;
editExternalLink(roomId, newLink)
.then((res) => {
setExternalLinks(res);
.then(() => {
deleteExternalLink(newLink.sharedTo.id);
toastr.success(t("Files:LinkDeletedSuccessfully"));
})
.catch((err) => toastr.error(err?.message))
@ -104,7 +104,7 @@ export default inject(({ auth, dialogsStore, publicRoomStore }) => {
setDeleteLinkDialogVisible: setIsVisible,
linkParams,
} = dialogsStore;
const { editExternalLink, setExternalLinks } = publicRoomStore;
const { editExternalLink, deleteExternalLink } = publicRoomStore;
return {
visible,
@ -112,6 +112,6 @@ export default inject(({ auth, dialogsStore, publicRoomStore }) => {
roomId: selectionParentRoom.id,
link: linkParams.link,
editExternalLink,
setExternalLinks,
deleteExternalLink,
};
})(observer(DeleteLinkDialog));

View File

@ -6,19 +6,27 @@ import Heading from "@docspace/components/heading";
import Aside from "@docspace/components/aside";
import IconButton from "@docspace/components/icon-button";
import { ShareAccessRights } from "@docspace/common/constants";
import PeopleSelector from "@docspace/client/src/components/PeopleSelector";
import Selector from "@docspace/components/selector";
import { withTranslation } from "react-i18next";
import Loaders from "@docspace/common/components/Loaders";
import withLoader from "../../../HOCs/withLoader";
import toastr from "@docspace/components/toast/toastr";
import Filter from "@docspace/common/api/people/filter";
import { getMembersList } from "@docspace/common/api/people";
import { getUserRole } from "@docspace/common/utils";
import DefaultUserPhoto from "PUBLIC_DIR/images/default_user_photo_size_82-82.png";
import CatalogAccountsReactSvgUrl from "PUBLIC_DIR/images/catalog.accounts.react.svg?url";
import EmptyScreenPersonsSvgUrl from "PUBLIC_DIR/images/empty_screen_persons.svg?url";
import EmptyScreenPersonsSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_persons_dark.svg?url";
let timer = null;
const AddUsersPanel = ({
isEncrypted,
defaultAccess,
onClose,
onParentPanelClose,
shareDataItems,
tempDataItems,
setDataItems,
t,
@ -29,6 +37,7 @@ const AddUsersPanel = ({
theme,
withoutBackground,
withBlur,
roomId,
}) => {
const accessRight = defaultAccess
? defaultAccess
@ -61,25 +70,21 @@ const AddUsersPanel = ({
const items = [];
for (let item of users) {
const currentItem = shareDataItems.find((x) => x.sharedTo.id === item.id);
const currentAccess =
item.isOwner || item.isAdmin
? ShareAccessRights.RoomManager
: access.access;
if (!currentItem) {
const currentAccess =
item.isOwner || item.isAdmin
? ShareAccessRights.RoomManager
: access.access;
const newItem = {
access: currentAccess,
email: item.email,
id: item.id,
displayName: item.label,
avatar: item.avatar,
isOwner: item.isOwner,
isAdmin: item.isAdmin,
};
items.push(newItem);
}
const newItem = {
access: currentAccess,
email: item.email,
id: item.id,
displayName: item.label,
avatar: item.avatar,
isOwner: item.isOwner,
isAdmin: item.isAdmin,
};
items.push(newItem);
}
if (users.length > items.length)
@ -89,22 +94,128 @@ const AddUsersPanel = ({
onClose();
};
const onUserSelect = (owner) => {
const ownerItem = shareDataItems.find((x) => x.isOwner);
ownerItem.sharedTo = owner[0];
if (owner[0].key) {
owner[0].id = owner[0].key;
}
setDataItems(shareDataItems);
onClose();
};
const selectedAccess = accessOptions.filter(
(access) => access.access === accessRight
)[0];
const [itemsList, setItemsList] = useState(null);
const [searchValue, setSearchValue] = useState("");
const [hasNextPage, setHasNextPage] = useState(true);
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
const [total, setTotal] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const cleanTimer = () => {
timer && clearTimeout(timer);
timer = null;
};
useEffect(() => {
loadNextPage(0);
}, []);
useEffect(() => {
if (isLoading) {
cleanTimer();
timer = setTimeout(() => {
setIsLoading(true);
}, 100);
} else {
cleanTimer();
setIsLoading(false);
}
return () => {
cleanTimer();
};
}, [isLoading]);
const onSearch = (value) => {
setSearchValue(value);
loadNextPage(0, value);
};
const onClearSearch = () => {
setSearchValue("");
loadNextPage(0, "");
};
const toListItem = (item) => {
const {
id,
email,
avatar,
icon,
displayName,
hasAvatar,
isOwner,
isAdmin,
isVisitor,
isCollaborator,
} = item;
const role = getUserRole(item);
const userAvatar = hasAvatar ? avatar : DefaultUserPhoto;
return {
id,
email,
avatar: userAvatar,
icon,
label: displayName || email,
role,
isOwner,
isAdmin,
isVisitor,
isCollaborator,
};
};
const loadNextPage = (startIndex, search = searchValue) => {
const pageCount = 100;
setIsNextPageLoading(true);
if (startIndex === 0) {
setIsLoading(true);
}
const currentFilter = getFilterWithOutDisabledUser();
currentFilter.page = startIndex / pageCount;
currentFilter.pageCount = pageCount;
currentFilter.excludeShared = true;
if (!!search.length) {
currentFilter.search = search;
}
getMembersList(roomId, currentFilter)
.then((response) => {
let newItems = startIndex ? itemsList : [];
let totalDifferent = startIndex ? response.total - total : 0;
const items = response.items.map((item) => toListItem(item));
newItems = [...newItems, ...items];
const newTotal = response.total - totalDifferent;
setHasNextPage(newItems.length < newTotal);
setItemsList(newItems);
setTotal(newTotal);
setIsNextPageLoading(false);
setIsLoading(false);
})
.catch((error) => console.log(error));
};
const emptyScreenImage = theme.isBase
? EmptyScreenPersonsSvgUrl
: EmptyScreenPersonsSvgDarkUrl;
return (
<>
<Backdrop
@ -121,18 +232,45 @@ const AddUsersPanel = ({
onClose={onClosePanels}
withoutBodyScroll
>
<PeopleSelector
isMultiSelect={isMultiSelect}
onAccept={onUsersSelect}
<Selector
headerLabel={t("PeopleSelector:ListAccounts")}
onBackClick={onBackClick}
accessRights={accessOptions}
searchPlaceholder={t("Common:Search")}
searchValue={searchValue}
onSearch={onSearch}
onClearSearch={onClearSearch}
items={itemsList}
isMultiSelect={isMultiSelect}
acceptButtonLabel={t("Common:AddButton")}
selectedAccessRight={selectedAccess}
onCancel={onClosePanels}
withCancelButton={!isMultiSelect}
withAccessRights={isMultiSelect}
onAccept={onUsersSelect}
withSelectAll={isMultiSelect}
filter={getFilterWithOutDisabledUser}
selectAllLabel={t("PeopleSelector:AllAccounts")}
selectAllIcon={CatalogAccountsReactSvgUrl}
withAccessRights={isMultiSelect}
accessRights={accessOptions}
selectedAccessRight={selectedAccess}
withCancelButton={!isMultiSelect}
cancelButtonLabel={t("Common:CancelButton")}
onCancel={onClosePanels}
emptyScreenImage={emptyScreenImage}
emptyScreenHeader={t("PeopleSelector:EmptyHeader")}
emptyScreenDescription={t("PeopleSelector:EmptyDescription")}
searchEmptyScreenImage={emptyScreenImage}
searchEmptyScreenHeader={t("People:NotFoundUsers")}
searchEmptyScreenDescription={t("SearchEmptyDescription")}
hasNextPage={hasNextPage}
isNextPageLoading={isNextPageLoading}
loadNextPage={loadNextPage}
totalItems={total}
isLoading={isLoading}
searchLoader={<Loaders.SelectorSearchLoader />}
rowLoader={
<Loaders.SelectorRowLoader
isMultiSelect={false}
isContainer={isLoading}
isUser={true}
/>
}
/>
</Aside>
</>
@ -146,7 +284,9 @@ AddUsersPanel.propTypes = {
};
export default inject(({ auth }) => {
return { theme: auth.settingsStore.theme };
return {
theme: auth.settingsStore.theme,
};
})(
observer(
withTranslation(["SharingPanel", "PeopleTranslations", "Common"])(

View File

@ -21,20 +21,18 @@ import LinkBlock from "./LinkBlock";
import ToggleBlock from "./ToggleBlock";
import PasswordAccessBlock from "./PasswordAccessBlock";
import LimitTimeBlock from "./LimitTimeBlock";
import { LinkType } from "../../../helpers/constants";
import { isMobileOnly } from "react-device-detect";
const EditLinkPanel = (props) => {
const {
t,
roomId,
linkId,
isEdit,
visible,
password,
setIsVisible,
editExternalLink,
setExternalLinks,
setExternalLink,
shareLink,
unsavedChangesDialogVisible,
setUnsavedChangesDialog,
@ -47,7 +45,7 @@ const EditLinkPanel = (props) => {
const [isLoading, setIsLoading] = useState(false);
const linkTitle = link.sharedTo.title ?? "";
const linkTitle = link?.sharedTo?.title ?? "";
const [linkNameValue, setLinkNameValue] = useState(linkTitle);
const [passwordValue, setPasswordValue] = useState(password);
const [expirationDate, setExpirationDate] = useState(date);
@ -93,7 +91,9 @@ const EditLinkPanel = (props) => {
return;
}
const newLink = JSON.parse(JSON.stringify(link));
const externalLink = link ?? { access: 2, sharedTo: {} };
const newLink = JSON.parse(JSON.stringify(externalLink));
newLink.sharedTo.title = linkNameValue;
newLink.sharedTo.password = passwordAccessIsChecked ? passwordValue : null;
@ -102,10 +102,8 @@ const EditLinkPanel = (props) => {
setIsLoading(true);
editExternalLink(roomId, newLink)
.then((res) => {
setExternalLinks(res);
const link = res.find((l) => l?.sharedTo?.id === linkId);
.then((link) => {
setExternalLink(link);
if (isEdit) {
copy(linkValue);
@ -271,25 +269,22 @@ export default inject(({ auth, dialogsStore, publicRoomStore }) => {
setUnsavedChangesDialog,
linkParams,
} = dialogsStore;
const { externalLinks, editExternalLink, setExternalLinks } = publicRoomStore;
const { externalLinks, editExternalLink, setExternalLink } = publicRoomStore;
const { isEdit } = linkParams;
const linkId = linkParams?.link?.sharedTo?.id;
const link = externalLinks.find((l) => l?.sharedTo?.id === linkId);
const template = externalLinks.find(
(t) =>
t?.sharedTo?.isTemplate && t?.sharedTo?.linkType === LinkType.External
);
const shareLink = link?.sharedTo?.shareLink ?? template?.sharedTo?.shareLink;
const shareLink = link?.sharedTo?.shareLink;
return {
visible: editLinkPanelIsVisible,
setIsVisible: setEditLinkPanelIsVisible,
isEdit,
linkId: link?.sharedTo?.id ?? template?.sharedTo?.id,
linkId: link?.sharedTo?.id,
editExternalLink,
roomId: selectionParentRoom.id,
setExternalLinks,
setExternalLink,
isLocked: !!link?.sharedTo?.password,
password: link?.sharedTo?.password ?? "",
date: link?.sharedTo?.expirationDate,
@ -298,7 +293,7 @@ export default inject(({ auth, dialogsStore, publicRoomStore }) => {
externalLinks,
unsavedChangesDialogVisible,
setUnsavedChangesDialog,
link: link ?? template,
link,
language: auth.language,
};
})(

View File

@ -181,6 +181,13 @@ const StyledRow = styled.div`
margin-right: 0;
`}
}
.combo-button-label {
color: ${(props) => props.theme.text.disableColor};
}
.combo-buttons_expander-icon path {
fill: ${(props) => props.theme.text.disableColor};
}
`;
const StyledInviteInput = styled.div`

View File

@ -24,6 +24,9 @@ import InviteInput from "./sub-components/InviteInput";
import ExternalLinks from "./sub-components/ExternalLinks";
import Scrollbar from "@docspace/components/scrollbar";
import { LinkType } from "../../../helpers/constants";
import InfoBar from "./sub-components/InfoBar";
const InvitePanel = ({
folders,
getFolderInfo,
@ -52,14 +55,16 @@ const InvitePanel = ({
const [selectedRoom, setSelectedRoom] = useState(null);
const [hasErrors, setHasErrors] = useState(false);
const [shareLinks, setShareLinks] = useState([]);
const [roomUsers, setRoomUsers] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [externalLinksVisible, setExternalLinksVisible] = useState(false);
const [scrollAllPanelContent, setScrollAllPanelContent] = useState(false);
const [activeLink, setActiveLink] = useState({});
const [infoBarIsVisible, setInfoBarIsVisible] = useState(true);
const [addUsersPanelVisible, setAddUsersPanelVisible] = useState(false);
const [isMobileView, setIsMobileView] = useState(isMobileOnly);
const onCloseBar = () => setInfoBarIsVisible(false);
const inputsRef = useRef();
const invitePanelBodyRef = useRef();
@ -67,10 +72,6 @@ const InvitePanel = ({
setExternalLinksVisible(visible);
};
const onChangeActiveLink = (activeLink) => {
setActiveLink(activeLink);
};
const selectRoom = () => {
const room = folders.find((folder) => folder.id === roomId);
@ -84,26 +85,24 @@ const InvitePanel = ({
};
const getInfo = () => {
getRoomSecurityInfo(roomId).then((users) => {
let links = [];
getRoomSecurityInfo(roomId).then((links) => {
const link = links[0];
if (link) {
const { shareLink, id, title, expirationDate } = link.sharedTo;
users.map((user) => {
const { shareLink, id, title, expirationDate, linkType } =
user.sharedTo;
const activeLink = {
id,
title,
shareLink,
expirationDate,
access: link.access || defaultAccess,
};
if (!!shareLink && linkType === LinkType.Invite) {
links.push({
id,
title,
shareLink,
expirationDate,
access: user.access || defaultAccess,
});
}
});
onChangeExternalLinksVisible(!!links.length);
setShareLinks(links);
setRoomUsers(users);
setShareLinks([activeLink]);
setActiveLink(activeLink);
}
});
};
@ -241,6 +240,7 @@ const InvitePanel = ({
const roomType = selectedRoom ? selectedRoom.roomType : -1;
const hasInvitedUsers = !!inviteItems.length;
const hasAdmins = inviteItems.findIndex((u) => u.isAdmin || u.isOwner) > -1;
const bodyInvitePanel = useMemo(() => {
return (
@ -248,11 +248,12 @@ const InvitePanel = ({
<ExternalLinks
t={t}
shareLinks={shareLinks}
setShareLinks={setShareLinks}
getInfo={getInfo}
roomType={roomType}
onChangeExternalLinksVisible={onChangeExternalLinksVisible}
externalLinksVisible={externalLinksVisible}
onChangeActiveLink={onChangeActiveLink}
setActiveLink={setActiveLink}
activeLink={activeLink}
isMobileView={isMobileView}
/>
@ -260,13 +261,15 @@ const InvitePanel = ({
<InviteInput
t={t}
onClose={onClose}
roomUsers={roomUsers}
roomType={roomType}
inputsRef={inputsRef}
addUsersPanelVisible={addUsersPanelVisible}
setAddUsersPanelVisible={setAddUsersPanelVisible}
isMobileView={isMobileView}
/>
{infoBarIsVisible && hasAdmins && (
<InfoBar t={t} onClose={onCloseBar} />
)}
{hasInvitedUsers && (
<ItemsList
t={t}
@ -289,7 +292,6 @@ const InvitePanel = ({
onChangeExternalLinksVisible,
externalLinksVisible,
onClose,
roomUsers,
setHasErrors,
scrollAllPanelContent,
hasInvitedUsers,
@ -450,5 +452,6 @@ export default inject(({ auth, peopleStore, filesStore, dialogsStore }) => {
"Translations",
"Common",
"InfoPanel",
"PeopleSelector",
])(observer(InvitePanel))
);

View File

@ -21,6 +21,7 @@ const AccessSelector = ({
className,
standalone,
isMobileView,
noBorder = false,
}) => {
const [horizontalOrientation, setHorizontalOrientation] = useState(false);
const width = containerRef?.current?.offsetWidth - 32;
@ -64,7 +65,7 @@ const AccessSelector = ({
selectedOption={selectedOption}
onSelect={onSelectAccess}
accessOptions={filteredAccesses ? filteredAccesses : accessOptions}
noBorder={false}
noBorder={noBorder}
directionX="right"
directionY="bottom"
fixedDirection={true}
@ -82,7 +83,7 @@ const AccessSelector = ({
selectedOption={selectedOption}
onSelect={onSelectAccess}
accessOptions={filteredAccesses ? filteredAccesses : accessOptions}
noBorder={false}
noBorder={noBorder}
directionX="right"
directionY="top"
fixedDirection={true}

View File

@ -1,6 +1,6 @@
import MediaDownloadReactSvgUrl from "PUBLIC_DIR/images/media.download.react.svg?url";
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
import React, { useState, useEffect, useRef, useCallback } from "react";
import React, { useState, useRef, useCallback } from "react";
import { inject, observer } from "mobx-react";
import copy from "copy-to-clipboard";
@ -22,6 +22,7 @@ import {
StyledToggleButton,
StyledDescription,
} from "../StyledInvitePanel";
import { RoomsType, ShareAccessRights } from "@docspace/common/constants";
const ExternalLinks = ({
t,
@ -29,12 +30,12 @@ const ExternalLinks = ({
roomType,
defaultAccess,
shareLinks,
setShareLinks,
setInvitationLinks,
isOwner,
getInfo,
onChangeExternalLinksVisible,
externalLinksVisible,
onChangeActiveLink,
setActiveLink,
activeLink,
isMobileView,
}) => {
@ -42,44 +43,37 @@ const ExternalLinks = ({
const inputsRef = useRef();
useEffect(() => {
if (shareLinks[0]?.expirationDate) toggleLinks(false);
}, [shareLinks]);
const toggleLinks = (withCopy = true) => {
let link = null;
if (!shareLinks.length) return;
if (roomId === -1) {
link = shareLinks.find((l) => l.access === +defaultAccess);
onChangeActiveLink(link);
} else {
link = shareLinks[0];
!externalLinksVisible ? editLink() : disableLink();
}
const toggleLinks = () => {
!externalLinksVisible ? editLink() : disableLink();
onChangeExternalLinksVisible(!externalLinksVisible);
if (!externalLinksVisible && withCopy) copyLink(link?.shareLink);
};
const disableLink = () => {
setInvitationLinks(roomId, shareLinks[0].id, "Invite", 0);
setTimeout(() => getInfo(), 100);
setInvitationLinks(roomId, "Invite", 0, shareLinks[0].id);
setShareLinks([]);
};
const editLink = () => {
if (!shareLinks[0].expirationDate) {
setInvitationLinks(
roomId,
shareLinks[0].id,
"Invite",
shareLinks[0].access
);
}
onChangeActiveLink(shareLinks[0]);
const editLink = async () => {
const type =
roomType === RoomsType.PublicRoom
? ShareAccessRights.RoomManager
: ShareAccessRights.ReadOnly;
const link = await setInvitationLinks(roomId, "Invite", type);
const { shareLink, id, title, expirationDate } = link.sharedTo;
const activeLink = {
id,
title,
shareLink,
expirationDate,
access: link.access || defaultAccess,
};
copyLink(shareLink);
setShareLinks([activeLink]);
setActiveLink(activeLink);
};
const onSelectAccess = (access) => {
@ -87,12 +81,12 @@ const ExternalLinks = ({
if (roomId === -1) {
link = shareLinks.find((l) => l.access === access.access);
onChangeActiveLink(link);
setActiveLink(link);
} else {
setInvitationLinks(roomId, shareLinks[0].id, "Invite", +access.access);
setInvitationLinks(roomId, "Invite", +access.access, shareLinks[0].id);
link = shareLinks[0];
onChangeActiveLink(shareLinks[0]);
setActiveLink(shareLinks[0]);
}
copyLink(link.shareLink);

View File

@ -0,0 +1,80 @@
import React from "react";
import { ReactSVG } from "react-svg";
import styled from "styled-components";
import InfoIcon from "PUBLIC_DIR/images/info.outline.react.svg?url";
import CrossReactSvg from "PUBLIC_DIR/images/cross.react.svg?url";
import IconButton from "@docspace/components/icon-button";
import Text from "@docspace/components/text";
const StyledInfoBar = styled.div`
display: flex;
background-color: #f8f9f9;
color: #333;
font-size: 12px;
padding: 12px 16px;
border-radius: 6px;
margin-bottom: 10px;
margin: -4px 16px 20px;
.text-container {
width: 100%;
display: flex;
flex-direction: column;
}
.header-body {
display: flex;
height: fit-content;
width: 100%;
gap: 8px;
font-weight: 600;
.header-icon {
svg {
path {
fill: #ed7309;
}
}
}
}
.body-container {
color: #555f65;
font-weight: 400;
}
.close-icon {
margin: 3px 1px 0px 0px;
path {
fill: ${({ theme }) => theme.iconButton.color};
}
svg {
weight: 10px;
height: 10px;
}
}
`;
const InfoBar = (props) => {
const { t, iconName, onClose, ...rest } = props;
return (
<StyledInfoBar {...rest}>
<div className="text-container">
<div className="header-body">
<div className="header-icon">
<ReactSVG src={InfoIcon} />
</div>
<Text fontSize="12px" fontWeight={600}>
{t("Common:Info")}
</Text>
</div>
<div className="body-container">{t("InfoPanel:InfoBanner")}</div>
</div>
<IconButton
className="close-icon"
size={10}
iconName={CrossReactSvg}
onClick={onClose}
/>
</StyledInfoBar>
);
};
export default InfoBar;

View File

@ -24,16 +24,19 @@ import {
StyledDescription,
} from "../StyledInvitePanel";
import Filter from "@docspace/common/api/people/filter";
import { getMembersList } from "@docspace/common/api/people";
const searchUsersThreshold = 2;
const InviteInput = ({
defaultAccess,
getUsersByQuery,
hideSelector,
inviteItems,
onClose,
roomId,
roomType,
setInviteItems,
roomUsers,
t,
isOwner,
inputsRef,
@ -44,15 +47,12 @@ const InviteInput = ({
const [inputValue, setInputValue] = useState("");
const [usersList, setUsersList] = useState([]);
const [searchPanelVisible, setSearchPanelVisible] = useState(false);
const [isAddEmailPanelBlocked, setIsAddEmailPanelBlocked] = useState(true);
const [selectedAccess, setSelectedAccess] = useState(defaultAccess);
const searchRef = useRef();
const inRoom = (id) => {
return roomUsers.some((user) => user.sharedTo.id === id);
};
const toUserItems = (query) => {
const addresses = parseAddresses(query);
const uid = () => Math.random().toString(36).slice(-6);
@ -81,10 +81,17 @@ const InviteInput = ({
const searchByQuery = async (value) => {
const query = value.trim();
if (!!query.length) {
const users = await getUsersByQuery(query);
setUsersList(users);
} else {
if (query.length >= searchUsersThreshold) {
const filter = Filter.getFilterWithOutDisabledUser();
filter.search = query;
const users = await getMembersList(roomId, filter);
setUsersList(users.items);
setIsAddEmailPanelBlocked(false);
}
if (!query) {
closeInviteInputPanel();
setInputValue("");
setUsersList([]);
@ -100,11 +107,20 @@ const InviteInput = ({
const value = e.target.value;
const clearValue = value.trim();
if ((!!usersList.length || clearValue.length > 2) && !searchPanelVisible) {
openInviteInputPanel();
setInputValue(value);
if (clearValue.length < searchUsersThreshold) {
setUsersList([]);
setIsAddEmailPanelBlocked(true);
return;
}
setInputValue(value);
if (
(!!usersList.length || clearValue.length >= searchUsersThreshold) &&
!searchPanelVisible
) {
openInviteInputPanel();
}
if (roomId !== -1) {
debouncedSearch(clearValue);
@ -124,9 +140,7 @@ const InviteInput = ({
};
const getItemContent = (item) => {
const { avatar, displayName, email, id } = item;
const invited = inRoom(id);
const { avatar, displayName, email, id, shared } = item;
item.access = selectedAccess;
@ -146,18 +160,18 @@ const InviteInput = ({
<DropDownItem
key={id}
onClick={addUser}
disabled={invited}
disabled={shared}
height={48}
className="list-item"
>
<Avatar size="min" role="user" source={avatar} />
<div>
<SearchItemText primary disabled={invited}>
<SearchItemText primary disabled={shared}>
{displayName}
</SearchItemText>
<SearchItemText>{email}</SearchItemText>
</div>
{invited && <SearchItemText info>{t("Invited")}</SearchItemText>}
{shared && <SearchItemText info>{t("Invited")}</SearchItemText>}
</DropDownItem>
);
};
@ -203,13 +217,27 @@ const InviteInput = ({
};
const closeInviteInputPanel = (e) => {
// if (e?.target.tagName.toUpperCase() == "INPUT") return;
if (e?.target.tagName.toUpperCase() === "INPUT") return;
setSearchPanelVisible(false);
};
const foundUsers = usersList.map((user) => getItemContent(user));
const addEmailPanel = isAddEmailPanelBlocked ? (
<></>
) : (
<DropDownItem
className="add-item"
style={{ width: "inherit" }}
textOverflow
onClick={addEmail}
height={48}
>
{t("Common:AddButton")} «{inputValue}»
</DropDownItem>
);
const accessOptions = getAccessOptions(t, roomType);
const onSelectAccess = (item) => {
@ -277,7 +305,7 @@ const InviteInput = ({
onKeyDown={onKeyDown}
/>
</StyledInviteInput>
{inputValue.length > 2 && (
{inputValue.length >= searchUsersThreshold && (
<StyledDropDown
width={searchRef?.current?.offsetWidth}
isDefaultMode={false}
@ -288,19 +316,7 @@ const InviteInput = ({
eventTypes="click"
{...dropDownMaxHeight}
>
{!!usersList.length ? (
foundUsers
) : (
<DropDownItem
className="add-item"
style={{ width: "inherit" }}
textOverflow
onClick={addEmail}
height={48}
>
{t("Common:AddButton")} «{inputValue}»
</DropDownItem>
)}
{!!usersList.length ? foundUsers : addEmailPanel}
</StyledDropDown>
)}
@ -320,7 +336,6 @@ const InviteInput = ({
onParentPanelClose={onClose}
onClose={closeUsersPanel}
visible={addUsersPanelVisible}
shareDataItems={roomUsers}
tempDataItems={inviteItems}
setDataItems={addItems}
accessOptions={accessOptions}
@ -329,6 +344,7 @@ const InviteInput = ({
defaultAccess={selectedAccess}
withoutBackground={isMobileView}
withBlur={!isMobileView}
roomId={roomId}
/>
)}
</StyledInviteInputContainer>
@ -336,16 +352,14 @@ const InviteInput = ({
);
};
export default inject(({ auth, peopleStore, filesStore, dialogsStore }) => {
export default inject(({ auth, dialogsStore }) => {
const { theme } = auth.settingsStore;
const { isOwner } = auth.userStore.user;
const { getUsersByQuery } = peopleStore.usersStore;
const { invitePanelOptions, setInviteItems, inviteItems } = dialogsStore;
return {
setInviteItems,
inviteItems,
getUsersByQuery,
roomId: invitePanelOptions.roomId,
hideSelector: invitePanelOptions.hideSelector,
defaultAccess: invitePanelOptions.defaultAccess,

View File

@ -149,6 +149,7 @@ const Item = ({
filteredAccesses={filteredAccesses}
setIsOpenItemAccess={setIsOpenItemAccess}
isMobileView={isMobileView}
noBorder
/>
)}
</>

View File

@ -63,6 +63,25 @@ const StyledTitle = styled.div`
align-items: center;
height: 32px;
.info_title-icons {
display: flex;
margin-left: auto;
gap: 14px;
.icon {
cursor: pointer;
path,
rect {
fill: ${(props) => props.theme.infoPanel.members.iconColor};
}
&:hover {
path,
rect {
fill: ${(props) => props.theme.infoPanel.members.iconHoverColor};
}
}
}
}
img {
&.icon {
display: flex;

View File

@ -6,30 +6,18 @@ const StyledUserTypeHeader = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding-top: ${props => (props.isExpect ? "20px" : "8px")};
padding-top: ${(props) => (props.isExpect ? "20px" : "16px")};
padding-bottom: 12px;
.title {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: ${props => props.theme.infoPanel.members.subtitleColor};
color: ${(props) => props.theme.infoPanel.members.subtitleColor};
}
.icon {
cursor: pointer;
path,
rect {
fill: ${props => props.theme.infoPanel.members.iconColor};
}
&:hover {
path,
rect {
fill: ${props => props.theme.infoPanel.members.iconHoverColor};
}
}
margin-right: 8px;
}
`;
@ -56,7 +44,7 @@ const StyledUser = styled.div`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
${props =>
${(props) =>
props.isExpect && `color: ${props.theme.infoPanel.members.isExpectName}`};
}
@ -64,8 +52,8 @@ const StyledUser = styled.div`
font-weight: 600;
font-size: 14px;
line-height: 16px;
color: ${props => props.theme.infoPanel.members.meLabelColor};
${props =>
color: ${(props) => props.theme.infoPanel.members.meLabelColor};
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -8px;
@ -76,7 +64,7 @@ const StyledUser = styled.div`
}
.role-wrapper {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 8px;
@ -93,8 +81,10 @@ const StyledUser = styled.div`
white-space: nowrap;
.disabled-role-combobox {
color: ${props =>
color: ${(props) =>
props.theme.infoPanel.members.disabledRoleSelectorColor};
margin-right: 16px;
}
}
@ -102,14 +92,14 @@ const StyledUser = styled.div`
cursor: pointer;
svg {
path {
fill: ${props => props.theme.iconButton.color};
fill: ${(props) => props.theme.iconButton.color};
}
}
:hover {
svg {
path {
fill: ${props => props.theme.iconButton.hoverColor};
fill: ${(props) => props.theme.iconButton.hoverColor};
}
}
}

View File

@ -1,16 +1,26 @@
import React, { useRef } from "react";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import { ReactSVG } from "react-svg";
import { Text } from "@docspace/components";
import PersonPlusReactSvgUrl from "PUBLIC_DIR/images/person+.react.svg?url";
import IconButton from "@docspace/components/icon-button";
import Text from "@docspace/components/text";
import ItemContextOptions from "./ItemContextOptions";
import { StyledTitle } from "../../styles/common";
import RoomIcon from "@docspace/client/src/components/RoomIcon";
const FilesItemTitle = ({ t, selection, isSeveralItems }) => {
import { RoomsType, ShareAccessRights } from "@docspace/common/constants";
const FilesItemTitle = ({
t,
selection,
isSeveralItems,
selectionParentRoom,
setIsMobileHidden,
isGracePeriod,
setInvitePanelOptions,
setInviteUsersWarningDialogVisible,
isPublicRoomType,
}) => {
const itemTitleRef = useRef();
if (isSeveralItems) return <></>;
@ -18,6 +28,27 @@ const FilesItemTitle = ({ t, selection, isSeveralItems }) => {
const icon = selection.icon;
const isLoadedRoomIcon = !!selection.logo?.medium;
const showDefaultRoomIcon = !isLoadedRoomIcon && selection.isRoom;
const security = selectionParentRoom ? selectionParentRoom.security : {};
const canInviteUserInRoomAbility = security?.EditAccess;
const onClickInviteUsers = () => {
setIsMobileHidden(true);
const parentRoomId = selectionParentRoom.id;
if (isGracePeriod) {
setInviteUsersWarningDialogVisible(true);
return;
}
setInvitePanelOptions({
visible: true,
roomId: parentRoomId,
hideSelector: false,
defaultAccess: isPublicRoomType
? ShareAccessRights.RoomManager
: ShareAccessRights.ReadOnly,
});
};
return (
<StyledTitle ref={itemTitleRef}>
@ -37,21 +68,57 @@ const FilesItemTitle = ({ t, selection, isSeveralItems }) => {
)}
</div>
<Text className="text">{selection.title}</Text>
{selection && (
<ItemContextOptions
t={t}
itemTitleRef={itemTitleRef}
selection={selection}
/>
)}
<div className="info_title-icons">
{canInviteUserInRoomAbility && (
<IconButton
id="info_add-user"
className={"icon"}
title={t("Common:AddUsers")}
iconName={PersonPlusReactSvgUrl}
isFill={true}
onClick={onClickInviteUsers}
size={16}
/>
)}
{selection && (
<ItemContextOptions
t={t}
itemTitleRef={itemTitleRef}
selection={selection}
/>
)}
</div>
</StyledTitle>
);
};
export default withTranslation([
"Files",
"Common",
"Translations",
"InfoPanel",
"SharingPanel",
])(observer(FilesItemTitle));
export default inject(({ auth, dialogsStore, selectedFolderStore }) => {
const { selectionParentRoom, setIsMobileHidden } = auth.infoPanelStore;
const { isGracePeriod } = auth.currentTariffStatusStore;
const { setInvitePanelOptions, setInviteUsersWarningDialogVisible } =
dialogsStore;
const roomType =
selectedFolderStore.roomType ?? selectionParentRoom?.roomType;
const isPublicRoomType =
roomType === RoomsType.PublicRoom || roomType === RoomsType.CustomRoom;
return {
selectionParentRoom,
setIsMobileHidden,
isGracePeriod,
setInvitePanelOptions,
setInviteUsersWarningDialogVisible,
isPublicRoomType,
};
})(
withTranslation([
"Files",
"Common",
"Translations",
"InfoPanel",
"SharingPanel",
])(observer(FilesItemTitle))
);

View File

@ -6,17 +6,6 @@ import { ContextMenu, ContextMenuButton } from "@docspace/components";
import ContextHelper from "../../helpers/ContextHelper";
const StyledItemContextOptions = styled.div`
${props =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
`
: css`
margin-left: auto;
`}
`;
const ItemContextOptions = ({
t,
selection,
@ -70,7 +59,7 @@ const ItemContextOptions = ({
};
return (
<StyledItemContextOptions>
<>
<ContextMenu
ref={contextMenuRef}
getContextModel={getData}
@ -91,7 +80,7 @@ const ItemContextOptions = ({
displayType="toggle"
/>
)}
</StyledItemContextOptions>
</>
);
};

View File

@ -49,7 +49,6 @@ const ItemTitle = ({
selectionLength={selectionLength}
selection={filesItemSelection}
isSeveralItems={isSeveralItems}
getIcon={getIcon}
/>
);
};

View File

@ -0,0 +1,191 @@
import React, { useState, useCallback, useEffect, memo } from "react";
import styled from "styled-components";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import CustomScrollbarsVirtualList from "@docspace/components/scrollbar/custom-scrollbars-virtual-list";
import InfiniteLoader from "react-window-infinite-loader";
import User from "./User";
import { tablet, mobile, isMobile } from "@docspace/components/utils/device";
const StyledMembersList = styled.div`
height: ${({ withBanner, isPublicRoomType }) =>
isPublicRoomType
? withBanner
? "calc(100vh - 442px)"
: "calc(100vh - 286px)"
: "calc(100vh - 266px)"};
@media ${tablet} {
height: ${({ withBanner, isPublicRoomType }) =>
isPublicRoomType
? withBanner
? "calc(100vh - 362px)"
: "calc(100vh - 206px)"
: "calc(100vh - 186px)"};
}
@media ${mobile} {
height: ${({ withBanner, isPublicRoomType }) =>
isPublicRoomType
? withBanner
? "calc(100vh - 426px)"
: "calc(100vh - 270px)"
: "calc(100vh - 250px)"};
}
`;
const Item = memo(({ data, index, style }) => {
const {
t,
members,
setMembers,
security,
membersHelper,
currentMember,
updateRoomMemberRole,
selectionParentRoom,
setSelectionParentRoom,
changeUserType,
setIsScrollLocked,
canInviteUserInRoomAbility,
onRepeatInvitation,
} = data;
const user = members[index];
return (
<div key={user.id} style={{ ...style, width: "calc(100% - 8px)" }}>
<User
t={t}
user={user}
key={user.id}
security={security}
membersHelper={membersHelper}
currentMember={currentMember}
updateRoomMemberRole={updateRoomMemberRole}
roomId={selectionParentRoom.id}
roomType={selectionParentRoom.roomType}
selectionParentRoom={selectionParentRoom}
setSelectionParentRoom={setSelectionParentRoom}
changeUserType={changeUserType}
setIsScrollLocked={setIsScrollLocked}
isTitle={user.isTitle}
isExpect={user.isExpect}
showInviteIcon={canInviteUserInRoomAbility && user.isExpect}
onRepeatInvitation={onRepeatInvitation}
setMembers={setMembers}
/>
</div>
);
}, areEqual);
const MembersList = (props) => {
const {
t,
security,
membersHelper,
currentMember,
updateRoomMemberRole,
selectionParentRoom,
setSelectionParentRoom,
changeUserType,
setIsScrollLocked,
members,
setMembers,
hasNextPage,
itemCount,
onRepeatInvitation,
loadNextPage,
isPublicRoomType,
withBanner,
} = props;
const itemsCount = members.length;
const canInviteUserInRoomAbility = security?.EditAccess;
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
const [isMobileView, setIsMobileView] = useState(isMobile());
const onResize = () => {
const isMobileView = isMobile();
setIsMobileView(isMobileView);
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
});
const isItemLoaded = useCallback(
(index) => {
return !hasNextPage || index < itemsCount;
},
[hasNextPage, itemsCount]
);
const loadMoreItems = useCallback(
async (startIndex) => {
setIsNextPageLoading(true);
if (!isNextPageLoading) {
await loadNextPage(startIndex - 1);
}
setIsNextPageLoading(false);
},
[isNextPageLoading, loadNextPage]
);
return (
<StyledMembersList
withBanner={withBanner}
isPublicRoomType={isPublicRoomType}
>
<AutoSizer>
{({ height, width }) => (
<InfiniteLoader
isItemLoaded={isItemLoaded}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
>
{({ onItemsRendered, ref }) => {
const listWidth = isMobileView ? width + 16 : width + 20; // for scroll
return (
<List
ref={ref}
width={listWidth}
height={height}
itemCount={itemsCount}
itemSize={48}
itemData={{
t,
security,
membersHelper,
currentMember,
updateRoomMemberRole,
selectionParentRoom,
setSelectionParentRoom,
changeUserType,
setIsScrollLocked,
members,
setMembers,
canInviteUserInRoomAbility,
onRepeatInvitation,
}}
outerElementType={CustomScrollbarsVirtualList}
onItemsRendered={onItemsRendered}
>
{Item}
</List>
);
}}
</InfiniteLoader>
)}
</AutoSizer>
</StyledMembersList>
);
};
export default MembersList;

View File

@ -9,10 +9,15 @@ import { isMobileOnly } from "react-device-detect";
import { decode } from "he";
import { filterUserRoleOptions } from "SRC_DIR/helpers/utils";
import { getUserRole } from "@docspace/common/utils";
import Text from "@docspace/components/text";
import EmailPlusReactSvgUrl from "PUBLIC_DIR/images/e-mail+.react.svg?url";
import { StyledUserTypeHeader } from "../../styles/members";
import IconButton from "@docspace/components/icon-button";
const User = ({
t,
user,
setMembers,
isExpect,
membersHelper,
currentMember,
@ -21,6 +26,9 @@ const User = ({
setSelectionParentRoom,
changeUserType,
setIsScrollLocked,
isTitle,
onRepeatInvitation,
showInviteIcon,
}) => {
if (!selectionParentRoom) return null;
if (!user.displayName && !user.email) return null;
@ -48,22 +56,45 @@ const User = ({
})
.then(() => {
setIsLoading(false);
const inRoomMembers = selectionParentRoom.members.inRoom;
const users = selectionParentRoom.members.users;
const administrators = selectionParentRoom.members.administrators;
const expectedMembers = selectionParentRoom.members.expected;
if (option.key === "remove") {
setMembers({
users: users?.filter((m) => m.id !== user.id),
administrators: administrators?.filter((m) => m.id !== user.id),
expected: expectedMembers?.filter((m) => m.id !== user.id),
});
setSelectionParentRoom({
...selectionParentRoom,
members: {
inRoom: inRoomMembers?.filter((m) => m.id !== user.id),
users: users?.filter((m) => m.id !== user.id),
administrators: administrators?.filter((m) => m.id !== user.id),
expected: expectedMembers?.filter((m) => m.id !== user.id),
},
});
//setUserIsRemoved(true);
} else {
setMembers({
users: users?.map((m) =>
m.id === user.id ? { ...m, access: option.access } : m
),
administrators: administrators?.map((m) =>
m.id === user.id ? { ...m, access: option.access } : m
),
expected: expectedMembers?.map((m) =>
m.id === user.id ? { ...m, access: option.access } : m
),
});
setSelectionParentRoom({
...selectionParentRoom,
members: {
inRoom: inRoomMembers?.map((m) =>
users: users?.map((m) =>
m.id === user.id ? { ...m, access: option.access } : m
),
administrators: administrators?.map((m) =>
m.id === user.id ? { ...m, access: option.access } : m
),
expected: expectedMembers?.map((m) =>
@ -126,7 +157,22 @@ const User = ({
user.isOwner ? t("Common:DocSpaceOwner") : t("Common:DocSpaceAdmin")
}. ${t("Common:HasFullAccess")}`;
return (
return isTitle ? (
<StyledUserTypeHeader isExpect={isExpect}>
<Text className="title">{user.displayName}</Text>
{showInviteIcon && (
<IconButton
className={"icon"}
title={t("Common:RepeatInvitation")}
iconName={EmailPlusReactSvgUrl}
isFill={true}
onClick={onRepeatInvitation}
size={16}
/>
)}
</StyledUserTypeHeader>
) : (
<StyledUser isExpect={isExpect} key={user.id}>
<Avatar
role={role}

View File

@ -2,29 +2,22 @@ import React, { useState, useEffect, useCallback } from "react";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import toastr from "@docspace/components/toast/toastr";
import { FolderType } from "@docspace/common/constants";
import Loaders from "@docspace/common/components/Loaders";
import PersonPlusReactSvgUrl from "PUBLIC_DIR/images/person+.react.svg?url";
import EmailPlusReactSvgUrl from "PUBLIC_DIR/images/e-mail+.react.svg?url";
import {
EmployeeActivationStatus,
RoomsType,
ShareAccessRights,
} from "@docspace/common/constants";
import { StyledUserList, StyledUserTypeHeader } from "../../styles/members";
import { RoomsType, ShareAccessRights } from "@docspace/common/constants";
import IconButton from "@docspace/components/icon-button";
import Text from "@docspace/components/text";
import User from "./User";
import MembersHelper from "../../helpers/MembersHelper";
import PublicRoomBlock from "./sub-components/PublicRoomBlock";
import MembersList from "./MembersList";
const Members = ({
t,
selfId,
selection,
setIsMobileHidden,
updateRoomMembers,
setUpdateRoomMembers,
@ -34,80 +27,116 @@ const Members = ({
setIsScrollLocked,
getRoomMembers,
getRoomLinks,
updateRoomMemberRole,
setView,
roomsView,
resendEmailInvitations,
setInvitePanelOptions,
setInviteUsersWarningDialogVisible,
changeUserType,
isGracePeriod,
isPublicRoomType,
setExternalLinks,
membersFilter,
externalLinks,
}) => {
const membersHelper = new MembersHelper({ t });
const [members, setMembers] = useState(null);
const [showLoader, setShowLoader] = useState(false);
const security = selectionParentRoom ? selectionParentRoom.security : {};
const canInviteUserInRoomAbility = security?.EditAccess;
const fetchMembers = async (roomId, clearFilter = true) => {
const isPublic = selection?.roomType ?? selectionParentRoom?.roomType;
const requests = [getRoomMembers(roomId, clearFilter)];
const fetchMembers = async (roomId) => {
let timerId;
if (members) timerId = setTimeout(() => setShowLoader(true), 1000);
let data = await getRoomMembers(roomId);
if (isPublic && clearFilter) {
requests.push(getRoomLinks(roomId));
}
setExternalLinks(data);
const [data, links] = await Promise.all(requests);
data = data.filter((m) => m.sharedTo.email || m.sharedTo.displayName);
clearTimeout(timerId);
links && setExternalLinks(links);
let inRoomMembers = [];
let expectedMembers = [];
const users = [];
const administrators = [];
const expectedMembers = [];
data.map((fetchedMember) => {
const member = {
access: fetchedMember.access,
canEditAccess: fetchedMember.canEditAccess,
...fetchedMember.sharedTo,
};
if (member.activationStatus !== 2) inRoomMembers.push(member);
else expectedMembers.push(member);
if (member.activationStatus === EmployeeActivationStatus.Pending) {
member.isExpect = true;
expectedMembers.push(member);
} else if (
member.access === ShareAccessRights.FullAccess ||
member.access === ShareAccessRights.RoomManager
) {
administrators.push(member);
} else {
users.push(member);
}
});
setShowLoader(false);
let hasPrevAdminsTitle =
members?.roomId === roomId
? getHasPrevTitle(members?.administrators, "administration")
: false;
if (administrators.length && !hasPrevAdminsTitle) {
administrators.unshift({
id: "administration",
displayName: t("Administration"),
isTitle: true,
});
}
let hasPrevUsersTitle =
members?.roomId === roomId
? getHasPrevTitle(members?.users, "user")
: false;
if (users.length && !hasPrevUsersTitle) {
users.unshift({ id: "user", displayName: t("Users"), isTitle: true });
}
let hasPrevExpectedTitle =
members?.roomId === roomId
? getHasPrevTitle(members?.expected, "expected")
: false;
if (expectedMembers.length && !hasPrevExpectedTitle) {
expectedMembers.unshift({
id: "expected",
displayName: t("ExpectUsers"),
isTitle: true,
isExpect: true,
});
}
setUpdateRoomMembers(false);
return {
inRoom: inRoomMembers,
users,
administrators,
expected: expectedMembers,
roomId,
};
};
const updateSelectionParentRoomAction = useCallback(async () => {
if (!selectionParentRoom) return;
if (selectionParentRoom.members) {
setMembers(selectionParentRoom.members);
return;
}
const fetchedMembers = await fetchMembers(selectionParentRoom.id);
setSelectionParentRoom({
...selectionParentRoom,
members: fetchedMembers,
});
}, [selectionParentRoom]);
useEffect(() => {
updateSelectionParentRoomAction();
}, [selectionParentRoom, updateSelectionParentRoomAction]);
const getHasPrevTitle = (array, type) => {
return array.findIndex((x) => x.id === type) > -1;
};
const updateSelectionParentRoomActionSelection = useCallback(async () => {
if (!selection.isRoom) return;
const fetchedMembers = await fetchMembers(selection.id);
setMembers(fetchedMembers);
setSelectionParentRoom({
...selection,
members: fetchedMembers,
@ -129,6 +158,7 @@ const Members = ({
...selectionParentRoom,
members: fetchedMembers,
});
setMembers(fetchedMembers);
}, [selectionParentRoom, selection?.id, updateRoomMembers]);
@ -141,135 +171,74 @@ const Members = ({
updateMembersAction,
]);
const onClickInviteUsers = () => {
setIsMobileHidden(true);
const parentRoomId = selectionParentRoom.id;
if (isGracePeriod) {
setInviteUsersWarningDialogVisible(true);
return;
}
setInvitePanelOptions({
visible: true,
roomId: parentRoomId,
hideSelector: false,
defaultAccess: isPublicRoomType
? ShareAccessRights.RoomManager
: ShareAccessRights.ReadOnly,
});
};
const onRepeatInvitation = async () => {
const userIds = members.expected.map((user) => user.id);
resendEmailInvitations(selectionParentRoom.id, userIds)
resendEmailInvitations(selectionParentRoom.id, true)
.then(() =>
toastr.success(t("PeopleTranslations:SuccessSentMultipleInvitatios"))
)
.catch((err) => toastr.error(err));
};
if (showLoader) return <Loaders.InfoPanelViewLoader view="members" />;
if (!selectionParentRoom || !members) return null;
const [currentMember] = members.inRoom.filter(
const [currentMember] = members.administrators.filter(
(member) => member.id === selfId
);
const loadNextPage = async () => {
const roomId = selectionParentRoom.id;
const fetchedMembers = await fetchMembers(roomId, false);
const { users, administrators, expected } = fetchedMembers;
const newMembers = {
administrators: [...members.administrators, ...administrators],
users: [...members.users, ...users],
expected: [...members.expected, ...expected],
};
setMembers(newMembers);
};
const { administrators, users, expected } = members;
const membersList = [...administrators, ...users, ...expected];
const adminsTitleCount = administrators.length ? 1 : 0;
const usersTitleCount = users.length ? 1 : 0;
const expectedTitleCount = expected.length ? 1 : 0;
const headersCount = adminsTitleCount + usersTitleCount + expectedTitleCount;
return (
<>
{isPublicRoomType && <PublicRoomBlock t={t} />}
<StyledUserTypeHeader>
<Text className="title">
{t("UsersInRoom")} : {members.inRoom.length}
</Text>
{canInviteUserInRoomAbility && (
<IconButton
id="info_add-user"
className={"icon"}
title={t("Common:AddUsers")}
iconName={PersonPlusReactSvgUrl}
isFill={true}
onClick={onClickInviteUsers}
size={16}
/>
)}
</StyledUserTypeHeader>
<StyledUserList>
{Object.values(members.inRoom).map((user) => (
<User
security={security}
key={user.id}
t={t}
user={user}
membersHelper={membersHelper}
currentMember={currentMember}
updateRoomMemberRole={updateRoomMemberRole}
roomId={selectionParentRoom.id}
roomType={selectionParentRoom.roomType}
selectionParentRoom={selectionParentRoom}
setSelectionParentRoom={setSelectionParentRoom}
changeUserType={changeUserType}
setIsScrollLocked={setIsScrollLocked}
/>
))}
</StyledUserList>
{!!members.expected.length && (
<StyledUserTypeHeader isExpect>
<Text className="title">{t("PendingInvitations")}</Text>
{canInviteUserInRoomAbility && (
<IconButton
className={"icon"}
title={t("Common:RepeatInvitation")}
iconName={EmailPlusReactSvgUrl}
isFill={true}
onClick={onRepeatInvitation}
size={16}
/>
)}
</StyledUserTypeHeader>
)}
<StyledUserList>
{Object.values(members.expected).map((user, i) => (
<User
security={security}
isExpect
key={i}
t={t}
user={user}
membersHelper={membersHelper}
currentMember={currentMember}
updateRoomMemberRole={updateRoomMemberRole}
roomId={selectionParentRoom.id}
roomType={selectionParentRoom.roomType}
selectionParentRoom={selectionParentRoom}
setSelectionParentRoom={setSelectionParentRoom}
changeUserType={changeUserType}
setIsScrollLocked={setIsScrollLocked}
/>
))}
</StyledUserList>
<MembersList
loadNextPage={loadNextPage}
t={t}
security={security}
members={membersList}
membersHelper={membersHelper}
currentMember={currentMember}
updateRoomMemberRole={updateRoomMemberRole}
selectionParentRoom={selectionParentRoom}
setSelectionParentRoom={setSelectionParentRoom}
changeUserType={changeUserType}
setIsScrollLocked={setIsScrollLocked}
hasNextPage={membersList.length - headersCount < membersFilter.total}
itemCount={membersFilter.total}
onRepeatInvitation={onRepeatInvitation}
isPublicRoomType={isPublicRoomType}
withBanner={isPublicRoomType && externalLinks.length > 0}
setMembers={setMembers}
/>
</>
);
};
export default inject(
({
auth,
filesStore,
peopleStore,
dialogsStore,
selectedFolderStore,
publicRoomStore,
}) => {
({ auth, filesStore, peopleStore, selectedFolderStore, publicRoomStore }) => {
const {
setIsMobileHidden,
selectionParentRoom,
selection,
setSelectionParentRoom,
setView,
roomsView,
@ -279,15 +248,17 @@ export default inject(
setIsScrollLocked,
} = auth.infoPanelStore;
const { getRoomMembers, updateRoomMemberRole, resendEmailInvitations } =
filesStore;
const {
getRoomMembers,
getRoomLinks,
updateRoomMemberRole,
resendEmailInvitations,
membersFilter,
} = filesStore;
const { id: selfId } = auth.userStore.user;
const { isGracePeriod } = auth.currentTariffStatusStore;
const { setInvitePanelOptions, setInviteUsersWarningDialogVisible } =
dialogsStore;
const { changeType: changeUserType } = peopleStore;
const { setExternalLinks } = publicRoomStore;
const { roomLinks, setExternalLinks } = publicRoomStore;
const roomType =
selectedFolderStore.roomType ?? selectionParentRoom?.roomType;
@ -298,13 +269,13 @@ export default inject(
return {
setView,
roomsView,
setIsMobileHidden,
selectionParentRoom,
setSelectionParentRoom,
setIsScrollLocked,
getRoomMembers,
getRoomLinks,
updateRoomMemberRole,
updateRoomMembers,
@ -312,13 +283,12 @@ export default inject(
selfId,
setInvitePanelOptions,
setInviteUsersWarningDialogVisible,
resendEmailInvitations,
changeUserType,
isGracePeriod,
isPublicRoomType,
setExternalLinks,
membersFilter,
externalLinks: roomLinks,
};
}
)(

View File

@ -40,15 +40,8 @@ const LinkRow = (props) => {
const [isLoading, setIsLoading] = useState(false);
const {
title,
shareLink,
id,
password,
disabled,
expirationDate,
isExpired,
} = link.sharedTo;
const { title, shareLink, password, disabled, expirationDate, isExpired } =
link.sharedTo;
const isLocked = !!password;
const expiryDate = !!expirationDate;
@ -76,8 +69,8 @@ const LinkRow = (props) => {
newLink.sharedTo.disabled = !newLink.sharedTo.disabled;
editExternalLink(roomId, newLink)
.then((res) => {
setExternalLink(id, res);
.then((link) => {
setExternalLink(link);
disabled
? toastr.success(t("Files:LinkEnabledSuccessfully"))

View File

@ -30,7 +30,6 @@ const PublicRoomBar = (props) => {
size={8}
iconName={CrossReactSvg}
onClick={onClose}
color="#657077"
/> */}
</StyledPublicRoomBar>
);

View File

@ -36,6 +36,10 @@ const StyledPublicRoomBar = styled.div`
.close-icon {
margin: -5px -17px 0 0;
path {
fill: ${({ theme }) => theme.iconButton.color};
}
svg {
weight: 8px;
height: 8px;

View File

@ -126,12 +126,14 @@ const InfoPanelHeaderContent = (props) => {
style={{ width: "100%" }}
data={roomsSubmenu}
forsedActiveItemId={roomsView}
size="scale"
/>
) : (
<Submenu
style={{ width: "100%" }}
data={personalSubmenu}
forsedActiveItemId={fileView}
size="scale"
/>
)}
</div>

View File

@ -10,7 +10,7 @@ import InfiniteGrid from "./InfiniteGrid";
const paddingCss = css`
@media ${desktop} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 1px;
@ -23,7 +23,7 @@ const paddingCss = css`
}
@media ${tablet} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -1px;
@ -36,12 +36,12 @@ const paddingCss = css`
const StyledGridWrapper = styled.div`
display: grid;
grid-template-columns: ${props =>
grid-template-columns: ${(props) =>
props.isRooms
? "repeat(auto-fill, minmax(274px, 1fr))"
: "repeat(auto-fill, minmax(216px, 1fr))"};
width: 100%;
margin-bottom: ${props => (props.isFolders || props.isRooms ? "23px" : 0)};
margin-bottom: ${(props) => (props.isFolders || props.isRooms ? "23px" : 0)};
box-sizing: border-box;
${paddingCss};
@ -90,7 +90,7 @@ const StyledTileContainer = styled.div`
cursor: pointer !important;
.sort-combo-box {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 3px;
@ -119,14 +119,14 @@ const StyledTileContainer = styled.div`
.option-item__icon {
display: none;
cursor: pointer;
${props =>
${(props) =>
props.isDesc &&
css`
transform: rotate(180deg);
`}
path {
fill: ${props => props.theme.filterInput.sort.sortFill};
fill: ${(props) => props.theme.filterInput.sort.sortFill};
}
}
@ -138,7 +138,7 @@ const StyledTileContainer = styled.div`
}
.selected-option-item {
background: ${props =>
background: ${(props) =>
props.theme.filterInput.sort.hoverBackground};
cursor: auto;
@ -156,10 +156,10 @@ const StyledTileContainer = styled.div`
font-size: 12px;
font-weight: 600;
color: ${props => props.theme.filterInput.sort.tileSortColor};
color: ${(props) => props.theme.filterInput.sort.tileSortColor};
.sort-icon {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 8px;
@ -169,7 +169,7 @@ const StyledTileContainer = styled.div`
`}
svg {
path {
fill: ${props => props.theme.filterInput.sort.tileSortFill};
fill: ${(props) => props.theme.filterInput.sort.tileSortFill};
}
}
}
@ -183,7 +183,7 @@ const StyledTileContainer = styled.div`
}
@media ${tablet} {
${props =>
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: -3px;
@ -217,7 +217,7 @@ class TileContainer extends React.PureComponent {
const Folders = [];
const Files = [];
React.Children.map(children, item => {
React.Children.map(children, (item) => {
const { isFolder, isRoom, fileExst, id } = item.props.item;
if ((isFolder || id === -1) && !fileExst && !isRoom) {
Folders.push(
@ -254,7 +254,8 @@ class TileContainer extends React.PureComponent {
<Heading
size="xsmall"
id={"folder-tile-heading"}
className="tile-items-heading">
className="tile-items-heading"
>
{headingFolders}
</Heading>
)}
@ -287,7 +288,8 @@ class TileContainer extends React.PureComponent {
className={`${className} files-tile-container`}
style={style}
useReactWindow={useReactWindow}
isDesc={isDesc}>
isDesc={isDesc}
>
{useReactWindow ? (
<InfiniteGrid>{renderTile}</InfiniteGrid>
) : (

View File

@ -10,7 +10,6 @@ const StyledRowContent = styled(RowContent)`
.row-main-container-wrapper {
display: flex;
justify-content: flex-start;
width: min-content;
}
${(props) => props.isSettingNotPaid && UnavailableStyles}
@ -27,8 +26,7 @@ export const AuditContent = ({ sectionWidth, item, isSettingNotPaid }) => {
sideColor="#A3A9AE"
nameColor="#D0D5DA"
sectionWidth={sectionWidth}
isSettingNotPaid={isSettingNotPaid}
>
isSettingNotPaid={isSettingNotPaid}>
<div className="user-container-wrapper">
<Text fontWeight={600} fontSize="14px" isTextOverflow={true}>
{item.user}
@ -40,16 +38,14 @@ export const AuditContent = ({ sectionWidth, item, isSettingNotPaid }) => {
fontSize="12px"
fontWeight={600}
truncate={true}
className="settings_unavailable"
>
className="settings_unavailable">
{dateStr}
</Text>
<Text
fontSize="12px"
as="div"
fontWeight={600}
className="settings_unavailable"
>
className="settings_unavailable">
{`${item.context ? item.context + " |" : ""} ${item.action}`}
</Text>
</StyledRowContent>

View File

@ -30,6 +30,7 @@ import { getContextMenuKeysByType } from "SRC_DIR/helpers/plugins";
import { PluginContextMenuItemType } from "SRC_DIR/helpers/plugins/constants";
import { CategoryType } from "SRC_DIR/helpers/constants";
import debounce from "lodash.debounce";
import clone from "lodash/clone";
import Queue from "queue-promise";
const { FilesFilter, RoomsFilter } = api;
@ -80,8 +81,9 @@ class FilesStore {
bufferSelection = null;
selected = "close";
filter = FilesFilter.getDefault(); //TODO: FILTER
filter = FilesFilter.getDefault();
roomsFilter = RoomsFilter.getDefault();
membersFilter = { page: 0, pageCount: 100, total: 0 };
categoryType = getCategoryType(window.location);
@ -2426,9 +2428,48 @@ class FilesStore {
return api.rooms.removeLogoFromRoom(id);
}
getRoomMembers(id) {
return api.rooms.getRoomMembers(id);
}
getDefaultMembersFilter = () => {
return { page: 0, pageCount: 100, total: 0 };
};
setRoomMembersFilter = (roomMembersFilter) => {
this.roomMembersFilter = roomMembersFilter;
};
getRoomMembers = (id, clearFilter = true) => {
let newFilter = this.membersFilter;
if (clearFilter) {
newFilter = this.getDefaultMembersFilter();
} else {
newFilter.page += 1;
}
this.setRoomMembersFilter(newFilter);
const membersFilters = {
startIndex: newFilter.page * newFilter.pageCount,
count: newFilter.pageCount,
filterType: 0, // 0 (Members)
};
return api.rooms.getRoomMembers(id, membersFilters).then((res) => {
const newFilter = clone(this.membersFilter);
newFilter.total = res.total;
this.setMembersFilter(newFilter);
return res.items;
});
};
setMembersFilter = (filter) => {
this.membersFilter = filter;
};
getRoomLinks = (id) => {
return api.rooms
.getRoomMembers(id, { filterType: 2 }) // 2 (External link)
.then((res) => res.items);
};
updateRoomMemberRole(id, data) {
return api.rooms.updateRoomMemberRole(id, data);
@ -3623,16 +3664,16 @@ class FilesStore {
return Math.floor(sectionWidth / minTileWidth);
};
setInvitationLinks = async (roomId, linkId, title, access) => {
setInvitationLinks = async (roomId, title, access, linkId) => {
return await api.rooms.setInvitationLinks(roomId, linkId, title, access);
};
resendEmailInvitations = async (id, usersIds) => {
return await api.rooms.resendEmailInvitations(id, usersIds);
resendEmailInvitations = async (id, resendAll) => {
return await api.rooms.resendEmailInvitations(id, resendAll);
};
getRoomSecurityInfo = async (id) => {
return await api.rooms.getRoomSecurityInfo(id);
return await api.rooms.getRoomSecurityInfo(id).then((res) => res.items);
};
setRoomSecurity = async (id, data) => {

View File

@ -45,12 +45,25 @@ class PublicRoomStore {
this.externalLinks = externalLinks;
};
setExternalLink = (linkId, data) => {
const linkIndex = this.externalLinks.findIndex(
(l) => l.sharedTo.id === linkId
deleteExternalLink = (linkId) => {
const externalLinks = this.externalLinks.filter(
(l) => l.sharedTo.id !== linkId
);
const dataLink = data.find((l) => l.sharedTo.id === linkId);
this.externalLinks[linkIndex] = dataLink;
this.externalLinks = externalLinks;
};
setExternalLink = (link) => {
const linkIndex = this.externalLinks.findIndex(
(l) => l.sharedTo.id === link.sharedTo.id
);
const externalLinks = this.externalLinks;
if (linkIndex === -1) {
externalLinks.push(link);
this.externalLinks = externalLinks;
} else {
externalLinks[linkIndex] = link;
}
};
setExternalLinks = (links) => {

View File

@ -358,3 +358,36 @@ export function getUsersByQuery(query) {
url: `/people/search?query=${query}`,
});
}
export function getMembersList(roomId, filter = Filter.getDefault()) {
let params = "";
if (filter) {
checkFilterInstance(filter, Filter);
params = `?${filter.toApiUrlParams(
"id,email,avatar,icon,displayName,hasAvatar,isOwner,isAdmin,isVisitor,isCollaborator,"
)}`;
}
const excludeShared = filter.excludeShared ? filter.excludeShared : false;
if (params) {
params += `&excludeShared=${excludeShared}`;
} else {
params = `excludeShared=${excludeShared}`;
}
return request({
method: "get",
url: `people/room/${roomId}${params}`,
}).then((res) => {
res.items = res.items.map((user) => {
if (user && user.displayName) {
user.displayName = Encoder.htmlDecode(user.displayName);
}
return user;
});
return res;
});
}

View File

@ -1,5 +1,9 @@
import { request } from "../client";
import { checkFilterInstance, decodeDisplayName } from "../../utils";
import {
checkFilterInstance,
decodeDisplayName,
toUrlParams,
} from "../../utils";
import { FolderType } from "../../constants";
import RoomsFilter from "./filter";
@ -45,10 +49,17 @@ export function getRoomInfo(id) {
});
}
export function getRoomMembers(id) {
export function getRoomMembers(id, filter) {
let params = "";
const str = toUrlParams(filter);
if (str) {
params = `?${str}`;
}
const options = {
method: "get",
url: `/files/rooms/${id}/share`,
url: `/files/rooms/${id}/share${params}`,
};
return request(options).then((res) => {
@ -288,12 +299,12 @@ export const setInvitationLinks = async (roomId, linkId, title, access) => {
return res;
};
export const resendEmailInvitations = async (id, usersIds) => {
export const resendEmailInvitations = async (id, resendAll = true) => {
const options = {
method: "post",
url: `/files/rooms/${id}/resend`,
data: {
usersIds,
resendAll,
},
};
@ -302,10 +313,11 @@ export const resendEmailInvitations = async (id, usersIds) => {
return res;
};
//// 1 (Invitation link)
export const getRoomSecurityInfo = async (id) => {
const options = {
method: "get",
url: `/files/rooms/${id}/share`,
url: `/files/rooms/${id}/share?filterType=1`,
};
const res = await request(options);

View File

@ -21,6 +21,7 @@ const Submenu = (props) => {
startSelect = 0,
forsedActiveItemId,
onSelect,
size,
...rest
} = props;
if (!data) return null;
@ -105,7 +106,7 @@ const Submenu = (props) => {
<div className="sticky">
<SubmenuRoot>
<SubmenuScrollbarSize />
<SubmenuScroller>
<SubmenuScroller size={size}>
<StyledSubmenuItems ref={submenuItemsRef} role="list">
{data.map((d) => {
const isActive =
@ -144,9 +145,14 @@ const Submenu = (props) => {
);
})}
</StyledSubmenuItems>
{size !== "scale" && (
<StyledSubmenuBottomLine className="bottom-line" />
)}
</SubmenuScroller>
</SubmenuRoot>
<StyledSubmenuBottomLine className="bottom-line" />
{size === "scale" && (
<StyledSubmenuBottomLine className="bottom-line" />
)}
</div>
<div className="sticky-indent"></div>
@ -164,6 +170,8 @@ Submenu.propTypes = {
startSelect: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
/** Property that allows explicitly selecting content passed through an external operation */
forsedActiveItemId: PropTypes.any,
/** Scales the width of the bottom line to 100%. */
size: PropTypes.string,
/** Sets a callback function that is triggered when the submenu item is selected */
onSelect: PropTypes.func,
};

View File

@ -128,6 +128,13 @@ export const SubmenuScroller = styled.div`
}
overflow-x: auto;
overflow-y: hidden;
${(props) =>
props.size !== "scale" &&
css`
display: grid;
flex: 0 1 auto;
`}
`;
export const SubmenuRoot = styled.div`

View File

@ -28,9 +28,10 @@ namespace ASC.Files.Core.ApiModels.RequestDto;
/// <summary>
/// </summary>
public class UserInvintationRequestDto
public class UserInvitationRequestDto
{
/// <summary>List of user IDs</summary>
/// <type>System.Collections.Generic.IEnumerable{System.Guid}, System.Collections.Generic</type>
public IEnumerable<Guid> UsersIds { get; set; }
public bool ResendAll { get; set; }
}

View File

@ -74,7 +74,6 @@ public class FileShareLink
public string Password { get; set; }
public bool? Disabled { get; set; }
public bool? DenyDownload { get; set; }
public bool IsTemplate { get; set; }
public bool? IsExpired { get; set; }
}
@ -126,7 +125,6 @@ public class FileShareDtoHelper
Password = aceWrapper.FileShareOptions?.Password,
Disabled = aceWrapper.FileShareOptions?.Disabled is true ? true : expired,
DenyDownload = aceWrapper.FileShareOptions?.DenyDownload,
IsTemplate = aceWrapper.IsTemplate,
LinkType = aceWrapper.SubjectType switch
{
SubjectType.InvitationLink => LinkType.Invitation,

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using User = ASC.Core.Common.EF.User;
namespace ASC.Files.Core.Data;
[Scope]
@ -202,6 +204,164 @@ internal abstract class SecurityBaseDao<T> : AbstractDao
return InternalGetPureShareRecordsAsync(entry);
}
public async Task<int> GetPureSharesCountAsync(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status)
{
if (entry == null)
{
return 0;
}
await using var filesDbContext = _dbContextFactory.CreateDbContext();
var q = await GetPureSharesQuery(entry, filterType, filesDbContext);
if (status.HasValue)
{
q = q.Join(filesDbContext.Users, s => s.Subject, u => u.Id,
(s, u) => new SecurityUserRecord { Security = s, User = u })
.Where(s => s.User.ActivationStatus == status.Value)
.Select(r => r.Security);
}
return await q.CountAsync();
}
public async IAsyncEnumerable<FileShareRecord> GetPureSharesAsync(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status, int offset = 0, int count = -1)
{
if (entry == null || count == 0)
{
yield break;
}
await using var filesDbContext = _dbContextFactory.CreateDbContext();
var q = await GetPureSharesQuery(entry, filterType, filesDbContext);
if (filterType == ShareFilterType.User)
{
var predicate = ShareCompareHelper.GetCompareExpression<SecurityUserRecord>(s => s.Security.Share);
var q1 = q.Join(filesDbContext.Users, s => s.Subject, u => u.Id,
(s, u) => new SecurityUserRecord { Security = s, User = u });
if (status.HasValue)
{
q = q1.Where(s => s.User.ActivationStatus == status.Value)
.OrderBy(predicate)
.Select(s => s.Security);
}
else
{
q = q1.OrderBy(s => s.User.ActivationStatus)
.ThenBy(predicate)
.Select(s => s.Security);
}
}
else
{
var predicate = ShareCompareHelper.GetCompareExpression<DbFilesSecurity>(s => s.Share);
q = q.OrderBy(predicate);
}
if (offset > 0)
{
q = q.Skip(offset);
}
if (count > 0)
{
q = q.Take(count);
}
await foreach (var r in q.ToAsyncEnumerable())
{
yield return await ToFileShareRecordAsync(r);
}
}
public async IAsyncEnumerable<UserInfoWithShared> GetUsersWithSharedAsync(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus,
bool excludeShared, int offset, int count)
{
if (entry == null || count == 0)
{
yield break;
}
await using var filesDbContext = _dbContextFactory.CreateDbContext();
var tenantId = TenantID;
var mappedId = (await MappingIDAsync(entry.Id)).ToString();
var q1 = GetUsersWithSharedQuery(tenantId, mappedId, entry, text, employeeStatus, activationStatus, excludeShared, filesDbContext);
if (offset > 0)
{
q1 = q1.Skip(offset);
}
if (count > 0)
{
q1 = q1.Take(count);
}
await foreach (var r in q1.ToAsyncEnumerable())
{
yield return new UserInfoWithShared { UserInfo = _mapper.Map<User, UserInfo>(r.User), Shared = r.Shared };
}
}
public async Task<int> GetUsersWithSharedCountAsync(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus,
bool excludeShared)
{
if (entry == null)
{
return 0;
}
await using var filesDbContext = _dbContextFactory.CreateDbContext();
var tenantId = TenantID;
var mappedId = (await MappingIDAsync(entry.Id)).ToString();
var q1 = GetUsersWithSharedQuery(tenantId, mappedId, entry, text, employeeStatus, activationStatus, excludeShared, filesDbContext);
return await q1.CountAsync();
}
private static IQueryable<UserWithShared> GetUsersWithSharedQuery(int tenantId, string entryId, FileEntry entry, string text, EmployeeStatus? employeeStatus,
EmployeeActivationStatus? activationStatus, bool excludeShared, FilesDbContext filesDbContext)
{
var q = filesDbContext.Users.AsNoTracking().Where(u => u.TenantId == tenantId);
if (employeeStatus.HasValue)
{
q = q.Where(u => u.Status == employeeStatus.Value);
}
if (activationStatus.HasValue)
{
q = q.Where(u => u.ActivationStatus == activationStatus.Value);
}
if (!string.IsNullOrEmpty(text))
{
q = q.Where(u => u.FirstName.Contains(text) || u.LastName.Contains(text) || u.Email.Contains(text));
}
var q1 = excludeShared
? q.Where(u => !filesDbContext.Security.Any(s => s.TenantId == tenantId && s.EntryType == entry.FileEntryType && s.EntryId == entryId && s.Subject == u.Id) &&
u.Id != entry.CreateBy)
.OrderBy(u => u.ActivationStatus)
.ThenBy(u => u.FirstName)
.Select(u => new UserWithShared { User = u, Shared = false })
: from user in q
join security in filesDbContext.Security.Where(s => s.TenantId == tenantId && s.EntryId == entryId && s.EntryType == entry.FileEntryType) on user.Id equals
security.Subject into grouping
from s in grouping.DefaultIfEmpty()
orderby user.ActivationStatus, user.FirstName
select new UserWithShared { User = user, Shared = s != null || user.Id == entry.CreateBy };
return q1;
}
internal async IAsyncEnumerable<FileShareRecord> InternalGetPureShareRecordsAsync(FileEntry<T> entry)
{
var files = new List<string>();
@ -234,6 +394,23 @@ internal abstract class SecurityBaseDao<T> : AbstractDao
await filesDbContext.SaveChangesAsync();
}
public async IAsyncEnumerable<FileShareRecord> GetPureSharesAsync(FileEntry<T> entry, IEnumerable<Guid> subjects)
{
if (subjects == null || !subjects.Any())
{
yield break;
}
var entryId = await MappingIDAsync(entry.Id);
await using var filesDbContext = _dbContextFactory.CreateDbContext();
await foreach (var security in Queries.EntrySharesBySubjectsAsync(filesDbContext, TenantID, entryId.ToString(), entry.FileEntryType, subjects))
{
yield return await ToFileShareRecordAsync(security);
}
}
internal async Task SelectFilesAndFoldersForShareAsync(FileEntry<T> entry, ICollection<string> files, ICollection<string> folders, ICollection<int> foldersInt)
{
@ -294,6 +471,32 @@ internal abstract class SecurityBaseDao<T> : AbstractDao
return result;
}
private async Task<IQueryable<DbFilesSecurity>> GetPureSharesQuery(FileEntry<T> entry, ShareFilterType filterType, FilesDbContext filesDbContext)
{
var entryId = await MappingIDAsync(entry.Id);
var q = filesDbContext.Security.AsNoTracking()
.Where(s => s.TenantId == TenantID && s.EntryId == entryId.ToString() && s.EntryType == entry.FileEntryType);
switch (filterType)
{
case ShareFilterType.User:
q = q.Where(s => s.SubjectType == SubjectType.User);
break;
case ShareFilterType.InvitationLink:
q = q.Where(s => s.SubjectType == SubjectType.InvitationLink);
break;
case ShareFilterType.ExternalLink:
q = q.Where(s => s.SubjectType == SubjectType.ExternalLink);
break;
case ShareFilterType.Link:
q = q.Where(s => s.SubjectType == SubjectType.InvitationLink || s.SubjectType == SubjectType.ExternalLink);
break;
}
return q;
}
}
[Scope]
@ -547,6 +750,24 @@ internal class SecurityTreeRecord
public int Level { get; set; }
}
public class SecurityUserRecord
{
public DbFilesSecurity Security { get; init; }
public User User { get; init; }
}
public class UserInfoWithShared
{
public UserInfo UserInfo { get; set; }
public bool Shared { get; set; }
}
public class UserWithShared
{
public User User { get; set; }
public bool Shared { get; set; }
}
static file class Queries
{
public static readonly Func<FilesDbContext, FileShareRecord, IAsyncEnumerable<DbFilesSecurity>>
@ -616,4 +837,10 @@ static file class Queries
.Where(r => r.TenantId == tenantId
&& (r.Subject == subject || r.Owner == subject))
.ExecuteDelete());
public static readonly Func<FilesDbContext, int, string, FileEntryType, IEnumerable<Guid>, IAsyncEnumerable<DbFilesSecurity>> EntrySharesBySubjectsAsync =
Microsoft.EntityFrameworkCore.EF.CompileAsyncQuery(
(FilesDbContext ctx, int tenantId, string entryId, FileEntryType entryType, IEnumerable<Guid> subjects) =>
ctx.Security
.Where(r => r.TenantId == tenantId && r.EntryId == entryId && r.EntryType == entryType && subjects.Contains(r.Subject)));
}

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using User = ASC.Core.Common.EF.User;
namespace ASC.Files.Core.EF;
public class FilesDbContext : DbContext
@ -42,6 +44,7 @@ public class FilesDbContext : DbContext
public DbSet<DbFilesProperties> FilesProperties { get; set; }
public DbSet<DbTenant> Tenants { get; set; }
public DbSet<FilesConverts> FilesConverts { get; set; }
public DbSet<User> Users { get; set; }
public FilesDbContext(DbContextOptions<FilesDbContext> dbContextOptions) : base(dbContextOptions) { }
@ -63,6 +66,7 @@ public class FilesDbContext : DbContext
.AddDbFilesProperties()
.AddDbTenant()
.AddFilesConverts()
.AddUser()
.AddDbFunctions();
}
}

View File

@ -533,7 +533,7 @@ public class FileStorageService //: IFileStorageService
var room = await InternalCreateNewFolderAsync(parentId, title, FolderType.PublicRoom, @private);
_ = await SetAceLinkAsync(room, SubjectType.ExternalLink, Guid.NewGuid(), FilesCommonResource.DefaultExternalLinkTitle, FileShare.Read, _actions[SubjectType.ExternalLink]);
return room;
}
@ -2513,17 +2513,47 @@ public class FileStorageService //: IFileStorageService
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(
IEnumerable<T> fileIds,
IEnumerable<T> folderIds,
IEnumerable<SubjectType> subjectTypes = null,
bool withoutTemplates = false)
IEnumerable<SubjectType> subjectTypes = null)
{
return await _fileSharing.GetSharedInfoAsync(fileIds, folderIds, subjectTypes, withoutTemplates);
return await _fileSharing.GetSharedInfoAsync(fileIds, folderIds, subjectTypes);
}
public async Task<List<AceShortWrapper>> GetSharedInfoShortFileAsync<T>(T fileId)
{
return await _fileSharing.GetSharedInfoShortFileAsync(fileId);
}
public async IAsyncEnumerable<AceWrapper> GetRoomSharedInfoAsync<T>(T roomId, ShareFilterType filterType, int offset, int count)
{
var room = await GetFolderDao<T>().GetFolderAsync(roomId).NotFoundIfNull();
await foreach (var ace in _fileSharing.GetRoomSharesAsync(room, filterType, null, offset, count))
{
yield return ace;
}
}
public async Task<int> GetRoomSharesCountAsync<T>(T roomId, ShareFilterType filterType)
{
var room = await GetFolderDao<T>().GetFolderAsync(roomId).NotFoundIfNull();
return await _fileSharing.GetRoomSharesCountAsync(room, filterType);
}
public async IAsyncEnumerable<AceWrapper> GetSharedInfoAsync<T>(T roomId, IEnumerable<Guid> subjects)
{
var room = await GetFolderDao<T>().GetFolderAsync(roomId).NotFoundIfNull();
await foreach (var ace in _fileSharing.GetPureSharesAsync(room, subjects))
{
yield return ace;
}
}
public async Task<string> SetAceObjectAsync<T>(AceCollection<T> aceCollection, bool notify)
{
var fileDao = GetFileDao<T>();
var folderDao = GetFolderDao<T>();
var securityDao = GetSecurityDao<T>();
var entries = new List<FileEntry<T>>();
string warning = null;
@ -2542,77 +2572,49 @@ public class FileStorageService //: IFileStorageService
{
try
{
var result = await _fileSharingAceHelper.SetAceObjectAsync(aceCollection.Aces, entry, notify, aceCollection.Message, aceCollection.AdvancedSettings);
warning ??= result.Warning;
var eventTypes = new List<(UserInfo User, EventType EventType, FileShare Access, string Email)>();
foreach (var ace in aceCollection.Aces)
if (!result.Changed)
{
var user = _userManager.GetUsers(ace.Id);
continue;
}
if (user == Constants.LostUser)
foreach (var (eventType, ace) in result.HandledAces)
{
if (ace.IsLink)
{
eventTypes.Add((null, EventType.Create, ace.Access, ace.Email));
continue;
}
var userId = user.Id;
var user = !string.IsNullOrEmpty(ace.Email)
? await _userManager.GetUserByEmailAsync(ace.Email)
: await _userManager.GetUsersAsync(ace.Id);
var name = user.DisplayUserName(false, _displayUserSettingsHelper);
var userSubjects = await _fileSecurity.GetUserSubjectsAsync(user.Id);
var usersRecords = await securityDao.GetSharesAsync(userSubjects).ToListAsync();
var recordEntrys = usersRecords.Select(r => r.EntryId.ToString());
EventType eventType;
if (usersRecords.Any() && ace.Access != FileShare.None && recordEntrys.Contains(entry.Id.ToString()))
if (entry is Folder<T> folder && DocSpaceHelper.IsRoom(folder.FolderType))
{
eventType = EventType.Update;
}
else if (!usersRecords.Any() || !recordEntrys.Contains(entry.Id.ToString()))
{
eventType = EventType.Create;
switch (eventType)
{
case EventType.Create:
_ = _filesMessageService.SendAsync(MessageAction.RoomCreateUser, entry, user.Id, name, GetAccessString(ace.Access));
break;
case EventType.Remove:
_ = _filesMessageService.SendAsync(MessageAction.RoomRemoveUser, entry, user.Id, name, GetAccessString(ace.Access));
break;
case EventType.Update:
_ = _filesMessageService.SendAsync(MessageAction.RoomUpdateAccessForUser, entry, user.Id, ace.Access, name);
break;
}
}
else
{
eventType = EventType.Remove;
}
eventTypes.Add((user, eventType, ace.Access, null));
}
var (changed, warningMessage) = await _fileSharingAceHelper.SetAceObjectAsync(aceCollection.Aces, entry, notify, aceCollection.Message, aceCollection.AdvancedSettings);
warning ??= warningMessage;
if (changed)
{
foreach (var e in eventTypes)
{
var user = e.User ?? await _userManager.GetUserByEmailAsync(e.Email);
var name = user.DisplayUserName(false, _displayUserSettingsHelper);
var access = e.Access;
if (entry.FileEntryType == FileEntryType.Folder && DocSpaceHelper.IsRoom(((Folder<T>)entry).FolderType))
{
switch (e.EventType)
{
case EventType.Create:
_ = _filesMessageService.SendAsync(MessageAction.RoomCreateUser, entry, user.Id, name, GetAccessString(access));
break;
case EventType.Remove:
_ = _filesMessageService.SendAsync(MessageAction.RoomRemoveUser, entry, user.Id, name, GetAccessString(access));
break;
case EventType.Update:
_ = _filesMessageService.SendAsync(MessageAction.RoomUpdateAccessForUser, entry, user.Id, access, name);
break;
}
}
else
{
_ = _filesMessageService.SendAsync(
entry.FileEntryType == FileEntryType.Folder ? MessageAction.FolderUpdatedAccessFor : MessageAction.FileUpdatedAccessFor,
entry,
entry.Title, name, GetAccessString(access));
}
_ = _filesMessageService.SendAsync(
entry.FileEntryType == FileEntryType.Folder ? MessageAction.FolderUpdatedAccessFor : MessageAction.FileUpdatedAccessFor,
entry,
entry.Title, name, GetAccessString(ace.Access));
}
}
}
@ -2666,7 +2668,7 @@ public class FileStorageService //: IFileStorageService
}
}
public async Task<List<AceWrapper>> SetInvitationLinkAsync<T>(T roomId, Guid linkId, string title, FileShare share)
public async Task<AceWrapper> SetInvitationLinkAsync<T>(T roomId, Guid linkId, string title, FileShare share)
{
var expirationDate = DateTime.UtcNow.Add(_invitationLinkHelper.IndividualLinkExpirationInterval);
@ -2675,13 +2677,13 @@ public class FileStorageService //: IFileStorageService
return await SetAceLinkAsync(room, SubjectType.InvitationLink, linkId, title, share, _actions[SubjectType.InvitationLink], expirationDate);
}
public async Task<List<AceWrapper>> SetExternalLinkAsync<T>(T entryId, FileEntryType entryType, Guid linkId, string title, FileShare share, DateTime expirationDate = default,
public async Task<AceWrapper> SetExternalLinkAsync<T>(T entryId, FileEntryType entryType, Guid linkId, string title, FileShare share, DateTime expirationDate = default,
string password = null, bool disabled = false, bool denyDownload = false)
{
FileEntry<T> entry = entryType == FileEntryType.File ? (await GetFileDao<T>().GetFileAsync(entryId)).NotFoundIfNull()
: (await GetFolderDao<T>().GetFolderAsync(entryId)).NotFoundIfNull();
return await SetAceLinkAsync(entry, SubjectType.ExternalLink, linkId, title, share, _actions[SubjectType.ExternalLink], expirationDate, password, disabled, denyDownload, 10);
return await SetAceLinkAsync(entry, SubjectType.ExternalLink, linkId, title, share, _actions[SubjectType.ExternalLink], expirationDate, password, disabled, denyDownload);
}
public async Task<bool> SetAceLinkAsync<T>(T fileId, FileShare share)
@ -2701,8 +2703,8 @@ public class FileStorageService //: IFileStorageService
try
{
var (changed, _) = await _fileSharingAceHelper.SetAceObjectAsync(aces, file, false, null, null);
if (changed)
var result = await _fileSharingAceHelper.SetAceObjectAsync(aces, file, false, null, null);
if (result.Changed)
{
_ = _filesMessageService.SendAsync(MessageAction.FileExternalLinkAccessUpdated, file, file.Title, GetAccessString(share));
}
@ -3119,10 +3121,13 @@ public class FileStorageService //: IFileStorageService
public async Task<bool> KeepNewFileNameAsync(bool set)
{
_filesSettingsHelper.KeepNewFileName = set;
await _messageService.SendHeadersMessageAsync(MessageAction.DocumentsKeepNewFileNameSettingsUpdated);
return _filesSettingsHelper.KeepNewFileName;
var current = _filesSettingsHelper.KeepNewFileName;
if (current != set)
{
_filesSettingsHelper.KeepNewFileName = set;
await _messageService.SendHeadersMessageAsync(MessageAction.DocumentsKeepNewFileNameSettingsUpdated);
}
return set;
}
public bool HideConfirmConvert(bool isForSave)
@ -3262,36 +3267,62 @@ public class FileStorageService //: IFileStorageService
return fileIds;
}
public async Task ResendEmailInvitationsAsync<T>(T id, IEnumerable<Guid> usersIds)
public async Task ResendEmailInvitationsAsync<T>(T id, IEnumerable<Guid> usersIds, bool resendAll)
{
ArgumentNullException.ThrowIfNull(usersIds);
if (!resendAll && (usersIds == null || !usersIds.Any()))
{
return;
}
var folderDao = _daoFactory.GetFolderDao<T>();
var room = await folderDao.GetFolderAsync(id);
var room = await folderDao.GetFolderAsync(id).NotFoundIfNull();
ErrorIf(room == null, FilesCommonResource.ErrorMassage_FolderNotFound);
ErrorIf(!await _fileSecurity.CanEditRoomAsync(room), FilesCommonResource.ErrorMassage_SecurityException);
var shares = (await _fileSharing.GetSharedInfoAsync(room)).ToDictionary(k => k.Id, v => v);
foreach (var userId in usersIds)
if (!resendAll)
{
if (!shares.TryGetValue(userId, out var share))
await foreach (var ace in _fileSharing.GetPureSharesAsync(room, usersIds))
{
continue;
var user = await _userManager.GetUsersAsync(ace.Id);
var link = await _invitationLinkService.GetInvitationLinkAsync(user.Email, ace.Access, _authContext.CurrentAccount.ID, room.Id.ToString());
await _studioNotifyService.SendEmailRoomInviteAsync(user.Email, room.Title, link);
}
return;
}
const int margin = 1;
const int packSize = 1000;
var offset = 0;
var finish = false;
while (!finish)
{
var counter = 0;
await foreach (var ace in _fileSharing.GetRoomSharesAsync(room, ShareFilterType.User, EmployeeActivationStatus.Pending, offset, packSize + margin))
{
counter++;
if (counter > packSize)
{
offset += packSize;
break;
}
var user = await _userManager.GetUsersAsync(ace.Id);
var link = await _invitationLinkService.GetInvitationLinkAsync(user.Email, ace.Access, _authContext.CurrentAccount.ID, id.ToString());
var shortenLink = await _urlShortener.GetShortenLinkAsync(link);
await _studioNotifyService.SendEmailRoomInviteAsync(user.Email, room.Title, shortenLink);
}
var user = await _userManager.GetUserAsync(share.Id, null);
if (user.ActivationStatus != EmployeeActivationStatus.Pending)
if (counter <= packSize)
{
continue;
finish = true;
}
var link = await _invitationLinkService.GetInvitationLinkAsync(user.Email, share.Access, _authContext.CurrentAccount.ID, id.ToString());
var shortenLink = await _urlShortener.GetShortenLinkAsync(link);
await _studioNotifyService.SendEmailRoomInviteAsync(user.Email, room.Title, shortenLink);
}
}
@ -3435,9 +3466,8 @@ public class FileStorageService //: IFileStorageService
}
}
private async Task<List<AceWrapper>> SetAceLinkAsync<T>(FileEntry<T> entry, SubjectType subjectType, Guid linkId, string title, FileShare share,
IReadOnlyDictionary<EventType, MessageAction> messageActions, DateTime expirationDate = default, string password = null, bool disabled = false, bool denyDownload = false,
int maxLinksCount = int.MaxValue)
private async Task<AceWrapper> SetAceLinkAsync<T>(FileEntry<T> entry, SubjectType subjectType, Guid linkId, string title, FileShare share,
IReadOnlyDictionary<EventType, MessageAction> messageActions, DateTime expirationDate = default, string password = null, bool disabled = false, bool denyDownload = false)
{
ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(title);
@ -3446,25 +3476,6 @@ public class FileStorageService //: IFileStorageService
linkId = Guid.NewGuid();
}
var action = EventType.Create;
var links = (await _fileSecurity.GetSharesAsync(entry))
.Where(r => r.SubjectType == subjectType).ToList();
if (share == FileShare.None)
{
action = EventType.Remove;
}
else if (links.Any(r => r.Subject == linkId))
{
action = EventType.Update;
}
if (action == EventType.Create && links.Count == maxLinksCount)
{
throw GenerateException(new InvalidOperationException(FilesCommonResource.ErrorMessage_MaxLinksCount));
}
var options = new FileShareOptions
{
Title = title,
@ -3497,11 +3508,16 @@ public class FileStorageService //: IFileStorageService
try
{
var (changed, _) = await _fileSharingAceHelper.SetAceObjectAsync(aces, entry, false, null, null);
var result = await _fileSharingAceHelper.SetAceObjectAsync(aces, entry, false, null, null);
if (changed)
if (!string.IsNullOrEmpty(result.Warning))
{
_ = _filesMessageService.SendAsync(messageActions[action], entry, entry.Title, GetAccessString(share));
throw GenerateException(new InvalidOperationException(result.Warning));
}
if (result.Changed)
{
_ = _filesMessageService.SendAsync(messageActions[result.HandledAces[0].Event], entry, entry.Title, GetAccessString(share));
}
}
catch (Exception e)
@ -3509,9 +3525,7 @@ public class FileStorageService //: IFileStorageService
throw GenerateException(e);
}
return entry.FileEntryType == FileEntryType.File ?
await GetSharedInfoAsync(new[] { entry.Id }, Array.Empty<T>()) :
await GetSharedInfoAsync(Array.Empty<T>(), new[] { entry.Id });
return (await _fileSharing.GetPureSharesAsync(entry, new[] { linkId }).FirstOrDefaultAsync());
}
private async Task<List<AceWrapper>> GetFullAceWrappersAsync(IEnumerable<FileShareParams> share)
@ -3569,13 +3583,6 @@ public class FileStorageService //: IFileStorageService
await SetAceObjectAsync(aceCollection, notify);
}
private enum EventType
{
Update,
Create,
Remove
}
private static readonly IReadOnlyDictionary<SubjectType, IReadOnlyDictionary<EventType, MessageAction>> _actions =
new Dictionary<SubjectType, IReadOnlyDictionary<EventType, MessageAction>>
{

View File

@ -926,8 +926,9 @@ public class FileSecurity : IFileSecurity
FileShareRecord ace;
if (!isRoom && e.RootFolderType is FolderType.VirtualRooms or FolderType.Archive &&
_cachedRecords.TryGetValue(GetCacheKey(e.ParentId, userId), out var value))
if (!isRoom && e.RootFolderType is FolderType.VirtualRooms or FolderType.Archive &&
(_cachedRecords.TryGetValue(GetCacheKey(e.ParentId, userId), out var value)) ||
_cachedRecords.TryGetValue(GetCacheKey(e.ParentId, await _externalShare.GetLinkIdAsync()), out value))
{
ace = value.Clone();
ace.EntryId = e.Id;
@ -965,6 +966,14 @@ public class FileSecurity : IFileSecurity
.ThenByDescending(r => r.Share, new FileShareRecord.ShareComparer())
.FirstOrDefault();
}
if (!isRoom && e.RootFolderType is FolderType.VirtualRooms or FolderType.Archive &&
ace is { SubjectType: SubjectType.User or SubjectType.ExternalLink })
{
var id = ace.SubjectType == SubjectType.ExternalLink ? ace.Subject : userId;
_cachedRecords.TryAdd(GetCacheKey(e.ParentId, id), ace);
}
}
if (ace is { SubjectType: SubjectType.ExternalLink } && ace.Subject != userId &&
@ -984,11 +993,6 @@ public class FileSecurity : IFileSecurity
e.Access = ace?.Share ?? defaultShare;
e.Access = e.RootFolderType is FolderType.ThirdpartyBackup ? FileShare.Restrict : e.Access;
if (ace is { IsLink: false } && !isRoom && e.RootFolderType is FolderType.VirtualRooms or FolderType.Archive && e.Access != FileShare.None)
{
_cachedRecords.TryAdd(GetCacheKey(e.ParentId, userId), ace);
}
switch (action)
{
case FilesSecurityActions.Read:
@ -996,7 +1000,8 @@ public class FileSecurity : IFileSecurity
case FilesSecurityActions.Mute:
return e.Access != FileShare.Restrict;
case FilesSecurityActions.Comment:
if (e.Access is FileShare.Comment or FileShare.Review ||
if (e.Access is FileShare.Comment ||
e.Access == FileShare.Review ||
e.Access == FileShare.CustomFilter ||
e.Access == FileShare.ReadWrite ||
e.Access == FileShare.RoomAdmin ||
@ -1149,7 +1154,7 @@ public class FileSecurity : IFileSecurity
if (e.Access != FileShare.Restrict && ace?.Options is not { DenyDownload: true })
{
return true;
}
}
break;
}
@ -1193,6 +1198,33 @@ public class FileSecurity : IFileSecurity
return await _daoFactory.GetSecurityDao<T>().GetSharesAsync(entry, subjects);
}
public IAsyncEnumerable<FileShareRecord> GetPureSharesAsync<T>(FileEntry<T> entry, IEnumerable<Guid> subjects)
{
return _daoFactory.GetSecurityDao<T>().GetPureSharesAsync(entry, subjects);
}
public IAsyncEnumerable<FileShareRecord> GetPureSharesAsync<T>(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status, int offset = 0, int count = -1)
{
return _daoFactory.GetSecurityDao<T>().GetPureSharesAsync(entry, filterType, status, offset, count);
}
public Task<int> GetPureSharesCountAsync<T>(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status)
{
return _daoFactory.GetSecurityDao<T>().GetPureSharesCountAsync(entry, filterType, status);
}
public IAsyncEnumerable<UserInfoWithShared> GetUsersWithSharedAsync<T>(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus,
bool excludeShared, int offset, int count)
{
return _daoFactory.GetSecurityDao<T>().GetUsersWithSharedAsync(entry, text, employeeStatus, activationStatus, excludeShared, offset, count);
}
public async Task<int> GetUsersWithSharedCountAsync<T>(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus,
bool excludeShared)
{
return await _daoFactory.GetSecurityDao<T>().GetUsersWithSharedCountAsync(entry, text, employeeStatus, activationStatus, excludeShared);
}
public async IAsyncEnumerable<FileEntry> GetSharesForMeAsync(FilterType filterType, bool subjectGroup, Guid subjectID, string searchText = "", bool searchInContent = false, bool withSubfolders = false)
{
var securityDao = _daoFactory.GetSecurityDao<int>();

View File

@ -41,17 +41,23 @@ public class FileShareRecord
public class ShareComparer : IComparer<FileShare>
{
private static readonly int[] _shareOrder = new[]
private static readonly int[] _shareOrder =
{
(int)FileShare.None,
(int)FileShare.ReadWrite,
(int)FileShare.CustomFilter,
(int)FileShare.Review,
(int)FileShare.FillForms,
(int)FileShare.Comment,
(int)FileShare.Read,
(int)FileShare.Restrict,
(int)FileShare.Varies
(int)FileShare.None,
(int)FileShare.RoomAdmin,
(int)FileShare.Collaborator,
(int)FileShare.Editing,
(int)FileShare.FillForms,
(int)FileShare.Review,
(int)FileShare.Comment,
(int)FileShare.Read,
// Not used
(int)FileShare.ReadWrite,
(int)FileShare.CustomFilter,
(int)FileShare.Varies,
(int)FileShare.Restrict
};
public int Compare(FileShare x, FileShare y)
@ -70,3 +76,39 @@ public class SmallShareRecord
public FileShare Share { get; set; }
public SubjectType SubjectType { get; set; }
}
public static class ShareCompareHelper
{
private static readonly ConcurrentDictionary<string, Expression> _predicates = new();
public static Expression<Func<TType, int>> GetCompareExpression<TType>(Expression<Func<TType, FileShare>> memberExpression)
{
var type = typeof(TType);
var key = type.ToString();
if (_predicates.TryGetValue(key, out var value))
{
return (Expression<Func<TType, int>>)value;
}
var shares = Enum.GetValues<FileShare>()
.Order(new FileShareRecord.ShareComparer())
.ToList();
ConditionalExpression expression = null;
for (var i = shares.Count - 1; i >= 0; i--)
{
expression = Expression.Condition(
Expression.Equal(memberExpression.Body, Expression.Constant(shares[i])), Expression.Constant(i),
expression != null ? expression : Expression.Constant(i + 1));
}
var predicate = Expression.Lambda<Func<TType, int>>(expression!, memberExpression.Parameters[0]);
_predicates.TryAdd(key, predicate);
return predicate;
}
}

View File

@ -38,4 +38,9 @@ public interface ISecurityDao<T>
IAsyncEnumerable<FileShareRecord> GetPureShareRecordsAsync(FileEntry<T> entry);
Task DeleteShareRecordsAsync(IEnumerable<FileShareRecord> records);
Task<bool> IsSharedAsync(T entryId, FileEntryType type);
IAsyncEnumerable<FileShareRecord> GetPureSharesAsync(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status, int offset = 0, int count = -1);
Task<int> GetPureSharesCountAsync(FileEntry<T> entry, ShareFilterType filterType, EmployeeActivationStatus? status);
IAsyncEnumerable<FileShareRecord> GetPureSharesAsync(FileEntry<T> entry, IEnumerable<Guid> subjects);
IAsyncEnumerable<UserInfoWithShared> GetUsersWithSharedAsync(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus, bool excludeShared, int offset = 0, int count = -1);
Task<int> GetUsersWithSharedCountAsync(FileEntry<T> entry, string text, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus, bool excludeShared);
}

View File

@ -26,6 +26,15 @@
namespace ASC.Files.Core.Security;
[Flags]
public enum ShareFilterType
{
User = 0,
InvitationLink = 1,
ExternalLink = 2,
Link = InvitationLink | ExternalLink
}
public enum SubjectType
{
User = 0,

View File

@ -65,6 +65,6 @@ public class UsersInRoomStatistic : ITenantQuotaFeatureStat<UsersInRoomFeature,
return 0;
}
return await securityDao.GetPureShareRecordsAsync(folder).Where(r => !r.IsLink).CountAsync();
return await securityDao.GetPureSharesCountAsync(folder, ShareFilterType.User, null);
}
}

View File

@ -217,7 +217,7 @@ class FileDownloadOperation<T> : FileOperation<FileDownloadOperationData<T>, T>
PublishChanges();
var filesMessageService = _serviceProvider.GetRequiredService<FilesMessageService>();
var filesMessageService = scope.ServiceProvider.GetRequiredService<FilesMessageService>();
foreach (var file in filesForSend)
{
var key = file.Id;

View File

@ -42,7 +42,6 @@ public class AceWrapper : IMapFrom<RoomInvitation>
public SubjectType SubjectType { get; set; }
public FileShareOptions FileShareOptions { get; set; }
public bool CanEditAccess { get; set; }
public bool IsTemplate { get; set; }
[JsonPropertyName("title")]
public string SubjectName { get; set; }

View File

@ -47,6 +47,9 @@ public class FileSharingAceHelper
private readonly UserManagerWrapper _userManagerWrapper;
private readonly CountPaidUserChecker _countPaidUserChecker;
private readonly IUrlShortener _urlShortener;
private const int MaxInvitationLinks = 1;
private const int MaxExternalLinks = 10;
public FileSharingAceHelper(
FileSecurity fileSecurity,
@ -86,23 +89,20 @@ public class FileSharingAceHelper
_urlShortener = urlShortener;
}
public async Task<(bool, string)> SetAceObjectAsync<T>(List<AceWrapper> aceWrappers, FileEntry<T> entry, bool notify, string message, AceAdvancedSettingsWrapper advancedSettings)
public async Task<AceProcessingResult> SetAceObjectAsync<T>(List<AceWrapper> aceWrappers, FileEntry<T> entry, bool notify, string message, AceAdvancedSettingsWrapper advancedSettings)
{
if (entry == null)
{
throw new ArgumentNullException(FilesCommonResource.ErrorMassage_BadRequest);
}
if (!aceWrappers.All(r => r.Id == _authContext.CurrentAccount.ID && r.Access == FileShare.None) && !await _fileSharingHelper.CanSetAccessAsync(entry) && advancedSettings is not { InvitationLink: true })
{
throw new SecurityException(FilesCommonResource.ErrorMassage_SecurityException);
}
if (entry is Folder<T> { Private: true } && advancedSettings is not { AllowSharingPrivateRoom: true })
if (!aceWrappers.All(r => r.Id == _authContext.CurrentAccount.ID && r.Access == FileShare.None) &&
!await _fileSharingHelper.CanSetAccessAsync(entry) && advancedSettings is not { InvitationLink: true })
{
throw new SecurityException(FilesCommonResource.ErrorMassage_SecurityException);
}
var handledAces = new List<(EventType, AceWrapper)>(aceWrappers.Count);
var ownerId = entry.RootFolderType == FolderType.USER ? entry.RootCreateBy : entry.CreateBy;
var room = entry is Folder<T> folder && DocSpaceHelper.IsRoom(folder.FolderType) ? folder : null;
var entryType = entry.FileEntryType;
@ -110,16 +110,23 @@ public class FileSharingAceHelper
var usersWithoutRight = new List<Guid>();
var changed = false;
string warning = null;
var shares = (await _fileSecurity.GetSharesAsync(entry)).ToList();
var shares = await _fileSecurity.GetPureSharesAsync(entry, aceWrappers.Select(a => a.Id))
.ToDictionaryAsync(r => r.Subject);
foreach (var w in aceWrappers.OrderByDescending(ace => ace.SubjectGroup))
{
if (w.Id == _authContext.CurrentAccount.ID)
{
continue;
}
var emailInvite = !string.IsNullOrEmpty(w.Email);
var currentUserType = await _userManager.GetUserTypeAsync(w.Id);
var userType = EmployeeType.User;
var existedShare = shares.FirstOrDefault(r => r.Subject == w.Id);
var existedShare = shares.Get(w.Id);
var rightIsAvailable = FileSecurity.AvailableUserRights.TryGetValue(currentUserType, out var userAccesses)
&& userAccesses.Contains(w.Access);
var eventType = existedShare != null ? w.Access == FileShare.None ? EventType.Remove : EventType.Update : EventType.Create;
if (room != null)
{
@ -127,15 +134,44 @@ public class FileSharingAceHelper
{
continue;
}
if (room.FolderType is not (FolderType.PublicRoom or FolderType.CustomRoom) && w.SubjectType == SubjectType.ExternalLink)
if (room.FolderType == FolderType.PublicRoom && w.Access == FileShare.Read && w.SubjectType != SubjectType.ExternalLink)
{
continue;
}
if (room.FolderType == FolderType.PublicRoom && w.Access == FileShare.Read && w.SubjectType != SubjectType.ExternalLink)
switch (w.SubjectType)
{
continue;
case SubjectType.ExternalLink when room.FolderType is not (FolderType.PublicRoom or FolderType.CustomRoom):
case SubjectType.ExternalLink when w.Access is not (FileShare.Read or FileShare.None):
continue;
case SubjectType.ExternalLink:
{
if (eventType == EventType.Create)
{
var linksCount = await _fileSecurity.GetPureSharesCountAsync(entry, ShareFilterType.ExternalLink, null);
if (linksCount >= MaxExternalLinks)
{
warning ??= string.Format(FilesCommonResource.ErrorMessage_MaxLinksCount, MaxExternalLinks);
continue;
}
}
break;
}
case SubjectType.InvitationLink when eventType == EventType.Create:
{
var linksCount = await _fileSecurity.GetPureSharesCountAsync(entry, ShareFilterType.InvitationLink, null);
if (linksCount >= MaxInvitationLinks)
{
warning ??= string.Format(FilesCommonResource.ErrorMessage_MaxLinksCount, MaxInvitationLinks);
continue;
}
break;
}
}
}
@ -225,6 +261,7 @@ public class FileSharingAceHelper
await _fileSecurity.ShareAsync(entry.Id, entryType, w.Id, share, w.SubjectType, w.FileShareOptions);
changed = true;
handledAces.Add((eventType, w));
if (emailInvite)
{
@ -271,9 +308,7 @@ public class FileSharingAceHelper
|| (share == FileShare.None && entry.RootFolderType == FolderType.COMMON);
var removeNew = share == FileShare.Restrict || (share == FileShare.None
&& (entry.RootFolderType == FolderType.USER ||
entry.RootFolderType == FolderType.VirtualRooms ||
entry.RootFolderType == FolderType.Archive));
&& entry.RootFolderType is FolderType.USER or FolderType.VirtualRooms or FolderType.Archive);
listUsersId.ForEach(id =>
{
@ -303,8 +338,7 @@ public class FileSharingAceHelper
await _fileMarker.MarkAsNewAsync(entry, recipients.Keys.ToList());
}
if ((entry.RootFolderType == FolderType.USER
|| entry.RootFolderType == FolderType.Privacy)
if (entry.RootFolderType is FolderType.USER or FolderType.Privacy
&& notify)
{
await _notifyClient.SendShareNoticeAsync(entry, recipients, message);
@ -322,7 +356,7 @@ public class FileSharingAceHelper
await _fileMarker.RemoveMarkAsNewAsync(entry, userId);
}
return (changed, warning);
return new AceProcessingResult(changed, warning, handledAces);
}
public async Task RemoveAceAsync<T>(FileEntry<T> entry)
@ -473,7 +507,72 @@ public class FileSharing
return await _fileSharingHelper.CanSetAccessAsync(entry);
}
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(FileEntry<T> entry, IEnumerable<SubjectType> subjectsTypes = null, bool withoutTemplates = false)
public async IAsyncEnumerable<AceWrapper> GetPureSharesAsync<T>(FileEntry<T> entry, IEnumerable<Guid> subjects)
{
if (entry == null)
{
throw new ArgumentNullException(FilesCommonResource.ErrorMassage_BadRequest);
}
if (!await _fileSecurity.CanReadAsync(entry))
{
_logger.ErrorUserCanTGetSharedInfo(_authContext.CurrentAccount.ID, entry.FileEntryType, entry.Id.ToString()!);
yield break;
}
var canEditAccess = await _fileSecurity.CanEditAccessAsync(entry);
await foreach (var record in _fileSecurity.GetPureSharesAsync(entry, subjects))
{
yield return await ToAceAsync(entry, record, canEditAccess);
}
}
public async IAsyncEnumerable<AceWrapper> GetRoomSharesAsync<T>(Folder<T> room, ShareFilterType filterType, EmployeeActivationStatus? status, int offset, int count)
{
if (room == null || !DocSpaceHelper.IsRoom(room.FolderType))
{
throw new ArgumentNullException(FilesCommonResource.ErrorMassage_BadRequest);
}
if (!await _fileSecurity.CanReadAsync(room))
{
_logger.ErrorUserCanTGetSharedInfo(_authContext.CurrentAccount.ID, room.FileEntryType, room.Id.ToString()!);
yield break;
}
var canEditAccess = await _fileSecurity.CanEditAccessAsync(room);
var allDefaultAces = await GetDefaultRoomAcesAsync(room, filterType, status).ToListAsync();
var defaultAces = allDefaultAces.Skip(offset).Take(count).ToList();
offset = Math.Max(defaultAces.Count > 0 ? 0 : offset - allDefaultAces.Count, 0);
count -= defaultAces.Count;
var records = _fileSecurity.GetPureSharesAsync(room, filterType, status, offset, count);
foreach (var record in defaultAces)
{
yield return record;
}
await foreach (var record in records)
{
yield return await ToAceAsync(room, record, canEditAccess);
}
}
public async Task<int> GetRoomSharesCountAsync<T>(Folder<T> room, ShareFilterType filterType)
{
var defaultAces = await GetDefaultRoomAcesAsync(room, filterType, null).CountAsync();
var sharesCount = await _fileSecurity.GetPureSharesCountAsync(room, filterType, null);
return defaultAces + sharesCount;
}
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(FileEntry<T> entry, IEnumerable<SubjectType> subjectsTypes = null)
{
if (entry == null)
{
@ -586,38 +685,6 @@ public class FileSharing
result.Add(w);
}
if (isRoom && canEditAccess && !withoutTemplates)
{
var invitationId = Guid.NewGuid();
var invitationAceTemplate = new AceWrapper
{
Id = invitationId,
Link = _invitationLinkService.GetInvitationLink(invitationId, _authContext.CurrentAccount.ID),
SubjectGroup = true,
Access = ((Folder<T>)entry).FolderType == FolderType.PublicRoom ? FileShare.RoomAdmin : FileShare.Read,
Owner = false,
IsTemplate = true,
SubjectType = SubjectType.InvitationLink
};
var externalId = Guid.NewGuid();
var externalAceTemplate = new AceWrapper
{
Id = externalId,
Link = await _externalShare.GetLinkAsync(externalId),
SubjectGroup = true,
Access = FileShare.Read,
Owner = false,
IsTemplate = true,
SubjectType = SubjectType.ExternalLink
};
result.Add(invitationAceTemplate);
result.Add(externalAceTemplate);
}
if (entry.FileEntryType == FileEntryType.File && result.All(w => w.Id != FileConstant.ShareLinkId)
&& entry.FileEntryType == FileEntryType.File
&& !((File<T>)entry).Encrypted)
@ -696,8 +763,7 @@ public class FileSharing
return result;
}
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(IEnumerable<T> fileIds, IEnumerable<T> folderIds, IEnumerable<SubjectType> subjectTypes = null,
bool withoutTemplates = false)
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(IEnumerable<T> fileIds, IEnumerable<T> folderIds, IEnumerable<SubjectType> subjectTypes = null)
{
if (!_authContext.IsAuthenticated)
{
@ -719,7 +785,7 @@ public class FileSharing
IEnumerable<AceWrapper> acesForObject;
try
{
acesForObject = await GetSharedInfoAsync(entry, subjectTypes, withoutTemplates);
acesForObject = await GetSharedInfoAsync(entry, subjectTypes);
}
catch (Exception e)
{
@ -821,4 +887,94 @@ public class FileSharing
.Where(aceWrapper => !aceWrapper.Id.Equals(FileConstant.ShareLinkId) || aceWrapper.Access != FileShare.Restrict)
.Select(aceWrapper => new AceShortWrapper(aceWrapper)));
}
private async IAsyncEnumerable<AceWrapper> GetDefaultRoomAcesAsync<T>(Folder<T> room, ShareFilterType filterType, EmployeeActivationStatus? status)
{
if (filterType != ShareFilterType.User)
{
yield break;
}
if (status.HasValue)
{
var user = await _userManager.GetUsersAsync(room.CreateBy);
if (user.ActivationStatus != status.Value)
{
yield break;
}
}
var owner = new AceWrapper
{
Id = room.CreateBy,
SubjectName = await _global.GetUserNameAsync(room.CreateBy),
SubjectGroup = false,
Access = FileShare.ReadWrite,
Owner = true,
CanEditAccess = false,
};
yield return owner;
}
private async Task<AceWrapper> ToAceAsync(FileEntry entry, FileShareRecord record, bool canEditAccess)
{
var w = new AceWrapper
{
Id = record.Subject,
SubjectGroup = false,
Access = record.Share,
FileShareOptions = record.Options,
SubjectType = record.SubjectType
};
w.CanEditAccess = _authContext.CurrentAccount.ID != w.Id && (w.SubjectType is SubjectType.User or SubjectType.Group) && canEditAccess;
if (record.IsLink)
{
var link = record.SubjectType == SubjectType.InvitationLink ?
_invitationLinkService.GetInvitationLink(record.Subject, _authContext.CurrentAccount.ID) :
await _externalShare.GetLinkAsync(record.Subject);
w.Link = await _urlShortener.GetShortenLinkAsync(link);
w.SubjectGroup = true;
w.CanEditAccess = false;
w.FileShareOptions.Password = await _externalShare.GetPasswordAsync(w.FileShareOptions.Password);
w.SubjectType = record.SubjectType;
}
else
{
var user = await _userManager.GetUsersAsync(record.Subject);
w.SubjectName = user.DisplayUserName(false, _displayUserSettingsHelper);
w.Owner = entry.RootFolderType == FolderType.USER
? entry.RootCreateBy == record.Subject
: entry.CreateBy == record.Subject;
w.LockedRights = record.Subject == _authContext.CurrentAccount.ID;
}
return w;
}
}
public class AceProcessingResult
{
public bool Changed { get; }
public string Warning { get; }
public IReadOnlyList<(EventType Event, AceWrapper Ace)> HandledAces { get; }
public AceProcessingResult(bool changed, string warning, IReadOnlyList<(EventType Event, AceWrapper Ace)> handledAces)
{
Changed = changed;
Warning = warning;
HandledAces = handledAces;
}
}
public enum EventType
{
Update,
Create,
Remove
}

View File

@ -1,4 +1,4 @@
// (c) Copyright Ascensio System SIA 2010-2022
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
@ -40,7 +40,8 @@ public class VirtualRoomsInternalController : VirtualRoomsController<int>
FileDtoHelper fileDtoHelper,
FileShareDtoHelper fileShareDtoHelper,
IMapper mapper,
SocketManager socketManager) : base(
SocketManager socketManager,
ApiContext apiContext) : base(
globalFolderHelper,
fileOperationDtoHelper,
coreBaseSettings,
@ -51,7 +52,8 @@ public class VirtualRoomsInternalController : VirtualRoomsController<int>
fileDtoHelper,
fileShareDtoHelper,
mapper,
socketManager)
socketManager,
apiContext)
{
}
@ -88,7 +90,8 @@ public class VirtualRoomsThirdPartyController : VirtualRoomsController<string>
FileDtoHelper fileDtoHelper,
FileShareDtoHelper fileShareDtoHelper,
IMapper mapper,
SocketManager socketManager) : base(
SocketManager socketManager,
ApiContext apiContext) : base(
globalFolderHelper,
fileOperationDtoHelper,
coreBaseSettings,
@ -99,7 +102,8 @@ public class VirtualRoomsThirdPartyController : VirtualRoomsController<string>
fileDtoHelper,
fileShareDtoHelper,
mapper,
socketManager)
socketManager,
apiContext)
{
}
@ -135,6 +139,7 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
private readonly FileShareDtoHelper _fileShareDtoHelper;
private readonly IMapper _mapper;
private readonly SocketManager _socketManager;
private readonly ApiContext _apiContext;
protected VirtualRoomsController(
GlobalFolderHelper globalFolderHelper,
@ -147,7 +152,8 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
FileDtoHelper fileDtoHelper,
FileShareDtoHelper fileShareDtoHelper,
IMapper mapper,
SocketManager socketManager) : base(folderDtoHelper, fileDtoHelper)
SocketManager socketManager,
ApiContext apiContext) : base(folderDtoHelper, fileDtoHelper)
{
_globalFolderHelper = globalFolderHelper;
_fileOperationDtoHelper = fileOperationDtoHelper;
@ -158,6 +164,7 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
_fileShareDtoHelper = fileShareDtoHelper;
_mapper = mapper;
_socketManager = socketManager;
_apiContext = apiContext;
}
/// <summary>
@ -286,22 +293,25 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
var result = new RoomSecurityDto();
if (inDto.Invitations != null && inDto.Invitations.Any())
if (inDto.Invitations == null || !inDto.Invitations.Any())
{
var wrappers = _mapper.Map<IEnumerable<RoomInvitation>, List<AceWrapper>>(inDto.Invitations);
var aceCollection = new AceCollection<T>
{
Files = Array.Empty<T>(),
Folders = new[] { id },
Aces = wrappers,
Message = inDto.Message
};
result.Warning = await _fileStorageService.SetAceObjectAsync(aceCollection, inDto.Notify);
return result;
}
result.Members = await GetRoomSecurityInfoAsync(id).ToListAsync();
var wrappers = _mapper.Map<IEnumerable<RoomInvitation>, List<AceWrapper>>(inDto.Invitations);
var aceCollection = new AceCollection<T>
{
Files = Array.Empty<T>(),
Folders = new[] { id },
Aces = wrappers,
Message = inDto.Message
};
result.Warning = await _fileStorageService.SetAceObjectAsync(aceCollection, inDto.Notify);
result.Members = await _fileStorageService.GetSharedInfoAsync(id, inDto.Invitations.Select(s => s.Id))
.SelectAwait(async a => await _fileShareDtoHelper.Get(a))
.ToListAsync();
return result;
}
@ -312,19 +322,30 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
/// <short>Get room access rights</short>
/// <category>Rooms</category>
/// <param type="System.Int32, System" method="url" name="id">Room ID</param>
/// <param name="filterType">
/// Share type filter
/// </param>
/// <returns type="ASC.Files.Core.ApiModels.ResponseDto.FileShareDto, ASC.Files.Core">Security information of room files</returns>
/// <path>api/2.0/files/rooms/{id}/share</path>
/// <httpMethod>GET</httpMethod>
/// <collection>list</collection>
[HttpGet("rooms/{id}/share")]
public async IAsyncEnumerable<FileShareDto> GetRoomSecurityInfoAsync(T id)
public async IAsyncEnumerable<FileShareDto> GetRoomSecurityInfoAsync(T id, ShareFilterType filterType = ShareFilterType.User)
{
var fileShares = await _fileStorageService.GetSharedInfoAsync(Array.Empty<T>(), new[] { id });
var offset = Convert.ToInt32(_apiContext.StartIndex);
var count = Convert.ToInt32(_apiContext.Count);
var counter = 0;
foreach (var fileShareDto in fileShares)
var totalCountTask = _fileStorageService.GetRoomSharesCountAsync(id, filterType);
await foreach (var ace in _fileStorageService.GetRoomSharedInfoAsync(id, filterType, offset, count))
{
yield return await _fileShareDtoHelper.Get(fileShareDto);
counter++;
yield return await _fileShareDtoHelper.Get(ace);
}
_apiContext.SetCount(counter).SetTotalCount(await totalCountTask);
}
/// <summary>
@ -339,9 +360,9 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
/// <httpMethod>PUT</httpMethod>
/// <collection>list</collection>
[HttpPut("rooms/{id}/links")]
public async IAsyncEnumerable<FileShareDto> SetLinkAsync(T id, LinkRequestDto inDto)
public async Task<FileShareDto> SetLinkAsync(T id, LinkRequestDto inDto)
{
var fileShares = inDto.LinkType switch
var linkAce = inDto.LinkType switch
{
LinkType.Invitation => await _fileStorageService.SetInvitationLinkAsync(id, inDto.LinkId, inDto.Title, inDto.Access),
LinkType.External => await _fileStorageService.SetExternalLinkAsync(id, FileEntryType.Folder, inDto.LinkId, inDto.Title,
@ -349,10 +370,7 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
_ => throw new InvalidOperationException()
};
foreach (var fileShareDto in fileShares)
{
yield return await _fileShareDtoHelper.Get(fileShareDto);
}
return linkAce != null ? await _fileShareDtoHelper.Get(linkAce) : null;
}
/// <summary>
@ -369,21 +387,24 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
[HttpGet("rooms/{id}/links")]
public async IAsyncEnumerable<FileShareDto> GetLinksAsync(T id, LinkType? type)
{
var subjectTypes = type.HasValue
? type.Value switch
var filterType = type.HasValue ? type.Value switch
{
LinkType.Invitation => new[] { SubjectType.InvitationLink },
LinkType.External => new[] { SubjectType.ExternalLink },
_ => new[] { SubjectType.InvitationLink, SubjectType.ExternalLink }
}
: new[] { SubjectType.InvitationLink, SubjectType.ExternalLink };
LinkType.Invitation => ShareFilterType.InvitationLink,
LinkType.External => ShareFilterType.ExternalLink,
_ => ShareFilterType.Link
}
: ShareFilterType.Link;
var fileShares = await _fileStorageService.GetSharedInfoAsync(Array.Empty<T>(), new[] { id }, subjectTypes, true);
foreach (var fileShareDto in fileShares)
var counter = 0;
await foreach (var ace in _fileStorageService.GetRoomSharedInfoAsync(id, filterType, 0, 100))
{
yield return await _fileShareDtoHelper.Get(fileShareDto);
counter++;
yield return await _fileShareDtoHelper.Get(ace);
}
_apiContext.SetCount(counter);
}
/// <summary>
@ -518,9 +539,9 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
/// <path>api/2.0/files/rooms/{id}/resend</path>
/// <httpMethod>POST</httpMethod>
[HttpPost("rooms/{id}/resend")]
public async Task ResendEmailInvitationsAsync(T id, UserInvintationRequestDto inDto)
public async Task ResendEmailInvitationsAsync(T id, UserInvitationRequestDto inDto)
{
await _fileStorageService.ResendEmailInvitationsAsync(id, inDto.UsersIds);
await _fileStorageService.ResendEmailInvitationsAsync(id, inDto.UsersIds, inDto.ResendAll);
}
protected void ErrorIfNotDocSpace()

View File

@ -1889,3 +1889,72 @@ public class UserController : PeopleControllerBase
// }
//}
}
[ConstraintRoute("int")]
public class UserControllerAdditionalInternal : UserControllerAdditional<int>
{
public UserControllerAdditionalInternal(
EmployeeFullDtoHelper employeeFullDtoHelper,
FileSecurity fileSecurity,
ApiContext apiContext,
IDaoFactory daoFactory)
: base(employeeFullDtoHelper, fileSecurity, apiContext, daoFactory)
{
}
}
public class UserControllerAdditionalThirdParty : UserControllerAdditional<string>
{
public UserControllerAdditionalThirdParty(
EmployeeFullDtoHelper employeeFullDtoHelper,
FileSecurity fileSecurity,
ApiContext apiContext,
IDaoFactory daoFactory)
: base(employeeFullDtoHelper, fileSecurity, apiContext, daoFactory)
{
}
}
public class UserControllerAdditional<T> : ApiControllerBase
{
private readonly EmployeeFullDtoHelper _employeeFullDtoHelper;
private readonly FileSecurity _fileSecurity;
private readonly ApiContext _apiContext;
private readonly IDaoFactory _daoFactory;
public UserControllerAdditional(
EmployeeFullDtoHelper employeeFullDtoHelper,
FileSecurity fileSecurity,
ApiContext apiContext,
IDaoFactory daoFactory)
{
_employeeFullDtoHelper = employeeFullDtoHelper;
_fileSecurity = fileSecurity;
_apiContext = apiContext;
_daoFactory = daoFactory;
}
[HttpGet("room/{id}")]
public async IAsyncEnumerable<EmployeeFullDto> GetUsersWithRoomSharedAsync(T id, EmployeeStatus? employeeStatus, EmployeeActivationStatus? activationStatus, bool? excludeShared)
{
var offset = Convert.ToInt32(_apiContext.StartIndex);
var count = Convert.ToInt32(_apiContext.Count);
var room = (await _daoFactory.GetFolderDao<T>().GetFolderAsync(id)).NotFoundIfNull();
var totalCountTask = _fileSecurity.GetUsersWithSharedCountAsync(room, _apiContext.FilterValue, employeeStatus, activationStatus, excludeShared ?? false);
var counter = 0;
await foreach (var u in _fileSecurity.GetUsersWithSharedAsync(room, _apiContext.FilterValue, employeeStatus, activationStatus, excludeShared ?? false, offset,
count))
{
counter++;
yield return await _employeeFullDtoHelper.GetFullAsync(u.UserInfo, u.Shared);
}
_apiContext.SetCount(counter).SetTotalCount(await totalCountTask);
}
}

View File

@ -24,23 +24,24 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
global using System.Globalization;
global using System.Globalization;
global using System.Net.Mail;
global using System.Security;
global using System.Security.Claims;
global using System.Security.Claims;
global using System.ServiceModel.Security;
global using System.Web;
global using ASC.Api.Core;
global using ASC.Api.Core.Convention;
global using ASC.Api.Core.Extensions;
global using ASC.Api.Core.Model;
global using ASC.Api.Core.Security;
global using ASC.Api.Core.Security;
global using ASC.Api.Core.Routing;
global using ASC.Api.Utils;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.Caching;
global using ASC.Common.Log;
global using ASC.Common.Threading;
global using ASC.Common.Threading;
global using ASC.Common.Utils;
global using ASC.Common.Web;
global using ASC.Core;
@ -54,19 +55,19 @@ global using ASC.Data.Reassigns;
global using ASC.FederatedLogin;
global using ASC.FederatedLogin.LoginProviders;
global using ASC.FederatedLogin.Profile;
global using ASC.Files.Core;
global using ASC.Files.Core.Core;
global using ASC.Files.Core.EF;
global using ASC.Files.Core;
global using ASC.Files.Core.Core;
global using ASC.Files.Core.EF;
global using ASC.Files.Core.Resources;
global using ASC.Files.Core.Security;
global using ASC.Files.Core.VirtualRooms;
global using ASC.Files.Core.VirtualRooms;
global using ASC.MessagingSystem.Core;
global using ASC.MessagingSystem.EF.Model;
global using ASC.People;
global using ASC.People.Api;
global using ASC.People.ApiModels.RequestDto;
global using ASC.People.ApiModels.ResponseDto;
global using ASC.People.Log;
global using ASC.People.Log;
global using ASC.People.Resources;
global using ASC.Security.Cryptography;
global using ASC.Web.Api.Models;
@ -74,30 +75,30 @@ global using ASC.Web.Api.Routing;
global using ASC.Web.Core;
global using ASC.Web.Core.Mobile;
global using ASC.Web.Core.PublicResources;
global using ASC.Web.Core.Quota;
global using ASC.Web.Core.Quota;
global using ASC.Web.Core.Users;
global using ASC.Web.Core.Utility;
global using ASC.Web.Files;
global using ASC.Web.Studio.Core;
global using ASC.Web.Studio.Core.Notify;
global using ASC.Web.Studio.Utility;
global using Autofac;
global using Microsoft.AspNetCore.Http.Extensions;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore.RateLimiting;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.Extensions.Hosting.WindowsServices;
global using SixLabors.ImageSharp;
global using SixLabors.ImageSharp.Formats;
global using SixLabors.ImageSharp.Formats;
global using Module = ASC.Api.Core.Module;
global using SecurityContext = ASC.Core.SecurityContext;
global using AllowAnonymousAttribute = Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute;
global using AuthorizeAttribute = Microsoft.AspNetCore.Authorization.AuthorizeAttribute;
global using HttpDeleteAttribute = Microsoft.AspNetCore.Mvc.HttpDeleteAttribute;
global using HttpGetAttribute = Microsoft.AspNetCore.Mvc.HttpGetAttribute;
global using HttpPostAttribute = Microsoft.AspNetCore.Mvc.HttpPostAttribute;
global using SecurityContext = ASC.Core.SecurityContext;
global using AllowAnonymousAttribute = Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute;
global using AuthorizeAttribute = Microsoft.AspNetCore.Authorization.AuthorizeAttribute;
global using HttpDeleteAttribute = Microsoft.AspNetCore.Mvc.HttpDeleteAttribute;
global using HttpGetAttribute = Microsoft.AspNetCore.Mvc.HttpGetAttribute;
global using HttpPostAttribute = Microsoft.AspNetCore.Mvc.HttpPostAttribute;
global using HttpPutAttribute = Microsoft.AspNetCore.Mvc.HttpPutAttribute;

View File

@ -1,12 +1,10 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_21120_56765)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.5 2C5.18629 2 2.5 4.68629 2.5 8C2.5 11.3137 5.18629 14 8.5 14C11.8137 14 14.5 11.3137 14.5 8C14.5 4.68629 11.8137 2 8.5 2ZM0.5 8C0.5 3.58172 4.08172 0 8.5 0C12.9183 0 16.5 3.58172 16.5 8C16.5 12.4183 12.9183 16 8.5 16C4.08172 16 0.5 12.4183 0.5 8Z" fill="#333333"/>
<circle cx="8.5" cy="5" r="1" fill="#333333"/>
<rect x="7.5" y="7" width="2" height="5" rx="1" fill="#333333"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_460_8224)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2ZM0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM9 5C9 5.55228 8.55228 6 8 6C7.44772 6 7 5.55228 7 5C7 4.44772 7.44772 4 8 4C8.55228 4 9 4.44772 9 5ZM8 7C7.44772 7 7 7.44772 7 8V11C7 11.5523 7.44772 12 8 12C8.55228 12 9 11.5523 9 11V8C9 7.44772 8.55228 7 8 7Z" fill="#657077"/>
</g>
<defs>
<clipPath id="clip0_21120_56765">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
<clipPath id="clip0_460_8224">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 710 B

After

Width:  |  Height:  |  Size: 739 B

View File

@ -164,19 +164,17 @@ public class UserPhotoManagerCache
public string SearchInCache(Guid userId, Size size)
{
string fileName = null;
if (!_photofiles.TryGetValue(userId, out var val))
{
return null;
}
if (!val.TryGetValue(UserPhotoManager.ToCache(size), out fileName))
if (!val.TryGetValue(UserPhotoManager.ToCache(size), out var fileName))
{
return null;
}
if (String.IsNullOrEmpty(fileName))
if (string.IsNullOrEmpty(fileName))
{
fileName = val.Values.FirstOrDefault(x => !string.IsNullOrEmpty(x) && x.Contains("_orig_"));
}

297
yarn.lock
View File

@ -108,13 +108,13 @@ __metadata:
linkType: hard
"@aws-sdk/client-cloudwatch-logs@npm:^3.297.0":
version: 3.418.0
resolution: "@aws-sdk/client-cloudwatch-logs@npm:3.418.0"
version: 3.421.0
resolution: "@aws-sdk/client-cloudwatch-logs@npm:3.421.0"
dependencies:
"@aws-crypto/sha256-browser": 3.0.0
"@aws-crypto/sha256-js": 3.0.0
"@aws-sdk/client-sts": 3.418.0
"@aws-sdk/credential-provider-node": 3.418.0
"@aws-sdk/client-sts": 3.421.0
"@aws-sdk/credential-provider-node": 3.421.0
"@aws-sdk/middleware-host-header": 3.418.0
"@aws-sdk/middleware-logger": 3.418.0
"@aws-sdk/middleware-recursion-detection": 3.418.0
@ -149,13 +149,13 @@ __metadata:
"@smithy/util-utf8": ^2.0.0
tslib: ^2.5.0
uuid: ^8.3.2
checksum: cf250588753d42dbbd4605425aeb0a7a1838a0b46f0054f0f2e18c7075abfde3509d7ca9f25236a4188bff6e640da44d47d4236b8d479e209e5633c04ab55665
checksum: 8335143173c8c26e4c36af9ef323c42afe002acdeeade86b4ca2b0877e4382b0e18abdd66e9a8dcb2a5e308e450b0eb4002ef9d8f056cc612466337ae955b500
languageName: node
linkType: hard
"@aws-sdk/client-sso@npm:3.418.0":
version: 3.418.0
resolution: "@aws-sdk/client-sso@npm:3.418.0"
"@aws-sdk/client-sso@npm:3.421.0":
version: 3.421.0
resolution: "@aws-sdk/client-sso@npm:3.421.0"
dependencies:
"@aws-crypto/sha256-browser": 3.0.0
"@aws-crypto/sha256-js": 3.0.0
@ -191,17 +191,17 @@ __metadata:
"@smithy/util-retry": ^2.0.2
"@smithy/util-utf8": ^2.0.0
tslib: ^2.5.0
checksum: 54c17953ac1143f07fff8a462bddeb87a5cb10d5ffb269ed93e837da7b37d4d3ae81aa0986b23c0e687712c58320bb28b5ac0ab4a8cb2841fbb0ce2436df648d
checksum: 2dcfbd104668f49fc5b18af6a5bced3845e75c1facab7fd26e97c40ea447e9c29d4f3a5e14d90e66a008db251a6aa27fc8af7652e3c1d8916853ff37df28c72c
languageName: node
linkType: hard
"@aws-sdk/client-sts@npm:3.418.0":
version: 3.418.0
resolution: "@aws-sdk/client-sts@npm:3.418.0"
"@aws-sdk/client-sts@npm:3.421.0":
version: 3.421.0
resolution: "@aws-sdk/client-sts@npm:3.421.0"
dependencies:
"@aws-crypto/sha256-browser": 3.0.0
"@aws-crypto/sha256-js": 3.0.0
"@aws-sdk/credential-provider-node": 3.418.0
"@aws-sdk/credential-provider-node": 3.421.0
"@aws-sdk/middleware-host-header": 3.418.0
"@aws-sdk/middleware-logger": 3.418.0
"@aws-sdk/middleware-recursion-detection": 3.418.0
@ -237,7 +237,7 @@ __metadata:
"@smithy/util-utf8": ^2.0.0
fast-xml-parser: 4.2.5
tslib: ^2.5.0
checksum: 594ce1a04a1c5aa6b21e7ddf0342d9705e27a60bfba3b30b192152987aba6bd6227d8e98e6ccddae1ccf7090de9e12737f8cd996918302ca574b69fe6f93ad37
checksum: c21a28288341a7f6749e61391846b7a6754de3594137a7c436b1d96d82a8043adc921b45f8d22e2894e50cdf3799f51143a42f214ae552bcd0e0088c85d2de09
languageName: node
linkType: hard
@ -253,13 +253,13 @@ __metadata:
languageName: node
linkType: hard
"@aws-sdk/credential-provider-ini@npm:3.418.0":
version: 3.418.0
resolution: "@aws-sdk/credential-provider-ini@npm:3.418.0"
"@aws-sdk/credential-provider-ini@npm:3.421.0":
version: 3.421.0
resolution: "@aws-sdk/credential-provider-ini@npm:3.421.0"
dependencies:
"@aws-sdk/credential-provider-env": 3.418.0
"@aws-sdk/credential-provider-process": 3.418.0
"@aws-sdk/credential-provider-sso": 3.418.0
"@aws-sdk/credential-provider-sso": 3.421.0
"@aws-sdk/credential-provider-web-identity": 3.418.0
"@aws-sdk/types": 3.418.0
"@smithy/credential-provider-imds": ^2.0.0
@ -267,18 +267,18 @@ __metadata:
"@smithy/shared-ini-file-loader": ^2.0.6
"@smithy/types": ^2.3.3
tslib: ^2.5.0
checksum: c519d15523d872c3d3249df48e513e7c648f1668bb9baec6af895613fcdf14f552d917337585bed744260796b5f0bd033215102c1bbceb588d46738b7d5093d1
checksum: 038e115250a966a28b43faa6a70ef4e9d543d51e0eed8446d0939c44a7a0c3333a3dcb0a80aa5c2b328cc225228aef8f8aa5b31ba1725316b3019b88ad091ad8
languageName: node
linkType: hard
"@aws-sdk/credential-provider-node@npm:3.418.0":
version: 3.418.0
resolution: "@aws-sdk/credential-provider-node@npm:3.418.0"
"@aws-sdk/credential-provider-node@npm:3.421.0":
version: 3.421.0
resolution: "@aws-sdk/credential-provider-node@npm:3.421.0"
dependencies:
"@aws-sdk/credential-provider-env": 3.418.0
"@aws-sdk/credential-provider-ini": 3.418.0
"@aws-sdk/credential-provider-ini": 3.421.0
"@aws-sdk/credential-provider-process": 3.418.0
"@aws-sdk/credential-provider-sso": 3.418.0
"@aws-sdk/credential-provider-sso": 3.421.0
"@aws-sdk/credential-provider-web-identity": 3.418.0
"@aws-sdk/types": 3.418.0
"@smithy/credential-provider-imds": ^2.0.0
@ -286,7 +286,7 @@ __metadata:
"@smithy/shared-ini-file-loader": ^2.0.6
"@smithy/types": ^2.3.3
tslib: ^2.5.0
checksum: bbaf9d9f4c7b7ab0c9b5772e3abcb6642021d8d10081f31ad4de34d1ba0522d9dc817aced7f28e2b8fff443177cfe90c295c0263f96be61d8c4e3849b44cb7c7
checksum: 0b0249ebc58383e7149f8b5398ca8d6af93df397fae4c0feaea9debd4617a90d7c9a01486709173799edaf06c6c34e9c979969da1a41bdb681d01c6661e6e0e3
languageName: node
linkType: hard
@ -303,18 +303,18 @@ __metadata:
languageName: node
linkType: hard
"@aws-sdk/credential-provider-sso@npm:3.418.0":
version: 3.418.0
resolution: "@aws-sdk/credential-provider-sso@npm:3.418.0"
"@aws-sdk/credential-provider-sso@npm:3.421.0":
version: 3.421.0
resolution: "@aws-sdk/credential-provider-sso@npm:3.421.0"
dependencies:
"@aws-sdk/client-sso": 3.418.0
"@aws-sdk/client-sso": 3.421.0
"@aws-sdk/token-providers": 3.418.0
"@aws-sdk/types": 3.418.0
"@smithy/property-provider": ^2.0.0
"@smithy/shared-ini-file-loader": ^2.0.6
"@smithy/types": ^2.3.3
tslib: ^2.5.0
checksum: e666dd10f50b9510b09f64bdac66f97ac18e1521bdb6fd642c1446740fbeaf5d7a609dfb5c7166561cb5cecd5149b0ae08d9b9df919641168c022657f297414d
checksum: 7fd59a74fe28602ac0877c9e845a95a95a3be91869be33776576c2cbe41cce8b5921480e769a1fdf02db41f67d792e9f1ea93d9e5f0256736cea8f24d8839b24
languageName: node
linkType: hard
@ -3774,9 +3774,9 @@ __metadata:
linkType: hard
"@eslint-community/regexpp@npm:^4.6.1":
version: 4.8.1
resolution: "@eslint-community/regexpp@npm:4.8.1"
checksum: 82d62c845ef42b810f268cfdc84d803a2da01735fb52e902fd34bdc09f92464a094fd8e4802839874b000b2f73f67c972859e813ba705233515d3e954f234bf2
version: 4.9.0
resolution: "@eslint-community/regexpp@npm:4.9.0"
checksum: 82411f0643ab9bfd271bf12c8c75031266b13595d9371585ee3b0d680d918d4abf37c7e94d0da22e45817c9bbc59b79dfcbd672050dfb00af88fb89c80fd420f
languageName: node
linkType: hard
@ -4254,12 +4254,12 @@ __metadata:
linkType: hard
"@grpc/grpc-js@npm:^1.3.2":
version: 1.9.3
resolution: "@grpc/grpc-js@npm:1.9.3"
version: 1.9.4
resolution: "@grpc/grpc-js@npm:1.9.4"
dependencies:
"@grpc/proto-loader": ^0.7.8
"@types/node": ">=12.12.47"
checksum: 09634de9f871a17c95db95338fe472522d5dca0f77b622e3a497ef806262813445ba1a1f3261f03461d84ac985e420317d5638b3314fcb524ee2e47e512ffa64
checksum: 83616d3727f16d9caf57fb16f8525e6884856569624a244ef8dde2873b7d7a439d302fd0a82d28bbbf9ba9777d3b25a58c706e35daa30120abb04f1646aa0e11
languageName: node
linkType: hard
@ -8386,11 +8386,11 @@ __metadata:
linkType: hard
"@types/istanbul-lib-report@npm:*":
version: 3.0.0
resolution: "@types/istanbul-lib-report@npm:3.0.0"
version: 3.0.1
resolution: "@types/istanbul-lib-report@npm:3.0.1"
dependencies:
"@types/istanbul-lib-coverage": "*"
checksum: 656398b62dc288e1b5226f8880af98087233cdb90100655c989a09f3052b5775bf98ba58a16c5ae642fb66c61aba402e07a9f2bff1d1569e3b306026c59f3f36
checksum: cfc66de48577bb7b2636a6afded7056483693c3ea70916276518cdfaa0d4b51bf564ded88fb13e75716665c3af3d4d54e9c2de042c0219dcabad7e81c398688b
languageName: node
linkType: hard
@ -8405,11 +8405,11 @@ __metadata:
linkType: hard
"@types/istanbul-reports@npm:^3.0.0":
version: 3.0.1
resolution: "@types/istanbul-reports@npm:3.0.1"
version: 3.0.2
resolution: "@types/istanbul-reports@npm:3.0.2"
dependencies:
"@types/istanbul-lib-report": "*"
checksum: f1ad54bc68f37f60b30c7915886b92f86b847033e597f9b34f2415acdbe5ed742fa559a0a40050d74cdba3b6a63c342cac1f3a64dba5b68b66a6941f4abd7903
checksum: f52028d6fe4d28f0085dd7ed66ccfa6af632579e9a4091b90928ffef93d4dbec0bacd49e9caf1b939d05df9eafc5ac1f5939413cdf8ac59fbe4b29602d4d0939
languageName: node
linkType: hard
@ -8460,46 +8460,46 @@ __metadata:
linkType: hard
"@types/mdast@npm:^3.0.0":
version: 3.0.12
resolution: "@types/mdast@npm:3.0.12"
version: 3.0.13
resolution: "@types/mdast@npm:3.0.13"
dependencies:
"@types/unist": ^2
checksum: 83adb8679b9d139f69f63554d120af921e9f1289e9903a2c99e0554a327c8524a6c0beccdc0721e4fdbccc606e81964fecb0d390d53df0f74360938e22f1a469
checksum: f13fa17a2931ed1492a2f0012a3abd6de3a2d1128145981321909e03fedba80162668f284a4af92aca3732b27e933c5f4772336d96b9ae660bfde696d07abbe6
languageName: node
linkType: hard
"@types/mdurl@npm:^1.0.0":
version: 1.0.2
resolution: "@types/mdurl@npm:1.0.2"
checksum: 79c7e523b377f53cf1f5a240fe23d0c6cae856667692bd21bf1d064eafe5ccc40ae39a2aa0a9a51e8c94d1307228c8f6b121e847124591a9a828c3baf65e86e2
version: 1.0.3
resolution: "@types/mdurl@npm:1.0.3"
checksum: 5bbed4f0eb9f60040fa26be77aa2158ca468b6423876cec0d2043e7f8298e83b8e5b95fb66056327b02d747c4d376aed16c11ff3fdc4cb3dca327a6931a71f18
languageName: node
linkType: hard
"@types/mdx@npm:^2.0.0":
version: 2.0.7
resolution: "@types/mdx@npm:2.0.7"
checksum: c746659ebea471535d99a49cc935dc9f831fac22def3fa8c8a0883ad2cae71c4cca1d8563fa60d0e2730b14cb13e95c32af8dfda455a4937476a7d9e2748d641
version: 2.0.8
resolution: "@types/mdx@npm:2.0.8"
checksum: 4a7c2241c37e87aaab044c561f24874fabcd5cd2d6feb28dc665e2c80562afa7ddf94391a3b8ab3f76041199c8bafcff9131acf6d060f1aca45d763b171bbc29
languageName: node
linkType: hard
"@types/mime-types@npm:^2.1.0":
version: 2.1.1
resolution: "@types/mime-types@npm:2.1.1"
checksum: 106b5d556add46446a579ad25ff15d6b421851790d887edcad558c90c1e64b1defc72bfbaf4b08f208916e21d9cc45cdb951d77be51268b18221544cfe054a3c
version: 2.1.2
resolution: "@types/mime-types@npm:2.1.2"
checksum: 9e3c78f1c63211e0450901212566a046da68d4438a5e543333ec9b0be3259bd5d01532734dc51ead40104889b98d12c7663b65212a318aafad3e34c98204e9e1
languageName: node
linkType: hard
"@types/mime@npm:*":
version: 3.0.1
resolution: "@types/mime@npm:3.0.1"
checksum: 4040fac73fd0cea2460e29b348c1a6173da747f3a87da0dbce80dd7a9355a3d0e51d6d9a401654f3e5550620e3718b5a899b2ec1debf18424e298a2c605346e7
version: 3.0.2
resolution: "@types/mime@npm:3.0.2"
checksum: 09cf74f6377d1b27f4a24512cb689ad30af59880ac473ed6f7bc5285ecde88bbe8fe500789340ad57810da9d6fe1704f86e8bfe147b9ea76d58925204a60b906
languageName: node
linkType: hard
"@types/mime@npm:^1":
version: 1.3.2
resolution: "@types/mime@npm:1.3.2"
checksum: 0493368244cced1a69cb791b485a260a422e6fcc857782e1178d1e6f219f1b161793e9f87f5fae1b219af0f50bee24fcbe733a18b4be8fdd07a38a8fb91146fd
version: 1.3.3
resolution: "@types/mime@npm:1.3.3"
checksum: 7e27dede6517c1d604821a8a5412d6b7131decc8397ad4bac9216fc90dea26c9571426623ebeea2a9b89dbfb89ad98f7370a3c62cd2be8896c6e897333b117c9
languageName: node
linkType: hard
@ -8511,18 +8511,18 @@ __metadata:
linkType: hard
"@types/morgan@npm:^1.9.4":
version: 1.9.5
resolution: "@types/morgan@npm:1.9.5"
version: 1.9.6
resolution: "@types/morgan@npm:1.9.6"
dependencies:
"@types/node": "*"
checksum: f98deb4c7f2ad6049ad34ed7b0f0d427546bdf2358011070af9d597de1b0a03b38cc10cfe65ef2e7673e569c384303d949e76df701acefe288d547f614142973
checksum: 6525248325a74342f929c958be69c0840c8f3a288e003a8904319cae92e531f17a8aa2700701e66775adcca7f9506dd630fec2f95dc04a3e73add04fde42aab8
languageName: node
linkType: hard
"@types/ms@npm:*":
version: 0.7.31
resolution: "@types/ms@npm:0.7.31"
checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da
version: 0.7.32
resolution: "@types/ms@npm:0.7.32"
checksum: 610744605c5924aa2657c8a62d307052af4f0e38e2aa015f154ef03391fabb4fd903f9c9baacb41f6e5798b8697e898463c351e5faf638738603ed29137b5254
languageName: node
linkType: hard
@ -8537,9 +8537,9 @@ __metadata:
linkType: hard
"@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0":
version: 20.6.5
resolution: "@types/node@npm:20.6.5"
checksum: b849e849cf7631458a65c5019c81962028e306d8c4455a48422277b240f5a7eb8a1f1dafa60306bd4c773b77263bb8b05c074b1026e868bd137bb2022cf63ea2
version: 20.7.1
resolution: "@types/node@npm:20.7.1"
checksum: 3140bd6c9130f1ed73a78ce7a1765ee43e155c1eea50eea45e18faeb31d11d97a84fffdc5e3a97582101d2f57d2652a50f510ede6c702780267bad74c822d56c
languageName: node
linkType: hard
@ -8551,9 +8551,9 @@ __metadata:
linkType: hard
"@types/node@npm:^18.15.5":
version: 18.17.19
resolution: "@types/node@npm:18.17.19"
checksum: 6ab47127cd7534511aa199550659d56b44e5a6dbec9df054d0cde279926b4d43f0e6438f92c8392b039ab4e2a85aa0f698b95926430aff860e23bfc36c96576c
version: 18.18.0
resolution: "@types/node@npm:18.18.0"
checksum: 61bcffa28eb713e7a4c66fd369df603369c3f834a783faeced95fe3e78903faa25f1a704d49e054f41d71b7915eeb066d10a37cc699421fcf5dd267f96ad5808
languageName: node
linkType: hard
@ -8616,27 +8616,27 @@ __metadata:
linkType: hard
"@types/range-parser@npm:*":
version: 1.2.4
resolution: "@types/range-parser@npm:1.2.4"
checksum: b7c0dfd5080a989d6c8bb0b6750fc0933d9acabeb476da6fe71d8bdf1ab65e37c136169d84148034802f48378ab94e3c37bb4ef7656b2bec2cb9c0f8d4146a95
version: 1.2.5
resolution: "@types/range-parser@npm:1.2.5"
checksum: db9aaa04a02d019395a9a4346475669a2864a32a6477ad0fc457bd2ef39a167cabe742f55a8a3fa8bc90abac795b716c22b37348bc3e19313ebe6c9310815233
languageName: node
linkType: hard
"@types/reach__router@npm:^1.2.3":
version: 1.3.11
resolution: "@types/reach__router@npm:1.3.11"
version: 1.3.12
resolution: "@types/reach__router@npm:1.3.12"
dependencies:
"@types/react": "*"
checksum: 6bcf40714e0dafff66cbf10a534320eae35dae8344f616d2ddf077937287a0df188dfbfb32fb8045cbc641139d1ab69ee5bb258a51642823cadefbcda020a044
checksum: df838ba6bc3463c50ae35e704e11a9073bb0617c04676887a9ad87177b078d6593aa3cd8b63de07e791ac8b86183dbb0f20072c22dd40754487a47f0064c3fb7
languageName: node
linkType: hard
"@types/react-dom@npm:*, @types/react-dom@npm:^18.0.11":
version: 18.2.7
resolution: "@types/react-dom@npm:18.2.7"
version: 18.2.8
resolution: "@types/react-dom@npm:18.2.8"
dependencies:
"@types/react": "*"
checksum: e02ea908289a7ad26053308248d2b87f6aeafd73d0e2de2a3d435947bcea0422599016ffd1c3e38ff36c42f5e1c87c7417f05b0a157e48649e4a02f21727d54f
checksum: d36264631028d021b73cd9e015f10b95c4959ae1ce8f7a7419f318d1f05b1d063e6afffcd2a349a6bccd64ccc9ee9d2d976e1f0437643f0e7db621fa035bca65
languageName: node
linkType: hard
@ -8659,13 +8659,13 @@ __metadata:
linkType: hard
"@types/react@npm:*, @types/react@npm:>=16, @types/react@npm:^18.0.28":
version: 18.2.22
resolution: "@types/react@npm:18.2.22"
version: 18.2.23
resolution: "@types/react@npm:18.2.23"
dependencies:
"@types/prop-types": "*"
"@types/scheduler": "*"
csstype: ^3.0.2
checksum: 44289523dabaadcd3fd85689abb98f9ebcc8492d7e978348d1c986138acef4801030b279e89a19e38a6319e294bcea77559e37e0c803e4bacf2b8ae3a56ba587
checksum: efb9d1ed1940c0e7ba08a21ffba5e266d8dbbb8fe618cfb97bc902dfc96385fdd8189e3f7f64b4aa13134f8e61947d60560deb23be151253c3a97b0d070897ca
languageName: node
linkType: hard
@ -8677,55 +8677,55 @@ __metadata:
linkType: hard
"@types/scheduler@npm:*":
version: 0.16.3
resolution: "@types/scheduler@npm:0.16.3"
checksum: 2b0aec39c24268e3ce938c5db2f2e77f5c3dd280e05c262d9c2fe7d890929e4632a6b8e94334017b66b45e4f92a5aa42ba3356640c2a1175fa37bef2f5200767
version: 0.16.4
resolution: "@types/scheduler@npm:0.16.4"
checksum: a57b0f10da1b021e6bd5eeef8a1917dd3b08a8715bd8029e2ded2096d8f091bb1bb1fef2d66e139588a983c4bfbad29b59e48011141725fa83c76e986e1257d7
languageName: node
linkType: hard
"@types/semver@npm:^7.3.12, @types/semver@npm:^7.3.4":
version: 7.5.2
resolution: "@types/semver@npm:7.5.2"
checksum: 743aa8a2b58e20b329c19bd2459152cb049d12fafab7279b90ac11e0f268c97efbcb606ea0c681cca03f79015381b40d9b1244349b354270bec3f939ed49f6e9
version: 7.5.3
resolution: "@types/semver@npm:7.5.3"
checksum: 349fdd1ab6c213bac5c991bac766bd07b8b12e63762462bb058740dcd2eb09c8193d068bb226f134661275f2022976214c0e727a4e5eb83ec1b131127c980d3e
languageName: node
linkType: hard
"@types/send@npm:*":
version: 0.17.1
resolution: "@types/send@npm:0.17.1"
version: 0.17.2
resolution: "@types/send@npm:0.17.2"
dependencies:
"@types/mime": ^1
"@types/node": "*"
checksum: 10b620a5960058ef009afbc17686f680d6486277c62f640845381ec4baa0ea683fdd77c3afea4803daf5fcddd3fb2972c8aa32e078939f1d4e96f83195c89793
checksum: 1ff5b1bd6a4f6fdc6402c7024781ff5dbd0e1f51a43c69529fb67c710943c7416d2f0d77c57c70fccf6616f25f838f32f960284526e408d4edae2e91e1fce95a
languageName: node
linkType: hard
"@types/serve-index@npm:^1.9.1":
version: 1.9.1
resolution: "@types/serve-index@npm:1.9.1"
version: 1.9.2
resolution: "@types/serve-index@npm:1.9.2"
dependencies:
"@types/express": "*"
checksum: 026f3995fb500f6df7c3fe5009e53bad6d739e20b84089f58ebfafb2f404bbbb6162bbe33f72d2f2af32d5b8d3799c8e179793f90d9ed5871fb8591190bb6056
checksum: 4fd0a8fcdd6e2b2d7704a539b7c1e0d143e9e00be4c3992394fe2ef7e9b67283d74b43db3f92b0e0717d779aa51184168bbae617d30456357cb95ec58aa59ea8
languageName: node
linkType: hard
"@types/serve-static@npm:*, @types/serve-static@npm:^1.13.10":
version: 1.15.2
resolution: "@types/serve-static@npm:1.15.2"
version: 1.15.3
resolution: "@types/serve-static@npm:1.15.3"
dependencies:
"@types/http-errors": "*"
"@types/mime": "*"
"@types/node": "*"
checksum: 15c261dbfc57890f7cc17c04d5b22b418dfa0330c912b46c5d8ae2064da5d6f844ef7f41b63c7f4bbf07675e97ebe6ac804b032635ec742ae45d6f1274259b3e
checksum: afa52252f0ba94cdb5391e80f23e17fd629bdf2a31be8876e2c4490312ed6b0570822dd7de7cea04c9002049e207709563568b7f4ee10bb9f456321db1e83e40
languageName: node
linkType: hard
"@types/sockjs@npm:^0.3.33":
version: 0.3.33
resolution: "@types/sockjs@npm:0.3.33"
version: 0.3.34
resolution: "@types/sockjs@npm:0.3.34"
dependencies:
"@types/node": "*"
checksum: b9bbb2b5c5ead2fb884bb019f61a014e37410bddd295de28184e1b2e71ee6b04120c5ba7b9954617f0bdf962c13d06249ce65004490889c747c80d3f628ea842
checksum: 1d38b1976a97f5895a6be00cead1b2a59d842f023b6e35450b7eec8a3131c860c447aba3f7ea3c880c066d8277b0c76fae9d080be1aad475811b568ed6079d49
languageName: node
linkType: hard
@ -8737,13 +8737,13 @@ __metadata:
linkType: hard
"@types/styled-components@npm:^5.1.26":
version: 5.1.27
resolution: "@types/styled-components@npm:5.1.27"
version: 5.1.28
resolution: "@types/styled-components@npm:5.1.28"
dependencies:
"@types/hoist-non-react-statics": "*"
"@types/react": "*"
csstype: ^3.0.2
checksum: 6f8d99642f09f28a233a5bf93d3139389c3bf5e4c643620881cb286f581d6844f3c1c43be5bd9405d3b199b6a858bd8b0ec6ccbb17b2304a88d70e2a6f91a996
checksum: 2e12be4fdc2d60f63e16f8f5cac98d8305e0686b3332a160fec39df2a277f16715b878e25384ddda29e48acf101110a29ca108b716296e9a06bf2740f1257385
languageName: node
linkType: hard
@ -8798,9 +8798,9 @@ __metadata:
linkType: hard
"@types/webpack-env@npm:^1.16.0":
version: 1.18.1
resolution: "@types/webpack-env@npm:1.18.1"
checksum: 3173c069763e51a96565d602af7e6dac9d772ae4aa6f26cac187cbf599a7f0b88f790b4b050b9dbdb0485daed3061b4a337863f3b8ce66f8a4e51f75ad387c6a
version: 1.18.2
resolution: "@types/webpack-env@npm:1.18.2"
checksum: 883908ade827d35a10efc574fb6f2728a7c520d4296cf1507633ac7457204ccd697bc6c8cadac99bc5d96074a6109c658ebfde59f42ba5ba0fdfffc538892b0f
languageName: node
linkType: hard
@ -8814,11 +8814,11 @@ __metadata:
linkType: hard
"@types/ws@npm:*, @types/ws@npm:^8.5.1, @types/ws@npm:^8.5.4":
version: 8.5.5
resolution: "@types/ws@npm:8.5.5"
version: 8.5.6
resolution: "@types/ws@npm:8.5.6"
dependencies:
"@types/node": "*"
checksum: d00bf8070e6938e3ccf933010921c6ce78ac3606696ce37a393b27a9a603f7bd93ea64f3c5fa295a2f743575ba9c9a9fdb904af0f5fe2229bf2adf0630386e4a
checksum: 7addb0c5fa4e7713d5209afb8a90f1852b12c02cb537395adf7a05fbaf21205dc5f7c110fd5ad6f3dbf147112cbff33fb11d8633059cb344f0c14f595b1ea1fb
languageName: node
linkType: hard
@ -10813,16 +10813,16 @@ __metadata:
linkType: hard
"browserslist@npm:^4.14.5, browserslist@npm:^4.21.10, browserslist@npm:^4.21.9, browserslist@npm:^4.9.1":
version: 4.21.11
resolution: "browserslist@npm:4.21.11"
version: 4.22.0
resolution: "browserslist@npm:4.22.0"
dependencies:
caniuse-lite: ^1.0.30001538
electron-to-chromium: ^1.4.526
caniuse-lite: ^1.0.30001539
electron-to-chromium: ^1.4.530
node-releases: ^2.0.13
update-browserslist-db: ^1.0.13
bin:
browserslist: cli.js
checksum: 89377745428d32c7bbec37055bc4b9e48aa859418ea3886b13218d825f8ea3053640f90d8652844ae855941fec0bffd2d69cf933035d6f9224b427d48d31eddf
checksum: 14fc119bbfb85b65e2ee4a82205fabf9327520d010c4c586f1176ceaf9136cfdb391397045a4eafaa9defe52b6dbdf875916714695826c69091a936d5838f9ec
languageName: node
linkType: hard
@ -11040,10 +11040,10 @@ __metadata:
languageName: node
linkType: hard
"caniuse-lite@npm:^1.0.30001538":
version: 1.0.30001539
resolution: "caniuse-lite@npm:1.0.30001539"
checksum: 8595905d6c7234173a4915439fdd69fa2c06b0272d56af63aee0df6114e1ee4727758af160c0db9844055f778e0ea27ed4714facf2e472a2e74d9f567f111f41
"caniuse-lite@npm:^1.0.30001539":
version: 1.0.30001541
resolution: "caniuse-lite@npm:1.0.30001541"
checksum: 972f6c223cf4ea2c6821b817b419249285006bbf67ebe415fe58097cf07551e3bae898586736d92f7c40b9f0ac28638dbf760631c23742b780affd0254f44d17
languageName: node
linkType: hard
@ -13061,10 +13061,10 @@ __metadata:
languageName: node
linkType: hard
"electron-to-chromium@npm:^1.4.526":
version: 1.4.528
resolution: "electron-to-chromium@npm:1.4.528"
checksum: e9b249929e012ce6c25f8d1e1965e7fb19a426cc72fdf72026a0ce010846ec1b0efe2730b1a1f3d1c98bd11200b94b73431bc5279211a9f4a28f2389e690c39c
"electron-to-chromium@npm:^1.4.530":
version: 1.4.532
resolution: "electron-to-chromium@npm:1.4.532"
checksum: e9f77b5d6df84aa1f7598359ec2c988c3758e58106e63f2a0a6dc4756a6733b126316e61a79a2a6643aa2a0f9a1cf9ebe66c817dcb970a3fc9d8190342ef070a
languageName: node
linkType: hard
@ -14793,9 +14793,9 @@ __metadata:
linkType: hard
"fs-monkey@npm:^1.0.4":
version: 1.0.4
resolution: "fs-monkey@npm:1.0.4"
checksum: 8b254c982905c0b7e028eab22b410dc35a5c0019c1c860456f5f54ae6a61666e1cb8c6b700d6c88cc873694c00953c935847b9959cc4dcf274aacb8673c1e8bf
version: 1.0.5
resolution: "fs-monkey@npm:1.0.5"
checksum: 424b67f65b37fe66117ae2bb061f790fe6d4b609e1d160487c74b3d69fbf42f262c665ccfba32e8b5f113f96f92e9a58fcdebe42d5f6649bdfc72918093a3119
languageName: node
linkType: hard
@ -15109,17 +15109,17 @@ __metadata:
linkType: hard
"glob@npm:^10.2.2":
version: 10.3.7
resolution: "glob@npm:10.3.7"
version: 10.3.10
resolution: "glob@npm:10.3.10"
dependencies:
foreground-child: ^3.1.0
jackspeak: ^2.0.3
jackspeak: ^2.3.5
minimatch: ^9.0.1
minipass: ^5.0.0 || ^6.0.2 || ^7.0.0
path-scurry: ^1.10.1
bin:
glob: dist/esm/bin.mjs
checksum: 9a27f1fa8774c3a8ab8f05c26a77276edaf5418aac29aff70c5d847ef75dabf536554cb113e945193323fb769fbe32edde12559d2d52266f38662595cbc7a031
checksum: 4f2fe2511e157b5a3f525a54092169a5f92405f24d2aed3142f4411df328baca13059f4182f1db1bf933e2c69c0bd89e57ae87edd8950cba8c7ccbe84f721cf3
languageName: node
linkType: hard
@ -16941,16 +16941,16 @@ __metadata:
languageName: node
linkType: hard
"jackspeak@npm:^2.0.3":
version: 2.3.3
resolution: "jackspeak@npm:2.3.3"
"jackspeak@npm:^2.3.5":
version: 2.3.6
resolution: "jackspeak@npm:2.3.6"
dependencies:
"@isaacs/cliui": ^8.0.2
"@pkgjs/parseargs": ^0.11.0
dependenciesMeta:
"@pkgjs/parseargs":
optional: true
checksum: 4313a7c0cc44c7753c4cb9869935f0b06f4cf96827515f63f58ff46b3d2f6e29aba6b3b5151778397c3f5ae67ef8bfc48871967bd10343c27e90cff198ec7808
checksum: 57d43ad11eadc98cdfe7496612f6bbb5255ea69fe51ea431162db302c2a11011642f50cfad57288bd0aea78384a0612b16e131944ad8ecd09d619041c8531b54
languageName: node
linkType: hard
@ -21563,8 +21563,8 @@ __metadata:
linkType: hard
"rc-virtual-list@npm:^3.5.1":
version: 3.11.1
resolution: "rc-virtual-list@npm:3.11.1"
version: 3.11.2
resolution: "rc-virtual-list@npm:3.11.2"
dependencies:
"@babel/runtime": ^7.20.0
classnames: ^2.2.6
@ -21573,7 +21573,7 @@ __metadata:
peerDependencies:
react: "*"
react-dom: "*"
checksum: bd6b41b2ca452313534d4d5d1dc0efcb5baf1e26b815acf3553e69f96bb7eeb0980319284adf389f4bcc3f934265d08e8d7e193fb4677a3643bbd896080bced6
checksum: b642e55ca2c4b56fb2048b5151a17e2f0f1c15cf3f4d8a26a4f1f370c95a6f4476743886b6633f083f107c82fbe86c5548bbf37b7e2f8b79615d90b62777824e
languageName: node
linkType: hard
@ -21794,15 +21794,15 @@ __metadata:
linkType: hard
"react-draggable@npm:^4.4.5":
version: 4.4.5
resolution: "react-draggable@npm:4.4.5"
version: 4.4.6
resolution: "react-draggable@npm:4.4.6"
dependencies:
clsx: ^1.1.1
prop-types: ^15.8.1
peerDependencies:
react: ">= 16.3.0"
react-dom: ">= 16.3.0"
checksum: 21c3775db086e13020967627c20acd41d1ddbc7c7d7fca51491a5bbb54a0aa7e1730a4bc9af17141eb50a4954e547a5e25b2368f5f54b70db6f2686a897bacf2
checksum: 9b15aac59244873ac4561c5a2bead43a56e18d406e0a5f242bd4f9d151c074530c02b99387983104bf43417292f9cf8d063e554ed08d88792235e3fbc965f1b8
languageName: node
linkType: hard
@ -22283,15 +22283,15 @@ __metadata:
linkType: hard
"react-tooltip@npm:^5.21.1":
version: 5.21.4
resolution: "react-tooltip@npm:5.21.4"
version: 5.21.5
resolution: "react-tooltip@npm:5.21.5"
dependencies:
"@floating-ui/dom": ^1.0.0
classnames: ^2.3.0
peerDependencies:
react: ">=16.14.0"
react-dom: ">=16.14.0"
checksum: 9b663c5d9a1472b96d3ed708ecce01052aed267dc739520c9b55389a5e42c195a6bcd0717219f260bf0dadee2452d80a7c5fa14c5c3f8b5cdc6a9174d7f42d4a
checksum: 067d6a5bc7c6b12b2ae054f855416b068bcc9fe59864b83c655e775066d6f81728b2ec13e80565968da7ce35c7c0286f8e154c635a1e79bb7a8233df8a356c51
languageName: node
linkType: hard
@ -23300,7 +23300,14 @@ __metadata:
languageName: node
linkType: hard
"sax@npm:^1.2.4, sax@npm:~1.2.1, sax@npm:~1.2.4":
"sax@npm:^1.2.4":
version: 1.3.0
resolution: "sax@npm:1.3.0"
checksum: 238ab3a9ba8c8f8aaf1c5ea9120386391f6ee0af52f1a6a40bbb6df78241dd05d782f2359d614ac6aae08c4c4125208b456548a6cf68625aa4fe178486e63ecd
languageName: node
linkType: hard
"sax@npm:~1.2.1, sax@npm:~1.2.4":
version: 1.2.4
resolution: "sax@npm:1.2.4"
checksum: d3df7d32b897a2c2f28e941f732c71ba90e27c24f62ee918bd4d9a8cfb3553f2f81e5493c7f0be94a11c1911b643a9108f231dd6f60df3fa9586b5d2e3e9e1fe