Web: Client: Profile. rewrited table view active sessions

This commit is contained in:
Elyor Djalilov 2024-04-15 22:13:59 +05:00
parent 579ab4130d
commit 89e29076e5
14 changed files with 606 additions and 446 deletions

View File

@ -1,5 +1,6 @@
{
"ActiveSessions": "Active Sessions",
"AutoDeleteTitle": "All the sessions older than 60 days will be automatically deleted.",
"ChangeEmailSuccess": "Email has been changed successfully",
"ChangePasswordAfterLoggingOut": "Change password after logging out",
"ConnectSocialNetworks": "Connect your social networks",
@ -28,5 +29,6 @@
"SuccessLogout": "The active connection was logged out: {{platform}}, {{browser}}",
"SystemTheme": "Use system theme",
"SystemThemeDescription": "Automatically switch between light and dark themes when your system does.",
"TerminateAllSessions": "Terminate all sessions except the current one",
"TwoFactorDescription": "Two-factor authentication via a code-generating app was enabled for all users by the admin."
}

View File

@ -24,8 +24,7 @@
// 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
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { ModalDialog } from "@docspace/shared/components/modal-dialog";
import { Checkbox } from "@docspace/shared/components/checkbox";
import { Button } from "@docspace/shared/components/button";
@ -34,20 +33,24 @@ import { Text } from "@docspace/shared/components/text";
import ModalDialogContainer from "../ModalDialogContainer";
const LogoutAllConnectionDialog = ({
const LogoutAllSessionDialog = ({
t,
visible,
onClose,
isLoading,
onRemoveAllSessions,
loading,
onRemoveAllExceptThis,
}) => {
const { t } = useTranslation(["Profile", "Common"]);
const [isChecked, setIsChecked] = useState(false);
const onChangeCheckbox = () => {
setIsChecked((prev) => !prev);
};
const onClickLogout = () => {
isChecked ? onRemoveAllSessions() : onRemoveAllExceptThis();
};
return (
<ModalDialogContainer
visible={visible}
@ -58,8 +61,8 @@ const LogoutAllConnectionDialog = ({
{t("Profile:LogoutAllActiveConnections")}
</ModalDialog.Header>
<ModalDialog.Body>
<Text as="p">{t("Profile:LogoutDescription")}</Text>
<Text as="p" style={{ margin: "15px 0" }}>
<Text>{t("Profile:LogoutDescription")}</Text>
<Text style={{ margin: "15px 0" }}>
{t("Profile:DescriptionForSecurity")}
</Text>
<Box displayProp="flex" alignItems="center">
@ -79,10 +82,8 @@ const LogoutAllConnectionDialog = ({
size="normal"
scale
primary={true}
onClick={() =>
isChecked ? onRemoveAllSessions() : onRemoveAllExceptThis()
}
isLoading={loading}
onClick={onClickLogout}
isLoading={isLoading}
/>
<Button
className="cancel-button"
@ -91,11 +92,11 @@ const LogoutAllConnectionDialog = ({
size="normal"
scale
onClick={onClose}
isDisabled={loading}
isDisabled={isLoading}
/>
</ModalDialog.Footer>
</ModalDialogContainer>
);
};
export default LogoutAllConnectionDialog;
export default LogoutAllSessionDialog;

View File

@ -24,21 +24,22 @@
// 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
import React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@docspace/shared/components/button";
import { ModalDialog } from "@docspace/shared/components/modal-dialog";
import ModalDialogContainer from "../ModalDialogContainer";
const LogoutConnectionDialog = ({
const LogoutSessionDialog = ({
t,
data,
visible,
onClose,
onRemoveSession,
data,
loading,
isLoading,
}) => {
const { t } = useTranslation(["Profile", "Common"]);
const onClick = () => {
onRemoveSession(data.id);
};
return (
<ModalDialogContainer
@ -62,8 +63,8 @@ const LogoutConnectionDialog = ({
size="normal"
scale
primary={true}
onClick={() => onRemoveSession(data.id)}
isLoading={loading}
onClick={onClick}
isLoading={isLoading}
/>
<Button
key="CloseBtn"
@ -71,11 +72,11 @@ const LogoutConnectionDialog = ({
size="normal"
scale
onClick={onClose}
isDisabled={loading}
isDisabled={isLoading}
/>
</ModalDialog.Footer>
</ModalDialogContainer>
);
};
export default LogoutConnectionDialog;
export default LogoutSessionDialog;

View File

@ -50,8 +50,8 @@ import ChangeNameDialog from "./ChangeNameDialog";
import AvatarEditorDialog from "./AvatarEditorDialog";
import DeletePortalDialog from "./DeletePortalDialog";
import InviteUsersWarningDialog from "./InviteUsersWarningDialog";
import LogoutConnectionDialog from "./LogoutConnectionDialog";
import LogoutAllConnectionDialog from "./LogoutAllConnectionDialog";
import LogoutSessionDialog from "./LogoutSessionDialog";
import LogoutAllSessionDialog from "./LogoutAllSessionDialog";
import CreateRoomConfirmDialog from "./CreateRoomConfirmDialog";
import PortalRenamingDialog from "./PortalRenamingDialog";
import DataReassignmentDialog from "./DataReassignmentDialog";
@ -97,9 +97,9 @@ export {
ChangeNameDialog,
AvatarEditorDialog,
DeletePortalDialog,
LogoutConnectionDialog,
LogoutSessionDialog,
InviteUsersWarningDialog,
LogoutAllConnectionDialog,
LogoutAllSessionDialog,
PortalRenamingDialog,
DataReassignmentDialog,
SubmitToFormGallery,

View File

@ -0,0 +1,129 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { useEffect, useState } from "react";
import { TableHeader } from "@docspace/shared/components/table";
const TABLE_VERSION = "5";
const TABLE_COLUMNS = `SessionsColumns_ver-${TABLE_VERSION}`;
const getColumns = (defaultColumns, userId) => {
const storageColumns = localStorage.getItem(`${TABLE_COLUMNS}=${userId}`);
const columns = [];
if (storageColumns) {
const splitColumns = storageColumns.split(",");
for (let col of defaultColumns) {
const column = splitColumns.find((key) => key === col.key);
column ? (col.enable = true) : (col.enable = false);
columns.push(col);
}
return columns;
} else {
return defaultColumns;
}
};
const SessionsTableHeader = (props) => {
const {
t,
userId,
sectionWidth,
setHideColumns,
containerRef,
columnStorageName,
columnInfoPanelStorageName,
} = props;
const defaultColumns = [
{
key: "Sessions",
title: t("Common:Sessions"),
resizable: true,
enable: true,
default: true,
active: true,
minWidth: 180,
isDisabled: true,
onChange: onColumnChange,
},
{
key: "Date",
title: t("Common:Date"),
enable: true,
resizable: true,
onChange: onColumnChange,
},
{
key: "Location",
title: t("Common:Location"),
enable: true,
resizable: true,
onChange: onColumnChange,
},
];
const [columns, setColumns] = useState(getColumns(defaultColumns, userId));
useEffect(() => {
setColumns(getColumns(defaultColumns));
}, []);
function onColumnChange(key, e) {
const columnIndex = columns.findIndex((c) => c.key === key);
if (columnIndex === -1) return;
setColumns((prevColumns) =>
prevColumns.map((item, index) =>
index === columnIndex ? { ...item, enable: !item.enable } : item,
),
);
const tableColumns = columns.map((c) => c.enable && c.key);
localStorage.setItem(`${TABLE_COLUMNS}=${userId}`, tableColumns);
}
return (
<TableHeader
checkboxSize="48px"
containerRef={containerRef}
columns={columns}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
sectionWidth={sectionWidth}
checkboxMargin="12px"
showSettings={false}
useReactWindow
setHideColumns={setHideColumns}
infoPanelVisible={false}
/>
);
};
export default SessionsTableHeader;

View File

@ -0,0 +1,152 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { inject, observer } from "mobx-react";
import { ReactSVG } from "react-svg";
import { Base } from "@docspace/shared/themes";
import styled, { css } from "styled-components";
import { TableRow } from "@docspace/shared/components/table";
import { TableCell } from "@docspace/shared/components/table";
import { Text } from "@docspace/shared/components/text";
import { Box } from "@docspace/shared/components/box";
import { IconButton } from "@docspace/shared/components/icon-button";
import { convertTime } from "@docspace/shared/utils/convertTime";
import RemoveSessionSvgUrl from "PUBLIC_DIR/images/remove.session.svg?url";
import TickSvgUrl from "PUBLIC_DIR/images/tick.svg?url";
const StyledTableRow = styled(TableRow)`
.session-platform {
font-weight: 600;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 5px;
`
: css`
margin-right: 5px;
`}
}
.session-info {
font-weight: 600;
color: ${(props) => props.theme.profile.activeSessions.tableCellColor};
}
.divider {
display: inline-block;
height: 12px;
width: 2px;
background-color: ${(props) =>
props.theme.profile.activeSessions.dividerColor};
margin: -2px 5px;
}
.tick-icon {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 8px;
`
: css`
margin-left: 8px;
`}
}
`;
StyledTableRow.defaultProps = { theme: Base };
const SessionsTableRow = (props) => {
const {
item,
hideColumns,
currentSession,
setPlatformModalData,
setLogoutDialogVisible,
} = props;
const { platform, browser, date, country, city, ip } = item;
const showRemoveIcon = currentSession !== item.id;
const showTickIcon = currentSession === item.id;
const onRemoveClick = () => {
setLogoutDialogVisible(true);
setPlatformModalData({
id: item?.id,
platform: item?.platform,
browser: item?.browser,
});
};
return (
<StyledTableRow key={item.id} hideColumns={hideColumns}>
<TableCell>
<Text className="session-platform">{platform}</Text>
<Text className="session-info">{`(${browser})`}</Text>
{showTickIcon && <ReactSVG className="tick-icon" src={TickSvgUrl} />}
</TableCell>
<TableCell>
<Text className="session-info" truncate>
{convertTime(date)}
</Text>
</TableCell>
<TableCell>
<Text className="session-info" truncate>
{country}, {city}
<Text as="span" className="divider" />
{ip}
</Text>
</TableCell>
{showRemoveIcon && (
<TableCell>
<Box>
<IconButton
size={20}
iconName={RemoveSessionSvgUrl}
isClickable
onClick={onRemoveClick}
/>
</Box>
</TableCell>
)}
</StyledTableRow>
);
};
export default inject(({ setup }) => {
const { currentSession, setLogoutDialogVisible, setPlatformModalData } =
setup;
return {
currentSession,
setLogoutDialogVisible,
setPlatformModalData,
};
})(observer(SessionsTableRow));

View File

@ -0,0 +1,102 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { useState, useRef } from "react";
import { inject, observer } from "mobx-react";
import { Base } from "@docspace/shared/themes";
import styled from "styled-components";
import SessionsTableHeader from "./SessionsTableHeader";
import SessionsTableRow from "./SessionsTableRow";
import { TableContainer } from "@docspace/shared/components/table";
import { TableBody } from "@docspace/shared/components/table";
const TABLE_VERSION = "5";
const COLUMNS_SIZE = `sessionsColumnsSize_ver-${TABLE_VERSION}`;
const INFO_PANEL_COLUMNS_SIZE = `infoPanelSessionsColumnsSize_ver-${TABLE_VERSION}`;
const StyledTableContainer = styled(TableContainer)`
margin: 0 0 20px;
.header-container-text {
color: ${(props) => props.theme.tableContainer.header.textColor};
font-size: ${(props) => props.theme.getCorrectFontSize("12px")};
}
`;
StyledTableContainer.defaultProps = { theme: Base };
const TableView = ({ t, sectionWidth, userId, sessionsData }) => {
const [hideColumns, setHideColumns] = useState(false);
const ref = useRef(null);
const columnStorageName = `${COLUMNS_SIZE}=${userId}`;
const columnInfoPanelStorageName = `${INFO_PANEL_COLUMNS_SIZE}=${userId}`;
return (
<StyledTableContainer forwardedRef={ref} useReactWindow>
<SessionsTableHeader
t={t}
userId={userId}
sectionWidth={sectionWidth}
setHideColumns={setHideColumns}
containerRef={ref}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
/>
<TableBody
itemHeight={49}
useReactWindow
infoPanelVisible={false}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
filesLength={sessionsData.length}
hasMoreFiles={false}
itemCount={sessionsData.length}
fetchMoreFiles={() => {}}
>
{sessionsData.map((item) => (
<SessionsTableRow
t={t}
key={item.id}
hideColumns={hideColumns}
userId={userId}
item={item}
/>
))}
</TableBody>
</StyledTableContainer>
);
};
export default inject(({ userStore }) => {
const { id: userId } = userStore.user;
return {
userId,
};
})(observer(TableView));

View File

@ -0,0 +1,61 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { inject, observer } from "mobx-react";
import { Consumer } from "@docspace/shared/utils/context";
import TableView from "./TableView";
import RowView from "./RowView";
const SessionsTable = ({ t, viewAs, sessionsData }) => {
return (
<Consumer>
{(context) =>
viewAs === "table" ? (
<TableView
t={t}
sectionWidth={context.sectionWidth}
sessionsData={sessionsData}
/>
) : (
<RowView
t={t}
sectionWidth={context.sectionWidth}
sessionsData={sessionsData}
/>
)
}
</Consumer>
);
};
export default inject(({ setup }) => {
const { viewAs } = setup;
return {
viewAs,
};
})(observer(SessionsTable));

View File

@ -24,71 +24,73 @@
// 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
import RemoveSessionSvgUrl from "PUBLIC_DIR/images/remove.session.svg?url";
import TickSvgUrl from "PUBLIC_DIR/images/tick.svg?url";
import InfoReactSvgUrl from "PUBLIC_DIR/images/info.react.svg?url";
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import { ReactSVG } from "react-svg";
import styled, { css } from "styled-components";
import { Text } from "@docspace/shared/components/text";
import { Link } from "@docspace/shared/components/link";
import { Box } from "@docspace/shared/components/box";
import { HelpButton } from "@docspace/shared/components/help-button";
import { toastr } from "@docspace/shared/components/toast";
import { useTheme } from "styled-components";
import { convertTime } from "@docspace/shared/utils/convertTime";
import { HelpButton } from "@docspace/shared/components/help-button";
import { ProfileFooterLoader } from "@docspace/shared/skeletons/profile";
import InfoReactSvgUrl from "PUBLIC_DIR/images/info.react.svg?url";
import SessionsTable from "./SessionsTable";
import {
LogoutConnectionDialog,
LogoutAllConnectionDialog,
LogoutSessionDialog,
LogoutAllSessionDialog,
} from "SRC_DIR/components/dialogs";
import {
StyledFooter,
Table,
TableHead,
TableRow,
TableHeaderCell,
TableBody,
TableDataCell,
} from "./styled-active-sessions";
import { DeviceType } from "@docspace/shared/enums";
import moment from "moment-timezone";
const removeIcon = (
<ReactSVG className="remove-icon" src={RemoveSessionSvgUrl} />
);
const tickIcon = (
<ReactSVG className="tick-icon" wrapper="span" src={TickSvgUrl} />
);
const StyledWrapper = styled.div`
.auto-delete-title {
font-size: 13px;
font-weight: 400;
line-height: 20px;
margin-top: 8px;
color: ${(props) => props.theme.profile.activeSessions.tableCellColor};
}
.terminate-session-container {
display: flex;
align-items: center;
justify-content: flex-start;
margin: 10px 0 0;
}
.terminate-all-sessions {
font-size: ${(props) => props.theme.getCorrectFontSize("13px")};
font-weight: 600;
}
.icon-button {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 5px;
`
: css`
margin-left: 5px;
`}
}
`;
const ActiveSessions = ({
t,
locale,
getAllSessions,
removeAllSessions,
removeSession,
logoutVisible,
setLogoutVisible,
logoutAllVisible,
setLogoutAllVisible,
logoutDialogVisible,
setLogoutDialogVisible,
logoutAllDialogVisible,
setLogoutAllDialogVisible,
removeAllExecptThis,
sessionsIsInit,
getSessions,
sessions,
currentSession,
setSessions,
currentDeviceType,
platformModalData,
}) => {
const isDesktop = currentDeviceType === DeviceType.desktop;
const isMobile = currentDeviceType === DeviceType.mobile;
const [modalData, setModalData] = useState({});
const [loading, setLoading] = useState(false);
const { interfaceDirection } = useTheme();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
getSessions();
@ -96,34 +98,34 @@ const ActiveSessions = ({
const onClickRemoveAllSessions = async () => {
try {
setLoading(true);
setIsLoading(true);
await removeAllSessions().then((res) => window.location.replace(res));
} catch (error) {
toastr.error(error);
} finally {
setLoading(false);
setLogoutAllVisible(false);
setIsLoading(false);
setLogoutAllDialogVisible(false);
}
};
const onClickRemoveAllExceptThis = async () => {
try {
setLoading(true);
setIsLoading(true);
await removeAllExecptThis().then(() =>
getAllSessions().then((res) => setSessions(res.items)),
);
} catch (error) {
toastr.error(error);
} finally {
setLoading(false);
setLogoutAllVisible(false);
setIsLoading(false);
setLogoutAllDialogVisible(false);
}
};
const onClickRemoveSession = async (id) => {
const foundSession = sessions.find((s) => s.id === id);
try {
setLoading(true);
setIsLoading(true);
await removeSession(foundSession.id).then(() =>
getAllSessions().then((res) => setSessions(res.items)),
);
@ -136,184 +138,97 @@ const ActiveSessions = ({
} catch (error) {
toastr.error(error);
} finally {
setLoading(false);
setLogoutVisible(false);
setIsLoading(false);
setLogoutDialogVisible(false);
}
};
const convertTime = (date) => {
return moment(date).tz(window.timezone).locale(locale).format("L, LTS");
};
const tableCell = (platform, browser) =>
interfaceDirection === "rtl" && !isMobile ? (
<>
<span className="session-browser">
<span>{browser}</span>
</span>
{platform}
</>
) : (
<>
{platform}
<span className="session-browser">
<span>{browser}</span>
</span>
</>
);
const tooltipContent = (
<Text fontSize="12px">
{t("Profile:LogoutAllActiveSessionsDescription")}
</Text>
);
if (!sessionsIsInit) return <ProfileFooterLoader isProfileFooter />;
return (
<StyledFooter>
<StyledWrapper>
<Text fontSize="16px" fontWeight={700} lineHeight="22px">
{t("Profile:ActiveSessions")}
</Text>
<Box
displayProp="flex"
alignItems="center"
justifyContent="flex-start"
marginProp="10px 0 0"
>
<Text className="auto-delete-title">{t("Profile:AutoDeleteTitle")}</Text>
<Box className="terminate-session-container">
<Link
className="session-logout"
className="terminate-all-sessions"
type="action"
isHovered
onClick={() => setLogoutAllVisible(true)}
onClick={() => setLogoutAllDialogVisible(true)}
>
{t("Profile:LogoutAllActiveSessions")}
{t("Profile:TerminateAllSessions")}
</Link>
<HelpButton
offsetRight={0}
iconName={InfoReactSvgUrl}
tooltipContent={
<Text fontSize="12px">
{t("Profile:LogoutAllActiveSessionsDescription")}
</Text>
}
tooltipContent={tooltipContent}
/>
</Box>
{!isDesktop ? (
<Table>
<TableBody>
{sessions.map((session) => (
<TableRow key={session.id}>
<TableDataCell style={{ borderTop: "0" }}>
{tableCell(session.platform, session.browser)}
{currentSession === session.id ? tickIcon : null}
<Box flexDirection="column" alignItems="center">
<span className="session-date">
{convertTime(session.date)}
</span>
<span className="session-ip" dir="ltr">
{session.ip}
</span>
</Box>
</TableDataCell>
<SessionsTable t={t} sessionsData={sessions} />
<TableDataCell
style={{ borderTop: "0" }}
onClick={() => {
setLogoutVisible(true);
setModalData({
id: session.id,
platform: session.platform,
browser: session.browser,
});
}}
>
{currentSession !== session.id ? removeIcon : null}
</TableDataCell>
</TableRow>
))}
</TableBody>
</Table>
) : (
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>{t("Common:Sessions")}</TableHeaderCell>
<TableHeaderCell>{t("Common:Date")}</TableHeaderCell>
<TableHeaderCell>{t("Common:IpAddress")}</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{sessions.map((session) => (
<TableRow key={session.id}>
<TableDataCell>
{tableCell(session.platform, session.browser)}
{currentSession === session.id ? tickIcon : null}
</TableDataCell>
<TableDataCell>{convertTime(session.date)}</TableDataCell>
<TableDataCell>{session.ip}</TableDataCell>
<TableDataCell
onClick={() => {
setLogoutVisible(true);
setModalData({
id: session.id,
platform: session.platform,
browser: session.browser,
});
}}
>
{currentSession !== session.id ? removeIcon : null}
</TableDataCell>
</TableRow>
))}
</TableBody>
</Table>
)}
{logoutVisible && (
<LogoutConnectionDialog
visible={logoutVisible}
data={modalData}
loading={loading}
onClose={() => setLogoutVisible(false)}
{logoutDialogVisible && (
<LogoutSessionDialog
t={t}
visible={logoutDialogVisible}
data={platformModalData}
isLoading={isLoading}
onClose={() => setLogoutDialogVisible(false)}
onRemoveSession={onClickRemoveSession}
/>
)}
{logoutAllVisible && (
<LogoutAllConnectionDialog
visible={logoutAllVisible}
loading={loading}
onClose={() => setLogoutAllVisible(false)}
{logoutAllDialogVisible && (
<LogoutAllSessionDialog
t={t}
visible={logoutAllDialogVisible}
isLoading={isLoading}
onClose={() => setLogoutAllDialogVisible(false)}
onRemoveAllSessions={onClickRemoveAllSessions}
onRemoveAllExceptThis={onClickRemoveAllExceptThis}
/>
)}
</StyledFooter>
</StyledWrapper>
);
};
export default inject(({ settingsStore, userStore, setup }) => {
const { culture, currentDeviceType } = settingsStore;
const { user } = userStore;
const locale = (user && user.cultureName) || culture || "en";
export default inject(({ settingsStore, setup }) => {
const { currentDeviceType } = settingsStore;
const {
getAllSessions,
removeAllSessions,
removeSession,
logoutVisible,
setLogoutVisible,
logoutAllVisible,
setLogoutAllVisible,
logoutDialogVisible,
setLogoutDialogVisible,
logoutAllDialogVisible,
setLogoutAllDialogVisible,
removeAllExecptThis,
sessionsIsInit,
sessions,
currentSession,
getSessions,
setSessions,
platformModalData,
} = setup;
return {
locale,
getAllSessions,
removeAllSessions,
removeSession,
logoutVisible,
setLogoutVisible,
logoutAllVisible,
setLogoutAllVisible,
logoutDialogVisible,
setLogoutDialogVisible,
logoutAllDialogVisible,
setLogoutAllDialogVisible,
removeAllExecptThis,
sessionsIsInit,
sessions,
@ -321,5 +236,6 @@ export default inject(({ settingsStore, userStore, setup }) => {
getSessions,
setSessions,
currentDeviceType,
platformModalData,
};
})(observer(withTranslation(["Profile", "Common"])(ActiveSessions)));

View File

@ -1,219 +0,0 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import styled, { css } from "styled-components";
import { mobile } from "@docspace/shared/utils";
export const StyledFooter = styled.div`
.session-logout {
font-size: ${(props) => props.theme.getCorrectFontSize("13px")};
font-weight: 600;
}
.icon-button {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 4px;
`
: css`
margin-left: 4px;
`}
}
`;
export const Table = styled.table`
width: 100%;
border-collapse: collapse;
margin-top: 2px;
`;
export const TableHead = styled.thead`
font-size: ${(props) => props.theme.getCorrectFontSize("12px")};
line-height: 16px;
`;
export const TableRow = styled.tr`
display: table-row;
`;
export const TableHeaderCell = styled.th`
border-top: 1px solid ${(props) => props.theme.activeSessions.borderColor};
border-bottom: 1px solid ${(props) => props.theme.activeSessions.borderColor};
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
text-align: right;
`
: css`
text-align: left;
`}
font-weight: 600;
padding: 12px 0;
color: #a3a9ae;
position: relative;
border-top: 0;
:not(:first-child):before {
content: "";
position: absolute;
top: 17px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
right: -8px;
`
: css`
left: -8px;
`}
width: 1px;
height: 10px;
background: ${(props) => props.theme.activeSessions.sortHeaderColor};
}
`;
export const TableBody = styled.tbody`
font-size: ${(props) => props.theme.getCorrectFontSize("11px")};
`;
export const TableDataCell = styled.td`
border-top: 1px solid ${(props) => props.theme.activeSessions.borderColor};
border-bottom: 1px solid ${(props) => props.theme.activeSessions.borderColor};
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
text-align: right;
`
: css`
text-align: left;
`}
font-weight: 600;
padding: 14px 0;
color: #a3a9ae;
.tick-icon {
svg {
path {
fill: ${(props) => props.theme.activeSessions.tickIconColor};
}
}
}
.remove-icon {
svg {
path {
fill: ${(props) => props.theme.activeSessions.removeIconColor};
}
}
}
@media ${mobile} {
.session-browser {
position: relative;
top: 4px;
max-width: 150px;
display: inline-block;
margin-left: 0 !important;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
span {
width: 100%;
}
}
}
:first-child {
font-size: ${(props) => props.theme.getCorrectFontSize("13px")};
color: ${(props) => props.theme.activeSessions.color};
span {
color: #a3a9ae;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 5px;
`
: css`
margin-left: 5px;
`}
}
}
.session-date {
position: relative;
${(props) =>
props.theme.interfaceDirection === "ltr"
? css`
margin-right: 8px;
margin-left: 0 !important;
:after {
content: "";
position: absolute;
top: 4px;
right: -8px;
width: 1px;
height: 12px;
background: ${(props) =>
props.theme.activeSessions.sortHeaderColor};
}
`
: css`
margin-left: 8px;
margin-right: 0 !important;
`}
}
.session-ip {
position: relative;
${(props) =>
props.theme.interfaceDirection === "rtl" &&
css`
:after {
content: "";
position: absolute;
top: 4px;
right: -8px;
width: 1px;
height: 12px;
background: ${(props) => props.theme.activeSessions.sortHeaderColor};
}
`}
}
:last-child {
text-align: end;
}
.remove-icon {
svg {
cursor: pointer;
width: 20px;
height: 20px;
path {
fill: #a3a9ae;
}
}
}
`;

View File

@ -47,8 +47,8 @@ class SettingsSetupStore {
tfaStore = null;
thirdPartyStore = null;
isInit = false;
logoutVisible = false;
logoutAllVisible = false;
logoutDialogVisible = false;
logoutAllDialogVisible = false;
viewAs = isDesktop() ? "table" : "row";
isLoadingDownloadReport = false;
@ -109,6 +109,7 @@ class SettingsSetupStore {
sessionsIsInit = false;
sessions = [];
currentSession = [];
platformModalData = {};
constructor(tfaStore, authStore, settingsStore, thirdPartyStore) {
this.selectionStore = new SelectionStore(this);
@ -554,9 +555,13 @@ class SettingsSetupStore {
return api.settings.removeActiveSession(id);
};
setLogoutVisible = (visible) => (this.logoutVisible = visible);
setLogoutDialogVisible = (visible) => {
this.logoutDialogVisible = visible;
};
setLogoutAllVisible = (visible) => (this.logoutAllVisible = visible);
setLogoutAllDialogVisible = (visible) => {
this.logoutAllDialogVisible = visible;
};
getSessions = () => {
if (this.sessionsIsInit) return;
@ -570,6 +575,14 @@ class SettingsSetupStore {
setSessions = (sessions) => {
this.sessions = sessions;
};
setPlatformModalData = (data) => {
this.platformModalData = {
id: data.id,
platform: data.platform,
browser: data.browser,
};
};
}
export default SettingsSetupStore;

View File

@ -3225,14 +3225,15 @@ export const getBaseTheme = () => {
notifications: {
textDescriptionColor: "#A3A9AE",
},
},
activeSessions: {
color: "#333",
borderColor: "#eceef1",
tickIconColor: "#35AD17",
removeIconColor: "#A3A9AE",
sortHeaderColor: "#d0d5da",
activeSessions: {
color: "#333",
borderColor: "#eceef1",
tickIconColor: "#35AD17",
removeIconColor: "#A3A9AE",
sortHeaderColor: "#d0d5da",
tableCellColor: "#a3a9ae",
dividerColor: "#D0D5DA",
},
},
formWrapper: {

View File

@ -3202,14 +3202,15 @@ const Dark: TTheme = {
notifications: {
textDescriptionColor: "#858585",
},
},
activeSessions: {
color: "#eeeeee",
borderColor: "#474747",
tickIconColor: "#3BA420",
removeIconColor: "#A3A9AE",
sortHeaderColor: "#474747",
activeSessions: {
color: "#eeeeee",
borderColor: "#474747",
tickIconColor: "#3BA420",
removeIconColor: "#A3A9AE",
sortHeaderColor: "#474747",
tableCellColor: "#858585",
dividerColor: "#474747",
},
},
formWrapper: {