Web:Client:PortalSettings:OAuth: add table view
This commit is contained in:
parent
6956adc231
commit
d8a535432d
@ -1,3 +1,12 @@
|
||||
export interface OAuthProps {}
|
||||
//@ts-ignore
|
||||
import { ClientProps } from "@docspace/common/utils/oauth/dto";
|
||||
|
||||
export interface OAuthStore {}
|
||||
//@ts-ignore
|
||||
import { ViewAsType } from "SRC_DIR/store/OAuthStore";
|
||||
|
||||
export interface OAuthProps {
|
||||
viewAs: ViewAsType;
|
||||
clientList: ClientProps[];
|
||||
isEmptyClientList: boolean;
|
||||
fetchClients: (page: number) => Promise<void>;
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// @ts-ignore
|
||||
@ -44,6 +42,4 @@ const OAuthSectionHeader = ({ isEdit }: OAuthSectionHeaderProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({}) => {
|
||||
return {};
|
||||
})(observer(OAuthSectionHeader));
|
||||
export default OAuthSectionHeader;
|
||||
|
@ -4,25 +4,3 @@ export const OAuthContainer = styled.div`
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
`;
|
||||
|
||||
export const Property = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
width: 500px;
|
||||
`;
|
||||
|
||||
export const StyledCategory = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const StyledTooltip = styled.div`
|
||||
.subtitle {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
`;
|
||||
|
@ -2,20 +2,66 @@ import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
//@ts-ignore
|
||||
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
|
||||
|
||||
import OAuthEmptyScreen from "./sub-components/EmptyScreen";
|
||||
import List from "./sub-components/List";
|
||||
|
||||
import { OAuthContainer } from "./StyledOAuth";
|
||||
import { OAuthProps } from "./OAuth.types";
|
||||
|
||||
const OAuth = ({}) => {
|
||||
const MIN_LOADER_TIME = 500;
|
||||
|
||||
const OAuth = ({
|
||||
clientList,
|
||||
viewAs,
|
||||
isEmptyClientList,
|
||||
fetchClients,
|
||||
}: OAuthProps) => {
|
||||
const { t } = useTranslation(["OAuth"]);
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(true);
|
||||
const startLoadingRef = React.useRef<null | Date>(null);
|
||||
|
||||
const getClientList = React.useCallback(async (page = 0) => {
|
||||
await fetchClients(page);
|
||||
|
||||
if (startLoadingRef.current) {
|
||||
const currentDate = new Date();
|
||||
|
||||
const ms = Math.abs(
|
||||
startLoadingRef.current.getTime() - currentDate.getTime()
|
||||
);
|
||||
|
||||
if (ms < MIN_LOADER_TIME)
|
||||
return setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, MIN_LOADER_TIME - ms);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
startLoadingRef.current = new Date();
|
||||
getClientList();
|
||||
}, [getClientList]);
|
||||
|
||||
return (
|
||||
<OAuthContainer>
|
||||
{isLoading ? (
|
||||
<div>Loading...</div>
|
||||
) : isEmptyClientList ? (
|
||||
<OAuthEmptyScreen t={t} />
|
||||
) : (
|
||||
<List t={t} clients={clientList} viewAs={viewAs} />
|
||||
)}
|
||||
</OAuthContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({}) => {
|
||||
return {};
|
||||
export default inject(({ oauthStore }: { oauthStore: OAuthStoreProps }) => {
|
||||
const { viewAs, clientList, isEmptyClientList, fetchClients } = oauthStore;
|
||||
return { viewAs, clientList, isEmptyClientList, fetchClients };
|
||||
})(observer(OAuth));
|
||||
|
@ -1,59 +0,0 @@
|
||||
import InfoReactSvgUrl from "PUBLIC_DIR/images/info.react.svg?url";
|
||||
import React from "react";
|
||||
import Text from "@docspace/components/text";
|
||||
import HelpButton from "@docspace/components/help-button";
|
||||
import Link from "@docspace/components/link";
|
||||
import { Base } from "@docspace/components/themes";
|
||||
import { StyledCategory, StyledTooltip } from "../StyledOAuth";
|
||||
|
||||
const Category = (props) => {
|
||||
const {
|
||||
t,
|
||||
title,
|
||||
tooltipTitle,
|
||||
tooltipUrl,
|
||||
theme,
|
||||
currentColorScheme,
|
||||
} = props;
|
||||
|
||||
const tooltip = () => (
|
||||
<StyledTooltip>
|
||||
<Text className={tooltipUrl ? "subtitle" : ""} fontSize="12px">
|
||||
{tooltipTitle}
|
||||
</Text>
|
||||
{tooltipUrl && (
|
||||
<Link
|
||||
fontSize="12px"
|
||||
target="_blank"
|
||||
isHovered
|
||||
href={tooltipUrl}
|
||||
color={currentColorScheme.main.accent}
|
||||
>
|
||||
{t("Common:LearnMore")}
|
||||
</Link>
|
||||
)}
|
||||
</StyledTooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledCategory>
|
||||
<Text fontSize="16px" fontWeight="700">
|
||||
{title}
|
||||
</Text>
|
||||
<HelpButton
|
||||
iconName={InfoReactSvgUrl}
|
||||
displayType="dropdown"
|
||||
place="right"
|
||||
offsetRight={0}
|
||||
getContent={tooltip}
|
||||
tooltipColor={theme.client.settings.security.owner.tooltipColor}
|
||||
/>
|
||||
</StyledCategory>
|
||||
);
|
||||
};
|
||||
|
||||
Category.defaultProps = {
|
||||
theme: Base,
|
||||
};
|
||||
|
||||
export default Category;
|
@ -1,4 +1,4 @@
|
||||
import { ClientProps, ScopeDTO } from "@docspace/common/utils/oauth/dto";
|
||||
import { ClientProps, Scope } from "@docspace/common/utils/oauth/interfaces";
|
||||
|
||||
export interface InputProps {
|
||||
value: string;
|
||||
@ -38,13 +38,19 @@ export interface ClientFormProps {
|
||||
id?: string;
|
||||
client?: ClientProps;
|
||||
|
||||
scopeList?: ScopeDTO[];
|
||||
tenant?: number;
|
||||
fetchTenant?: () => Promise<number>;
|
||||
|
||||
scopeList?: Scope[];
|
||||
|
||||
fetchClient?: (clientId: string) => Promise<ClientProps>;
|
||||
fetchScopes?: () => Promise<void>;
|
||||
|
||||
saveClient: (client: ClientProps) => Promise<ClientProps>;
|
||||
updateClient: (clientId: string, client: ClientProps) => Promise<ClientProps>;
|
||||
saveClient?: (client: ClientProps) => Promise<ClientProps>;
|
||||
updateClient?: (
|
||||
clientId: string,
|
||||
client: ClientProps
|
||||
) => Promise<ClientProps>;
|
||||
|
||||
regenerateSecret?: (clientId: string) => Promise<string>;
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import { BlockContainer } from "../ClientForm.styled";
|
||||
import { BlockProps } from "../ClientForm.types";
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import Text from "@docspace/components/text";
|
||||
//@ts-ignore
|
||||
import HelpButton from "@docspace/components/help-button";
|
||||
|
@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import Checkbox from "@docspace/components/checkbox";
|
||||
import Text from "@docspace/components/text";
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
import InputBlock from "@docspace/components/input-block";
|
||||
@ -45,8 +44,8 @@ const Input = ({
|
||||
size={"base"}
|
||||
isReadOnly={isReadOnly}
|
||||
isDisabled={isReadOnly}
|
||||
iconName={withCopy ? CopyReactSvgUrl : ""}
|
||||
onIconClick={onCopy}
|
||||
iconName={withCopy ? CopyReactSvgUrl : null}
|
||||
onIconClick={withCopy && onCopy}
|
||||
scale={true}
|
||||
type={isSecret ? "password" : "text"}
|
||||
/>
|
||||
|
@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
import Text from "@docspace/components/text";
|
||||
|
||||
const InputHeader = ({ header }: { header: string }) => {
|
||||
|
@ -3,7 +3,7 @@ import { inject, observer } from "mobx-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
|
||||
import { ClientProps, ScopeDTO } from "@docspace/common/utils/oauth/dto";
|
||||
import { ClientProps, Scope } from "@docspace/common/utils/oauth/interfaces";
|
||||
|
||||
import Button from "@docspace/components/button";
|
||||
|
||||
@ -27,6 +27,9 @@ const ClientForm = ({
|
||||
|
||||
scopeList,
|
||||
|
||||
tenant,
|
||||
fetchTenant,
|
||||
|
||||
fetchClient,
|
||||
fetchScopes,
|
||||
|
||||
@ -45,20 +48,19 @@ const ClientForm = ({
|
||||
appName: "",
|
||||
appIcon: "",
|
||||
description: "",
|
||||
|
||||
redirectUrl: "",
|
||||
termsURL: "",
|
||||
privacyURL: "",
|
||||
logoutRedirectUrl: "",
|
||||
|
||||
privacyURL: "",
|
||||
authenticationMethod: "",
|
||||
});
|
||||
|
||||
const [clientId, setClientId] = React.useState<string>(
|
||||
"23b2ec16-6a10-462b-8084-16be8e105b73"
|
||||
);
|
||||
const [secret, setSecret] = React.useState<string>(
|
||||
"d2c083aa-9a2d-4147-9328-df32b7be0294"
|
||||
);
|
||||
const [clientId, setClientId] = React.useState<string>("");
|
||||
const [secret, setSecret] = React.useState<string>("");
|
||||
|
||||
const [scopes, setScopes] = React.useState<ScopeDTO[]>([]);
|
||||
const [scopes, setScopes] = React.useState<Scope[]>([]);
|
||||
const [checkedScopes, setCheckedScopes] = React.useState<string[]>([]);
|
||||
|
||||
const onInputChange = React.useCallback(
|
||||
@ -101,8 +103,17 @@ const ClientForm = ({
|
||||
newClient.scopes = [...checkedScopes];
|
||||
|
||||
if (!id) {
|
||||
if (!saveClient) return;
|
||||
|
||||
if (tenant === -1 && fetchTenant) {
|
||||
const t = await fetchTenant();
|
||||
|
||||
newClient.tenant = t;
|
||||
}
|
||||
|
||||
await saveClient(newClient);
|
||||
} else {
|
||||
if (!updateClient) return;
|
||||
await updateClient(clientId, newClient);
|
||||
}
|
||||
|
||||
@ -137,11 +148,15 @@ const ClientForm = ({
|
||||
const setClient = React.useCallback(async (client: ClientProps) => {
|
||||
setForm({
|
||||
appName: client.name,
|
||||
appIcon: client.logoUrl,
|
||||
appIcon: client.logoUrl || "",
|
||||
description: client.description,
|
||||
|
||||
redirectUrl: client.redirectUri,
|
||||
logoutRedirectUrl: client.logoutRedirectUri,
|
||||
privacyURL: client.policyUrl,
|
||||
termsUrl: client.termsUrl,
|
||||
logoutRedirectUrl: client.logoutRedirectUri,
|
||||
|
||||
authenticationMethod: client.authenticationMethod,
|
||||
});
|
||||
|
||||
setSecret(client.secret);
|
||||
@ -178,7 +193,7 @@ const ClientForm = ({
|
||||
let isValid = false;
|
||||
|
||||
for (let key in form) {
|
||||
if (!!form[key]) {
|
||||
if (!!form[key] || key === "appIcon" || key === "authenticationMethod") {
|
||||
if (initClient) {
|
||||
switch (key) {
|
||||
case "appName":
|
||||
@ -186,7 +201,7 @@ const ClientForm = ({
|
||||
|
||||
break;
|
||||
case "appIcon":
|
||||
isValid = isValid || initClient.logoUrl !== form[key];
|
||||
isValid = isValid || initClient.name !== form[key];
|
||||
|
||||
break;
|
||||
case "description":
|
||||
@ -204,6 +219,11 @@ const ClientForm = ({
|
||||
case "privacyUrl":
|
||||
isValid = isValid || initClient.policyUrl !== form[key];
|
||||
|
||||
break;
|
||||
|
||||
case "termsUrl":
|
||||
isValid = isValid || initClient.termsUrl !== form[key];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -266,6 +286,7 @@ const ClientForm = ({
|
||||
</InputGroup>
|
||||
</Block>
|
||||
|
||||
{id && (
|
||||
<Block>
|
||||
<BlockHeader header={"Client"} helpButtonText="" />
|
||||
<InputGroup>
|
||||
@ -294,7 +315,20 @@ const ClientForm = ({
|
||||
onClickButton={onResetClick}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<InputHeader header={"Authentication method "} />
|
||||
<Input
|
||||
value={form.authenticationMethod}
|
||||
name={"authenticationMethod"}
|
||||
placeholder={"Enter secret"}
|
||||
onChange={onInputChange}
|
||||
isReadOnly
|
||||
withCopy
|
||||
/>
|
||||
</InputGroup>
|
||||
</Block>
|
||||
)}
|
||||
|
||||
<Block>
|
||||
<BlockHeader header={"OAuth URLs"} helpButtonText="" />
|
||||
@ -316,15 +350,6 @@ const ClientForm = ({
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</InputGroup>
|
||||
{/* <InputGroup>
|
||||
<InputHeader header={"Allowed origins"} />
|
||||
<Input
|
||||
value={form.allowedOrigins}
|
||||
name={"allowedOrigins"}
|
||||
placeholder={"Enter URL"}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</InputGroup> */}
|
||||
</Block>
|
||||
|
||||
<Block>
|
||||
@ -345,15 +370,7 @@ const ClientForm = ({
|
||||
|
||||
<Block>
|
||||
<BlockHeader header={"Support & Legal info"} helpButtonText="" />
|
||||
{/* <InputGroup>
|
||||
<InputHeader header={"Website URL"} />
|
||||
<Input
|
||||
value={form.websiteUrl}
|
||||
name={"websiteUrl"}
|
||||
placeholder={"Enter URL"}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</InputGroup> */}
|
||||
|
||||
<InputGroup>
|
||||
<InputHeader header={"Privacy policy URL"} />
|
||||
<Input
|
||||
@ -363,15 +380,15 @@ const ClientForm = ({
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</InputGroup>
|
||||
{/* <InputGroup>
|
||||
<InputGroup>
|
||||
<InputHeader header={"Terms of Service URL"} />
|
||||
<Input
|
||||
value={form.serviceUrl}
|
||||
name={"serviceUrl"}
|
||||
value={form.termsURL}
|
||||
name={"termsURL"}
|
||||
placeholder={"Enter URL"}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</InputGroup> */}
|
||||
</InputGroup>
|
||||
</Block>
|
||||
|
||||
<div className="button-container">
|
||||
@ -408,6 +425,9 @@ export default inject(
|
||||
fetchClient,
|
||||
fetchScopes,
|
||||
|
||||
tenant,
|
||||
fetchTenant,
|
||||
|
||||
saveClient,
|
||||
updateClient,
|
||||
|
||||
@ -420,6 +440,9 @@ export default inject(
|
||||
fetchClient,
|
||||
fetchScopes,
|
||||
|
||||
tenant,
|
||||
fetchTenant,
|
||||
|
||||
saveClient,
|
||||
updateClient,
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
// @ts-ignore
|
||||
import EmptyScreenContainer from "@docspace/components/empty-screen-container";
|
||||
|
||||
|
@ -1,116 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import TableHeader from "@docspace/components/table-container/TableHeader";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
const TABLE_VERSION = "1";
|
||||
const TABLE_COLUMNS = `oauthConfigColumns_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 Header = (props) => {
|
||||
const {
|
||||
userId,
|
||||
sectionWidth,
|
||||
tableRef,
|
||||
columnStorageName,
|
||||
columnInfoPanelStorageName,
|
||||
setHideColumns,
|
||||
} = props;
|
||||
const { t } = useTranslation(["Webhooks", "Common"]);
|
||||
|
||||
const defaultColumns = [
|
||||
{
|
||||
key: "Logo",
|
||||
title: "Logo",
|
||||
enable: true,
|
||||
active: true,
|
||||
resizable: false,
|
||||
defaultSize: 64,
|
||||
onChange: onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Name",
|
||||
title: t("Common:Name"),
|
||||
resizable: true,
|
||||
enable: true,
|
||||
default: true,
|
||||
active: true,
|
||||
minWidth: 150,
|
||||
onChange: onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Description",
|
||||
title: "Description",
|
||||
resizable: true,
|
||||
enable: true,
|
||||
minWidth: 150,
|
||||
onChange: onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Enable",
|
||||
title: "Enable",
|
||||
enable: true,
|
||||
resizable: false,
|
||||
defaultSize: 64,
|
||||
onChange: onColumnChange,
|
||||
},
|
||||
];
|
||||
|
||||
const [columns, setColumns] = useState(getColumns(defaultColumns, userId));
|
||||
|
||||
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, enabled: !item.enabled } : item
|
||||
)
|
||||
);
|
||||
|
||||
const tableColumns = columns.map((c) => c.enabled && c.key);
|
||||
localStorage.setItem(`${TABLE_COLUMNS}=${userId}`, tableColumns);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHeader
|
||||
checkboxSize="48px"
|
||||
containerRef={tableRef}
|
||||
columns={columns}
|
||||
columnStorageName={columnStorageName}
|
||||
columnInfoPanelStorageName={columnInfoPanelStorageName}
|
||||
sectionWidth={sectionWidth}
|
||||
checkboxMargin="12px"
|
||||
showSettings={false}
|
||||
useReactWindow
|
||||
setHideColumns={setHideColumns}
|
||||
infoPanelVisible={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({ auth }) => {
|
||||
return {
|
||||
userId: auth.userStore.user.id,
|
||||
};
|
||||
})(observer(Header));
|
@ -0,0 +1,68 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
//@ts-ignore
|
||||
import TableHeader from "@docspace/components/table-container/TableHeader";
|
||||
|
||||
const TABLE_VERSION = "1";
|
||||
const TABLE_COLUMNS = `oauthConfigColumns_ver-${TABLE_VERSION}`;
|
||||
|
||||
interface HeaderProps {
|
||||
sectionWidth: number;
|
||||
tableRef: HTMLDivElement;
|
||||
columnStorageName: string;
|
||||
}
|
||||
|
||||
const Header = (props: HeaderProps) => {
|
||||
const { sectionWidth, tableRef, columnStorageName } = props;
|
||||
const { t } = useTranslation(["Webhooks", "Common"]);
|
||||
|
||||
const defaultColumns: {
|
||||
[key: string]:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| ((key: string, e: any) => void | undefined);
|
||||
}[] = [
|
||||
{
|
||||
key: "Name",
|
||||
title: t("Common:Name"),
|
||||
resizable: true,
|
||||
enable: true,
|
||||
default: true,
|
||||
active: false,
|
||||
minWidth: 210,
|
||||
},
|
||||
{
|
||||
key: "Description",
|
||||
title: "Description",
|
||||
resizable: true,
|
||||
enable: true,
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
key: "Enable",
|
||||
title: "Enable",
|
||||
enable: true,
|
||||
resizable: false,
|
||||
defaultSize: 64,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<TableHeader
|
||||
checkboxSize="48px"
|
||||
containerRef={tableRef}
|
||||
columns={defaultColumns}
|
||||
columnStorageName={columnStorageName}
|
||||
sectionWidth={sectionWidth}
|
||||
checkboxMargin="12px"
|
||||
showSettings={false}
|
||||
useReactWindow
|
||||
infoPanelVisible={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
@ -1,148 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import TableRow from "@docspace/components/table-container/TableRow";
|
||||
import TableCell from "@docspace/components/table-container/TableCell";
|
||||
import Text from "@docspace/components/text";
|
||||
|
||||
import ToggleButton from "@docspace/components/toggle-button";
|
||||
import SettingsIcon from "PUBLIC_DIR/images/catalog.settings.react.svg?url";
|
||||
import HistoryIcon from "PUBLIC_DIR/images/history.react.svg?url";
|
||||
import DeleteIcon from "PUBLIC_DIR/images/delete.react.svg?url";
|
||||
import LinuxIcon from "PUBLIC_DIR/images/linux.react.svg?url";
|
||||
//import StatusBadge from "../../StatusBadge";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: contents;
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
.table-container_cell {
|
||||
padding-right: 30px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.mr-8 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.textOverflow {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.toggleButton {
|
||||
display: contents;
|
||||
}
|
||||
`;
|
||||
|
||||
const Row = (props) => {
|
||||
const {
|
||||
item,
|
||||
setEnabled,
|
||||
openDeleteModal,
|
||||
setCurrentClient,
|
||||
hideColumns,
|
||||
} = props;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation(["Webhooks", "Common"]);
|
||||
|
||||
const [isChecked, setIsChecked] = useState(item.enabled);
|
||||
|
||||
const openClientSettings = () => {
|
||||
navigate(window.location.pathname + `/${item.id}`);
|
||||
};
|
||||
|
||||
const handleToggleEnabled = () => {
|
||||
setEnabled(item.id);
|
||||
setIsChecked((prevIsChecked) => !prevIsChecked);
|
||||
};
|
||||
|
||||
const onSettingsOpen = () => {
|
||||
setCurrentClient(item);
|
||||
openClientSettings();
|
||||
};
|
||||
|
||||
const onDeleteOpen = () => {
|
||||
setCurrentClient(item);
|
||||
openDeleteModal();
|
||||
};
|
||||
|
||||
const handleRowClick = (e) => {
|
||||
if (
|
||||
e.target.closest(".checkbox") ||
|
||||
e.target.closest(".table-container_row-checkbox") ||
|
||||
e.target.closest(".type-combobox") ||
|
||||
e.target.closest(".table-container_row-context-menu-wrapper") ||
|
||||
e.target.closest(".toggleButton") ||
|
||||
e.detail === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSettingsOpen();
|
||||
};
|
||||
|
||||
const contextOptions = [
|
||||
{
|
||||
key: "settings",
|
||||
label: t("Common:Settings"),
|
||||
icon: SettingsIcon,
|
||||
onClick: onSettingsOpen,
|
||||
},
|
||||
{
|
||||
key: "Separator dropdownItem",
|
||||
isSeparator: true,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: DeleteIcon,
|
||||
onClick: onDeleteOpen,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledWrapper onClick={handleRowClick}>
|
||||
<StyledTableRow
|
||||
contextOptions={contextOptions}
|
||||
hideColumns={hideColumns}
|
||||
>
|
||||
<TableCell>
|
||||
<img src={LinuxIcon} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text as="span" fontWeight={600} className="mr-8 textOverflow">
|
||||
{item.name}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text as="span" fontWeight={400} className="mr-8 textOverflow">
|
||||
{item.description}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ToggleButton
|
||||
className="toggle toggleButton"
|
||||
isChecked={isChecked}
|
||||
onChange={handleToggleEnabled}
|
||||
/>
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
</StyledWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({ oauthStore }) => {
|
||||
const { setCurrentClient, setEnabled } = oauthStore;
|
||||
|
||||
return { setEnabled, setCurrentClient };
|
||||
})(observer(Row));
|
@ -0,0 +1,195 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
//@ts-ignore
|
||||
import TableRow from "@docspace/components/table-container/TableRow";
|
||||
|
||||
//@ts-ignore
|
||||
import TableCell from "@docspace/components/table-container/TableCell";
|
||||
import Text from "@docspace/components/text";
|
||||
import ToggleButton from "@docspace/components/toggle-button";
|
||||
import { Base } from "@docspace/components/themes";
|
||||
|
||||
import SettingsIcon from "PUBLIC_DIR/images/catalog.settings.react.svg?url";
|
||||
import DeleteIcon from "PUBLIC_DIR/images/delete.react.svg?url";
|
||||
|
||||
import { ClientProps } from "@docspace/common/utils/oauth/interfaces";
|
||||
|
||||
import NameCell from "./columns/name";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: contents;
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
.table-container_cell {
|
||||
text-overflow: ellipsis;
|
||||
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.mr-8 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.textOverflow {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.toggleButton {
|
||||
display: contents;
|
||||
|
||||
input {
|
||||
position: relative;
|
||||
|
||||
margin-left: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-container_row-loader {
|
||||
margin-left: 8px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
:hover {
|
||||
.table-container_cell {
|
||||
cursor: pointer;
|
||||
background: ${(props) =>
|
||||
`${props.theme.filesSection.tableView.row.backgroundActive} !important`};
|
||||
|
||||
margin-top: -1px;
|
||||
|
||||
border-top: ${(props) =>
|
||||
`1px solid ${props.theme.filesSection.tableView.row.borderColor}`};
|
||||
}
|
||||
|
||||
.table-container_file-name-cell {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: -24px;
|
||||
padding-right: 24px;
|
||||
`
|
||||
: css`
|
||||
margin-left: -24px;
|
||||
padding-left: 24px;
|
||||
`}
|
||||
}
|
||||
.table-container_row-context-menu-wrapper {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-left: -20px;
|
||||
padding-left: 18px;
|
||||
`
|
||||
: css`
|
||||
margin-right: -20px;
|
||||
padding-right: 18px;
|
||||
`}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledTableRow.defaultProps = { theme: Base };
|
||||
|
||||
interface RowProps {
|
||||
item: ClientProps;
|
||||
isChecked: boolean;
|
||||
inProgress: boolean;
|
||||
setSelection?: (clientId: string) => void;
|
||||
changeClientStatus?: (clientId: string, status: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
const Row = (props: RowProps) => {
|
||||
const { item, changeClientStatus, isChecked, inProgress, setSelection } =
|
||||
props;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation(["Webhooks", "Common"]);
|
||||
|
||||
const editClient = () => {
|
||||
navigate(window.location.pathname + `/${item.clientId}`);
|
||||
};
|
||||
|
||||
const handleToggleEnabled = async () => {
|
||||
if (!changeClientStatus) return;
|
||||
await changeClientStatus(item.clientId, !item.enabled);
|
||||
};
|
||||
|
||||
const onDeleteOpen = () => {};
|
||||
|
||||
const handleRowClick = (e: any) => {
|
||||
if (
|
||||
e.target.closest(".checkbox") ||
|
||||
e.target.closest(".table-container_row-checkbox") ||
|
||||
e.target.closest(".type-combobox") ||
|
||||
e.target.closest(".table-container_row-context-menu-wrapper") ||
|
||||
e.target.closest(".toggleButton") ||
|
||||
e.detail === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
editClient();
|
||||
};
|
||||
|
||||
const contextOptions = [
|
||||
{
|
||||
key: "settings",
|
||||
label: t("Common:Settings"),
|
||||
icon: SettingsIcon,
|
||||
onClick: editClient,
|
||||
},
|
||||
{
|
||||
key: "Separator dropdownItem",
|
||||
isSeparator: true,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: DeleteIcon,
|
||||
onClick: onDeleteOpen,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledWrapper className="handle">
|
||||
<StyledTableRow
|
||||
contextOptions={contextOptions}
|
||||
onClick={handleRowClick}
|
||||
>
|
||||
<TableCell className={"table-container_file-name-cell"}>
|
||||
<NameCell
|
||||
name={item.name}
|
||||
icon={item.logoUrl}
|
||||
isChecked={isChecked}
|
||||
inProgress={inProgress}
|
||||
clientId={item.clientId}
|
||||
setSelection={setSelection}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{/* @ts-ignore */}
|
||||
<Text as="span" fontWeight={400} className="mr-8 textOverflow">
|
||||
{item.description}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ToggleButton
|
||||
className="toggle toggleButton"
|
||||
isChecked={item.enabled}
|
||||
onChange={handleToggleEnabled}
|
||||
/>
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
</StyledWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Row;
|
@ -0,0 +1,88 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import Text from "@docspace/components/text";
|
||||
import Checkbox from "@docspace/components/checkbox";
|
||||
//@ts-ignore
|
||||
import TableCell from "@docspace/components/table-container/TableCell";
|
||||
import Loader from "@docspace/components/loader";
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
.table-container_row-checkbox {
|
||||
margin-left: -8px;
|
||||
|
||||
width: 16px;
|
||||
|
||||
padding: 16px 8px 16px 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledImage = styled.img`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
`;
|
||||
|
||||
interface NameCellProps {
|
||||
name: string;
|
||||
icon?: string;
|
||||
clientId: string;
|
||||
inProgress?: boolean;
|
||||
isChecked?: boolean;
|
||||
setSelection?: (clientId: string) => void;
|
||||
}
|
||||
|
||||
const NameCell = ({
|
||||
name,
|
||||
icon,
|
||||
clientId,
|
||||
inProgress,
|
||||
isChecked,
|
||||
setSelection,
|
||||
}: NameCellProps) => {
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSelection && setSelection(clientId);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{inProgress ? (
|
||||
<Loader
|
||||
className="table-container_row-loader"
|
||||
type="oval"
|
||||
size="16px"
|
||||
/>
|
||||
) : (
|
||||
<TableCell
|
||||
className="table-container_element-wrapper"
|
||||
hasAccess={true}
|
||||
checked={isChecked}
|
||||
>
|
||||
<StyledContainer className="table-container_element-container">
|
||||
<div className="table-container_element">
|
||||
{icon && <StyledImage src={icon} alt={"App icon"} />}
|
||||
</div>
|
||||
<Checkbox
|
||||
className="table-container_row-checkbox"
|
||||
onChange={onChange}
|
||||
isChecked={isChecked}
|
||||
title={name}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
{/* @ts-ignore */}
|
||||
<Text
|
||||
type="page"
|
||||
title={name}
|
||||
fontWeight="600"
|
||||
fontSize="13px"
|
||||
isTextOverflow
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NameCell;
|
@ -1,116 +0,0 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { isMobile } from "react-device-detect";
|
||||
|
||||
import styled from "styled-components";
|
||||
|
||||
import TableContainer from "@docspace/components/table-container/TableContainer";
|
||||
import TableBody from "@docspace/components/table-container/TableBody";
|
||||
|
||||
import Row from "./Row";
|
||||
import Header from "./Header";
|
||||
|
||||
import { Base } from "@docspace/components/themes";
|
||||
|
||||
const TableWrapper = styled(TableContainer)`
|
||||
margin-top: 16px;
|
||||
|
||||
.header-container-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.table-container_header {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.table-list-item {
|
||||
margin-top: -1px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: ${(props) =>
|
||||
props.theme.isBase ? "#F8F9F9" : "#282828"};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
TableWrapper.defaultProps = { theme: Base };
|
||||
|
||||
const TABLE_VERSION = "1";
|
||||
const COLUMNS_SIZE = `oauthConfigColumnsSize_ver-${TABLE_VERSION}`;
|
||||
const INFO_PANEL_COLUMNS_SIZE = `infoPanelOauthConfigColumnsSize_ver-${TABLE_VERSION}`;
|
||||
|
||||
const TableView = (props) => {
|
||||
const {
|
||||
items,
|
||||
getClients,
|
||||
sectionWidth,
|
||||
viewAs,
|
||||
setViewAs,
|
||||
openSettingsModal,
|
||||
openDeleteModal,
|
||||
userId,
|
||||
} = props;
|
||||
|
||||
const tableRef = useRef(null);
|
||||
const [hideColumns, setHideColumns] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sectionWidth) return;
|
||||
if (sectionWidth < 1025 || isMobile) {
|
||||
viewAs !== "row" && setViewAs("row");
|
||||
} else {
|
||||
viewAs !== "table" && setViewAs("table");
|
||||
}
|
||||
}, [sectionWidth]);
|
||||
|
||||
const columnStorageName = `${COLUMNS_SIZE}=${userId}`;
|
||||
const columnInfoPanelStorageName = `${INFO_PANEL_COLUMNS_SIZE}=${userId}`;
|
||||
|
||||
return (
|
||||
<TableWrapper forwardedRef={tableRef} useReactWindow>
|
||||
<Header
|
||||
sectionWidth={sectionWidth}
|
||||
tableRef={tableRef}
|
||||
columnStorageName={columnStorageName}
|
||||
columnInfoPanelStorageName={columnInfoPanelStorageName}
|
||||
setHideColumns={setHideColumns}
|
||||
/>
|
||||
<TableBody
|
||||
itemHeight={49}
|
||||
useReactWindow
|
||||
infoPanelVisible={false}
|
||||
columnStorageName={columnStorageName}
|
||||
columnInfoPanelStorageName={columnInfoPanelStorageName}
|
||||
filesLength={items.length}
|
||||
fetchMoreFiles={getClients}
|
||||
hasMoreFiles={false}
|
||||
itemCount={items.length}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<Row
|
||||
key={item.id}
|
||||
item={item}
|
||||
index={index}
|
||||
openSettingsModal={openSettingsModal}
|
||||
openDeleteModal={openDeleteModal}
|
||||
hideColumns={hideColumns}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</TableWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({ oauthStore, setup, auth }) => {
|
||||
const { getClients } = oauthStore;
|
||||
|
||||
const { viewAs, setViewAs } = setup;
|
||||
const { id: userId } = auth.userStore.user;
|
||||
|
||||
return {
|
||||
viewAs,
|
||||
setViewAs,
|
||||
getClients,
|
||||
userId,
|
||||
};
|
||||
})(observer(TableView));
|
@ -0,0 +1,140 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { ClientProps } from "@docspace/common/utils/oauth/interfaces";
|
||||
|
||||
//@ts-ignore
|
||||
import TableContainer from "@docspace/components/table-container/TableContainer";
|
||||
//@ts-ignore
|
||||
import TableBody from "@docspace/components/table-container/TableBody";
|
||||
//@ts-ignore
|
||||
import { OAuthStoreProps, ViewAsType } from "SRC_DIR/store/OAuthStore";
|
||||
|
||||
import Row from "./Row";
|
||||
import Header from "./Header";
|
||||
|
||||
const TableWrapper = styled(TableContainer)`
|
||||
margin-top: 0px;
|
||||
|
||||
.header-container-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.table-container_header {
|
||||
position: absolute;
|
||||
}
|
||||
`;
|
||||
|
||||
const TABLE_VERSION = "1";
|
||||
const COLUMNS_SIZE = `oauthConfigColumnsSize_ver-${TABLE_VERSION}`;
|
||||
|
||||
interface TableViewProps {
|
||||
items: ClientProps[];
|
||||
sectionWidth: number;
|
||||
viewAs?: ViewAsType;
|
||||
setViewAs?: (value: ViewAsType) => void;
|
||||
userId?: string;
|
||||
selection?: string[];
|
||||
setSelection?: (clientId: string) => void;
|
||||
|
||||
bufferSelection?: ClientProps | null;
|
||||
currentClient?: ClientProps | null;
|
||||
setBufferSelection?: (clientId: string) => void;
|
||||
changeClientStatus?: (clientId: string, status: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
const TableView = ({
|
||||
items,
|
||||
|
||||
sectionWidth,
|
||||
|
||||
viewAs,
|
||||
setViewAs,
|
||||
|
||||
selection,
|
||||
bufferSelection,
|
||||
|
||||
setSelection,
|
||||
|
||||
changeClientStatus,
|
||||
|
||||
userId,
|
||||
}: TableViewProps) => {
|
||||
const tableRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!sectionWidth || !setViewAs) return;
|
||||
if (sectionWidth < 1025 || isMobile) {
|
||||
viewAs !== "row" && setViewAs("row");
|
||||
} else {
|
||||
viewAs !== "table" && setViewAs("table");
|
||||
}
|
||||
}, [sectionWidth, viewAs, setViewAs]);
|
||||
|
||||
const columnStorageName = `${COLUMNS_SIZE}=${userId}`;
|
||||
|
||||
return (
|
||||
<TableWrapper forwardedRef={tableRef} useReactWindow>
|
||||
<Header
|
||||
sectionWidth={sectionWidth}
|
||||
//@ts-ignore
|
||||
tableRef={tableRef}
|
||||
columnStorageName={columnStorageName}
|
||||
/>
|
||||
<TableBody
|
||||
itemHeight={49}
|
||||
useReactWindow
|
||||
columnStorageName={columnStorageName}
|
||||
filesLength={items.length}
|
||||
fetchMoreFiles={() => console.log("call")}
|
||||
hasMoreFiles={false}
|
||||
itemCount={items.length}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<Row
|
||||
key={item.clientId}
|
||||
item={item}
|
||||
isChecked={selection?.includes(item.clientId) || false}
|
||||
inProgress={false}
|
||||
setSelection={setSelection}
|
||||
changeClientStatus={changeClientStatus}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</TableWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(
|
||||
({ auth, oauthStore }: { auth: any; oauthStore: OAuthStoreProps }) => {
|
||||
const { id: userId } = auth.userStore.user;
|
||||
|
||||
const {
|
||||
viewAs,
|
||||
setViewAs,
|
||||
selection,
|
||||
bufferSelection,
|
||||
|
||||
setSelection,
|
||||
setBufferSelection,
|
||||
|
||||
changeClientStatus,
|
||||
|
||||
currentClient,
|
||||
} = oauthStore;
|
||||
return {
|
||||
viewAs,
|
||||
setViewAs,
|
||||
userId,
|
||||
changeClientStatus,
|
||||
selection,
|
||||
bufferSelection,
|
||||
|
||||
setSelection,
|
||||
setBufferSelection,
|
||||
currentClient,
|
||||
};
|
||||
}
|
||||
)(observer(TableView));
|
@ -1,30 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { Consumer } from "@docspace/components/utils/context";
|
||||
|
||||
import TableView from "./TableView";
|
||||
|
||||
const List = ({ viewAs, openSettingsModal, openDeleteModal, clients }) => {
|
||||
return (
|
||||
<Consumer>
|
||||
{(context) => (
|
||||
<TableView
|
||||
items={clients}
|
||||
sectionWidth={context.sectionWidth}
|
||||
openSettingsModal={openSettingsModal}
|
||||
openDeleteModal={openDeleteModal}
|
||||
/>
|
||||
)}
|
||||
</Consumer>
|
||||
);
|
||||
};
|
||||
export default inject(({ setup, oauthStore }) => {
|
||||
const { viewAs } = setup;
|
||||
const { clients } = oauthStore;
|
||||
|
||||
return {
|
||||
viewAs,
|
||||
clients,
|
||||
};
|
||||
})(observer(List));
|
@ -0,0 +1,94 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
//@ts-ignore
|
||||
import { ClientProps } from "@docspace/common/utils/oauth/interfaces";
|
||||
|
||||
import Text from "@docspace/components/text";
|
||||
//@ts-ignore
|
||||
import { Consumer } from "@docspace/components/utils/context";
|
||||
|
||||
//@ts-ignore
|
||||
import { ViewAsType } from "SRC_DIR/store/OAuthStore";
|
||||
|
||||
import TableView from "./TableView";
|
||||
import RegisterNewButton from "../RegisterNewButton";
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
button {
|
||||
width: fit-content;
|
||||
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface ListProps {
|
||||
t: any;
|
||||
clients: ClientProps[];
|
||||
viewAs?: ViewAsType;
|
||||
setViewAs?: (value: ViewAsType) => void;
|
||||
}
|
||||
|
||||
const List = ({ t, clients, viewAs, setViewAs }: ListProps) => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Text
|
||||
fontSize={"16px"}
|
||||
fontWeight={700}
|
||||
lineHeight={"22px"}
|
||||
title={"OAuth applications"}
|
||||
tag={""}
|
||||
as={"p"}
|
||||
color={""}
|
||||
textAlign={""}
|
||||
className="header"
|
||||
>
|
||||
{"OAuth applications"}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize={"12px"}
|
||||
fontWeight={400}
|
||||
lineHeight={"16px"}
|
||||
title={"OAuth description"}
|
||||
tag={""}
|
||||
as={"p"}
|
||||
color={""}
|
||||
textAlign={""}
|
||||
className="description"
|
||||
>
|
||||
{"OAuth description"}
|
||||
</Text>
|
||||
<RegisterNewButton t={t} />
|
||||
<Consumer>
|
||||
{(context: { sectionWidth: number; sectionHeight: number }) => (
|
||||
<>
|
||||
{viewAs === "table" ? (
|
||||
<TableView
|
||||
viewAs={viewAs}
|
||||
setViewAs={setViewAs}
|
||||
items={clients || []}
|
||||
sectionWidth={context.sectionWidth}
|
||||
/>
|
||||
) : (
|
||||
<div>row</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Consumer>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default List;
|
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isMobile } from "react-device-detect";
|
||||
|
||||
|
@ -1,108 +1,239 @@
|
||||
import { makeAutoObservable } from "mobx";
|
||||
|
||||
//@ts-ignore
|
||||
import { getPortal } from "@docspace/common/api/portal";
|
||||
|
||||
import {
|
||||
addClient,
|
||||
deleteClient,
|
||||
getClient,
|
||||
updateClient,
|
||||
changeClientStatus,
|
||||
regenerateSecret,
|
||||
deleteClient,
|
||||
getClientList,
|
||||
getScope,
|
||||
getScopeList,
|
||||
regenerateSecret,
|
||||
updateClient,
|
||||
} from "@docspace/common/api/oauth";
|
||||
|
||||
import {
|
||||
ClientDTO,
|
||||
ClientListDTO,
|
||||
ClientListProps,
|
||||
ClientProps,
|
||||
ScopeDTO,
|
||||
} from "@docspace/common/utils/oauth/dto";
|
||||
Scope,
|
||||
} from "@docspace/common/utils/oauth/interfaces";
|
||||
|
||||
const PAGE_LIMIT = 20;
|
||||
|
||||
export type ViewAsType = "table" | "row";
|
||||
|
||||
export interface OAuthStoreProps {
|
||||
viewAs: ViewAsType;
|
||||
|
||||
deleteDialogVisible: boolean;
|
||||
setDeleteDialogVisible: (value: boolean) => void;
|
||||
|
||||
clients: ClientProps[];
|
||||
currentClient: null | ClientProps;
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
|
||||
scopes: ScopeDTO[];
|
||||
selection: string[];
|
||||
setSelection: (clientId: string) => void;
|
||||
|
||||
fetchClient: (clientId: string) => Promise<ClientProps>;
|
||||
fetchClients: (page: number, limit: number) => Promise<void>;
|
||||
saveClient: (client: ClientProps) => Promise<ClientProps>;
|
||||
updateClient: (clientId: string, client: ClientProps) => Promise<ClientProps>;
|
||||
regenerateSecret: (clientId: string) => Promise<string>;
|
||||
bufferSelection: ClientProps | null;
|
||||
setBufferSelection: (clientId: string) => void;
|
||||
|
||||
tenant: number;
|
||||
fetchTenant: () => Promise<number>;
|
||||
|
||||
scopes: Scope[];
|
||||
|
||||
setViewAs: (value: "table" | "row") => void;
|
||||
|
||||
fetchClient: (clientId: string) => Promise<ClientProps | undefined>;
|
||||
fetchClients: (page: number) => Promise<void>;
|
||||
saveClient: (client: ClientProps) => Promise<void>;
|
||||
updateClient: (clientId: string, client: ClientProps) => Promise<void>;
|
||||
changeClientStatus: (clientId: string, status: boolean) => Promise<void>;
|
||||
regenerateSecret: (clientId: string) => Promise<string | undefined>;
|
||||
deleteClient: (clientId: string) => Promise<void>;
|
||||
|
||||
fetchScope: (name: string) => Promise<ScopeDTO>;
|
||||
fetchScope: (name: string) => Promise<Scope | undefined>;
|
||||
fetchScopes: () => Promise<void>;
|
||||
|
||||
clientList: ClientProps[];
|
||||
scopeList: ScopeDTO[];
|
||||
isEmptyClientList: boolean;
|
||||
hasNextPage: boolean;
|
||||
scopeList: Scope[];
|
||||
}
|
||||
|
||||
class OAuthStore implements OAuthStoreProps {
|
||||
clients: ClientProps[] = [];
|
||||
currentClient: ClientProps | null = null;
|
||||
viewAs: "table" | "row" = "table";
|
||||
|
||||
scopes: ScopeDTO[] = [];
|
||||
currentPage: number = 0;
|
||||
totalPages: number = 0;
|
||||
|
||||
deleteDialogVisible: boolean = false;
|
||||
|
||||
selection: string[] = [];
|
||||
|
||||
bufferSelection: ClientProps | null = null;
|
||||
|
||||
tenant: number = -1;
|
||||
|
||||
clients: ClientProps[] = [];
|
||||
|
||||
scopes: Scope[] = [];
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
setViewAs = (value: "table" | "row") => {
|
||||
this.viewAs = value;
|
||||
};
|
||||
|
||||
setDeleteDialogVisible = (value: boolean) => {
|
||||
this.deleteDialogVisible = value;
|
||||
};
|
||||
|
||||
setSelection = (clientId: string) => {
|
||||
if (this.selection.includes(clientId)) {
|
||||
this.selection = this.selection.filter((s) => s !== clientId);
|
||||
} else {
|
||||
this.selection.push(clientId);
|
||||
}
|
||||
};
|
||||
|
||||
setBufferSelection = (clientId: string) => {
|
||||
const client = this.clients.find((c) => c.clientId === clientId);
|
||||
|
||||
if (client) {
|
||||
this.bufferSelection = { ...client, scopes: [...client.scopes] };
|
||||
}
|
||||
};
|
||||
|
||||
fetchTenant = async () => {
|
||||
if (this.tenant > -1) return this.tenant;
|
||||
|
||||
const { tenant } = await getPortal();
|
||||
|
||||
this.tenant = tenant;
|
||||
return tenant;
|
||||
};
|
||||
|
||||
fetchClient = async (clientId: string) => {
|
||||
try {
|
||||
const client = await getClient(clientId);
|
||||
|
||||
return client;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
fetchClients = async (page: number, limit: number) => {
|
||||
const clientList: ClientListProps = await getClientList(0, 20);
|
||||
fetchClients = async (page: number) => {
|
||||
try {
|
||||
const clientList: ClientListProps = await getClientList(0, PAGE_LIMIT);
|
||||
|
||||
this.totalPages = clientList.totalPages;
|
||||
this.currentPage = page;
|
||||
this.clients = clientList.content;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
//TODO: add tenant and other params
|
||||
//TODO: OAuth, add tenant and other params
|
||||
saveClient = async (client: ClientProps) => {
|
||||
try {
|
||||
client.tenant = 1;
|
||||
client.authenticationMethod = "zxc";
|
||||
client.termsUrl = "zxc";
|
||||
const newClient = await addClient(client);
|
||||
|
||||
return newClient;
|
||||
this.clients.push(newClient);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
updateClient = async (clientId: string, client: ClientProps) => {
|
||||
try {
|
||||
const newClient = await updateClient(clientId, client);
|
||||
|
||||
return newClient;
|
||||
const idx = this.clients.findIndex((c) => c.clientId === clientId);
|
||||
|
||||
if (idx > -1) {
|
||||
this.clients[idx] = newClient;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
changeClientStatus = async (clientId: string, status: boolean) => {
|
||||
try {
|
||||
await changeClientStatus(clientId, status);
|
||||
|
||||
const idx = this.clients.findIndex((c) => c.clientId === clientId);
|
||||
|
||||
if (idx > -1) {
|
||||
this.clients[idx] = { ...this.clients[idx], enabled: status };
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
regenerateSecret = async (clientId: string) => {
|
||||
try {
|
||||
const secret = await regenerateSecret(clientId);
|
||||
|
||||
return secret;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
deleteClient = async (clientId: string) => {
|
||||
try {
|
||||
await deleteClient(clientId);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
fetchScope = async (name: string) => {
|
||||
try {
|
||||
const scope = await getScope(name);
|
||||
|
||||
return scope;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
fetchScopes = async () => {
|
||||
try {
|
||||
const scopes = await getScopeList();
|
||||
|
||||
this.scopes = scopes;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
get clientList() {
|
||||
return this.clients;
|
||||
}
|
||||
|
||||
get isEmptyClientList() {
|
||||
return this.clientList.length === 0;
|
||||
}
|
||||
|
||||
get hasNextPage() {
|
||||
return this.totalPages - this.currentPage !== 0;
|
||||
}
|
||||
|
||||
get scopeList() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
@ -2,22 +2,23 @@ import axios, { AxiosRequestConfig } from "axios";
|
||||
|
||||
import {
|
||||
transformToClientProps,
|
||||
transformToClientDTO,
|
||||
transformToClientReqDTO,
|
||||
} from "./../../utils/oauth/index";
|
||||
|
||||
import {
|
||||
ClientDTO,
|
||||
ClientListDTO,
|
||||
ClientListProps,
|
||||
ClientProps,
|
||||
ScopeDTO,
|
||||
} from "../../utils/oauth/dto";
|
||||
ClientResDTO,
|
||||
ClientListProps,
|
||||
ClientListDTO,
|
||||
Scope,
|
||||
} from "../../utils/oauth/interfaces";
|
||||
|
||||
const axiosConfig: AxiosRequestConfig = {
|
||||
baseURL: "/api",
|
||||
responseType: "json",
|
||||
timeout: 0,
|
||||
withCredentials: true,
|
||||
// TODO: OAuth, remove X-Tenant into API
|
||||
headers: { "X-API-Version": "1", "X-Tenant": "1" },
|
||||
};
|
||||
|
||||
@ -36,11 +37,14 @@ const request = (options: any): Promise<any> => {
|
||||
};
|
||||
|
||||
export const getClient = async (clientId: string): Promise<ClientProps> => {
|
||||
const client: ClientDTO = await request({
|
||||
const client: ClientResDTO = await request({
|
||||
method: "get",
|
||||
url: `/clients/${clientId}`,
|
||||
headers: {},
|
||||
});
|
||||
|
||||
client.enabled = true;
|
||||
|
||||
return transformToClientProps(client);
|
||||
};
|
||||
|
||||
@ -58,6 +62,9 @@ export const getClientList = async (
|
||||
data.content.forEach((item) => {
|
||||
const client = transformToClientProps(item);
|
||||
|
||||
// TODO: OAuth, get it from request
|
||||
client.enabled = true;
|
||||
|
||||
clients.content.push({ ...client });
|
||||
});
|
||||
|
||||
@ -65,12 +72,15 @@ export const getClientList = async (
|
||||
};
|
||||
|
||||
export const addClient = async (data: ClientProps): Promise<ClientProps> => {
|
||||
const client: ClientDTO = await request({
|
||||
const client: ClientResDTO = await request({
|
||||
method: "post",
|
||||
url: `/clients`,
|
||||
data: transformToClientDTO(data),
|
||||
data: transformToClientReqDTO(data),
|
||||
});
|
||||
|
||||
// TODO: OAuth, get it from request
|
||||
client.enabled = true;
|
||||
|
||||
return transformToClientProps(client);
|
||||
};
|
||||
|
||||
@ -78,15 +88,27 @@ export const updateClient = async (
|
||||
clientId: string,
|
||||
data: ClientProps
|
||||
): Promise<ClientProps> => {
|
||||
const client: ClientDTO = await request({
|
||||
const client: ClientResDTO = await request({
|
||||
method: "put",
|
||||
url: `/clients/${clientId}`,
|
||||
data: transformToClientDTO(data),
|
||||
data: transformToClientReqDTO(data),
|
||||
});
|
||||
|
||||
// TODO: OAuth, get it from request
|
||||
client.enabled = true;
|
||||
|
||||
return transformToClientProps(client);
|
||||
};
|
||||
|
||||
export const changeClientStatus = async (
|
||||
clientId: string,
|
||||
status: boolean
|
||||
): Promise<boolean> => {
|
||||
console.log(`Change client:${clientId} status to ${status}`);
|
||||
|
||||
return !status;
|
||||
};
|
||||
|
||||
export const regenerateSecret = async (clientId: string): Promise<string> => {
|
||||
const clientSecret: string = (
|
||||
await request({
|
||||
@ -105,8 +127,8 @@ export const deleteClient = async (clientId: string): Promise<void> => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getScope = async (name: string): Promise<ScopeDTO> => {
|
||||
const scope: ScopeDTO = await request({
|
||||
export const getScope = async (name: string): Promise<Scope> => {
|
||||
const scope: Scope = await request({
|
||||
method: "get",
|
||||
url: `/scopes/${name}`,
|
||||
});
|
||||
@ -114,8 +136,8 @@ export const getScope = async (name: string): Promise<ScopeDTO> => {
|
||||
return scope;
|
||||
};
|
||||
|
||||
export const getScopeList = async (): Promise<ScopeDTO[]> => {
|
||||
const scopeList: ScopeDTO[] = await request({
|
||||
export const getScopeList = async (): Promise<Scope[]> => {
|
||||
const scopeList: Scope[] = await request({
|
||||
method: "get",
|
||||
url: `/scopes`,
|
||||
});
|
||||
|
@ -1,10 +0,0 @@
|
||||
export const enum OauthScopes {
|
||||
ReadFiles = "files:read",
|
||||
WriteFiles = "files:write",
|
||||
ReadRooms = "rooms:read",
|
||||
WriteRooms = "rooms:write",
|
||||
ReadAccount = "account.self:read",
|
||||
WriteAccount = "account.self:write",
|
||||
ReadAccounts = "accounts:read",
|
||||
WriteAccounts = "accounts:write",
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import { ClientDTO, ClientProps } from "./dto";
|
||||
import { ClientResDTO, ClientReqDTO, ClientProps } from "./interfaces";
|
||||
|
||||
export const transformToClientProps = (clientDto: ClientDTO): ClientProps => {
|
||||
export const transformToClientProps = (
|
||||
clientDto: ClientResDTO
|
||||
): ClientProps => {
|
||||
const {
|
||||
client_id,
|
||||
client_secret,
|
||||
@ -8,13 +10,14 @@ export const transformToClientProps = (clientDto: ClientDTO): ClientProps => {
|
||||
terms_url,
|
||||
policy_url,
|
||||
logo_url,
|
||||
authenticationMethod,
|
||||
authentication_method,
|
||||
redirect_uri,
|
||||
logout_redirect_uri,
|
||||
scopes,
|
||||
tenant,
|
||||
invalidated,
|
||||
name,
|
||||
enabled,
|
||||
} = clientDto;
|
||||
|
||||
const client: ClientProps = {
|
||||
@ -24,22 +27,24 @@ export const transformToClientProps = (clientDto: ClientDTO): ClientProps => {
|
||||
termsUrl: terms_url,
|
||||
policyUrl: policy_url,
|
||||
logoUrl: logo_url,
|
||||
authenticationMethod,
|
||||
authenticationMethod: authentication_method,
|
||||
redirectUri: redirect_uri,
|
||||
logoutRedirectUri: logout_redirect_uri,
|
||||
scopes,
|
||||
tenant,
|
||||
invalidated,
|
||||
name,
|
||||
enabled,
|
||||
};
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
export const transformToClientDTO = (clientProps: ClientProps): ClientDTO => {
|
||||
export const transformToClientReqDTO = (
|
||||
clientProps: ClientProps
|
||||
): ClientReqDTO => {
|
||||
const {
|
||||
clientId: client_id,
|
||||
secret: client_secret,
|
||||
name,
|
||||
description,
|
||||
termsUrl: terms_url,
|
||||
policyUrl: policy_url,
|
||||
@ -49,24 +54,21 @@ export const transformToClientDTO = (clientProps: ClientProps): ClientDTO => {
|
||||
logoutRedirectUri: logout_redirect_uri,
|
||||
scopes,
|
||||
tenant,
|
||||
invalidated,
|
||||
name,
|
||||
} = clientProps;
|
||||
|
||||
const client: ClientDTO = {
|
||||
client_id,
|
||||
client_secret,
|
||||
const client: ClientReqDTO = {
|
||||
name,
|
||||
description,
|
||||
terms_url,
|
||||
policy_url,
|
||||
logo_url,
|
||||
authenticationMethod,
|
||||
|
||||
redirect_uri,
|
||||
logout_redirect_uri,
|
||||
terms_url,
|
||||
policy_url,
|
||||
|
||||
scopes,
|
||||
|
||||
tenant,
|
||||
invalidated,
|
||||
name,
|
||||
};
|
||||
|
||||
return client;
|
||||
|
@ -1,4 +1,4 @@
|
||||
export type ScopeDTO = {
|
||||
export type Scope = {
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
@ -6,34 +6,61 @@ export type ScopeDTO = {
|
||||
export interface ClientProps {
|
||||
clientId: string;
|
||||
secret: string;
|
||||
description: string;
|
||||
termsUrl?: string;
|
||||
policyUrl: string;
|
||||
logoUrl: string;
|
||||
authenticationMethod?: string;
|
||||
redirectUri: string;
|
||||
logoutRedirectUri: string;
|
||||
scopes: string[];
|
||||
tenant?: number;
|
||||
invalidated?: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type ClientDTO = {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
name: string;
|
||||
description: string;
|
||||
terms_url: string;
|
||||
policy_url: string;
|
||||
logo_url: string;
|
||||
logoUrl?: string;
|
||||
|
||||
redirectUri: string;
|
||||
policyUrl: string;
|
||||
termsUrl: string;
|
||||
logoutRedirectUri: string;
|
||||
|
||||
authenticationMethod: string;
|
||||
redirect_uri: string;
|
||||
logout_redirect_uri: string;
|
||||
|
||||
scopes: string[];
|
||||
|
||||
enabled: boolean;
|
||||
tenant: number;
|
||||
invalidated?: boolean;
|
||||
}
|
||||
|
||||
export interface ClientReqDTO {
|
||||
name: string;
|
||||
};
|
||||
description: string;
|
||||
logo_url?: string;
|
||||
|
||||
redirect_uri: string;
|
||||
policy_url: string;
|
||||
terms_url: string;
|
||||
logout_redirect_uri: string;
|
||||
|
||||
scopes: string[];
|
||||
|
||||
tenant: number;
|
||||
}
|
||||
|
||||
export interface ClientResDTO {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
|
||||
name: string;
|
||||
description: string;
|
||||
logo_url?: string;
|
||||
|
||||
redirect_uri: string;
|
||||
terms_url: string;
|
||||
policy_url: string;
|
||||
logout_redirect_uri: string;
|
||||
|
||||
authentication_method: string;
|
||||
|
||||
scopes: string[];
|
||||
|
||||
enabled: boolean;
|
||||
tenant: number;
|
||||
invalidated?: boolean;
|
||||
}
|
||||
|
||||
export interface ClientListProps {
|
||||
content: ClientProps[];
|
||||
@ -65,7 +92,7 @@ export interface ClientListProps {
|
||||
}
|
||||
|
||||
export type ClientListDTO = {
|
||||
content: ClientDTO[];
|
||||
content: ClientResDTO[];
|
||||
empty: boolean;
|
||||
first: boolean;
|
||||
last: true;
|
@ -127,8 +127,7 @@ class InputBlock extends React.Component {
|
||||
forwardedRef={forwardedRef}
|
||||
{...props}
|
||||
/>
|
||||
{
|
||||
//iconNames.includes(iconName) && (
|
||||
{iconName && (
|
||||
<div className="append">
|
||||
<StyledIconBlock
|
||||
className={`input-block-icon ${iconButtonClassName}`}
|
||||
@ -148,7 +147,7 @@ class InputBlock extends React.Component {
|
||||
/>
|
||||
</StyledIconBlock>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</StyledInputGroup>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user