mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 16923cbe1b | |||
| 7a21d57840 | |||
| 578d9fe8d2 | |||
| 3ba1b55fc9 | |||
| 9dcd087c82 | |||
| a56ab96210 |
@@ -11,7 +11,7 @@ export default function SearchActions() {
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!searches.isLoaded) {
|
||||
void searches.fetchPage({});
|
||||
void searches.fetchPage();
|
||||
}
|
||||
}, [searches]);
|
||||
|
||||
|
||||
@@ -16,10 +16,12 @@ type RequestResponse<T> = {
|
||||
* A hook to make an API request and track its state within a component.
|
||||
*
|
||||
* @param requestFn The function to call to make the request, it should return a promise.
|
||||
* @param makeRequestOnMount Whether to make the request when the component mounts.
|
||||
* @returns
|
||||
*/
|
||||
export default function useRequest<T = unknown>(
|
||||
requestFn: () => Promise<T>
|
||||
requestFn: () => Promise<T>,
|
||||
makeRequestOnMount = false
|
||||
): RequestResponse<T> {
|
||||
const isMounted = useIsMounted();
|
||||
const [data, setData] = React.useState<T>();
|
||||
@@ -48,5 +50,11 @@ export default function useRequest<T = unknown>(
|
||||
return undefined;
|
||||
}, [requestFn, isMounted]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (makeRequestOnMount) {
|
||||
void request();
|
||||
}
|
||||
}, [request, makeRequestOnMount]);
|
||||
|
||||
return { data, loading, error, request };
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
SettingsIcon,
|
||||
ExportIcon,
|
||||
ImportIcon,
|
||||
GlobeIcon,
|
||||
} from "outline-icons";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -23,6 +24,7 @@ import Features from "~/scenes/Settings/Features";
|
||||
import GoogleAnalytics from "~/scenes/Settings/GoogleAnalytics";
|
||||
import Groups from "~/scenes/Settings/Groups";
|
||||
import Import from "~/scenes/Settings/Import";
|
||||
import LinkedAccounts from "~/scenes/Settings/LinkedAccounts";
|
||||
import Members from "~/scenes/Settings/Members";
|
||||
import Notifications from "~/scenes/Settings/Notifications";
|
||||
import Preferences from "~/scenes/Settings/Preferences";
|
||||
@@ -79,6 +81,14 @@ const useSettingsConfig = () => {
|
||||
group: t("Account"),
|
||||
icon: EmailIcon,
|
||||
},
|
||||
{
|
||||
name: t("Linked Accounts"),
|
||||
path: settingsPath("linked-accounts"),
|
||||
component: LinkedAccounts,
|
||||
enabled: true,
|
||||
group: t("Account"),
|
||||
icon: LinkIcon,
|
||||
},
|
||||
{
|
||||
name: t("API Tokens"),
|
||||
path: settingsPath("tokens"),
|
||||
@@ -134,7 +144,7 @@ const useSettingsConfig = () => {
|
||||
component: Shares,
|
||||
enabled: true,
|
||||
group: t("Workspace"),
|
||||
icon: LinkIcon,
|
||||
icon: GlobeIcon,
|
||||
},
|
||||
{
|
||||
name: t("Import"),
|
||||
|
||||
@@ -18,7 +18,7 @@ function RecentSearches() {
|
||||
const [isPreloaded] = React.useState(searches.recent.length > 0);
|
||||
|
||||
React.useEffect(() => {
|
||||
void searches.fetchPage({});
|
||||
void searches.fetchPage();
|
||||
}, [searches]);
|
||||
|
||||
const content = searches.recent.length ? (
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
import { partition } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
import { LinkIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { setCookie } from "tiny-cookie";
|
||||
import SlackLogo from "~/components/AuthLogo/SlackLogo";
|
||||
import Button from "~/components/Button";
|
||||
import Heading from "~/components/Heading";
|
||||
import GoogleIcon from "~/components/Icons/GoogleIcon";
|
||||
import List from "~/components/List";
|
||||
import PlaceholderList from "~/components/List/Placeholder";
|
||||
import Scene from "~/components/Scene";
|
||||
import Text from "~/components/Text";
|
||||
import env from "~/env";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useRequest from "~/hooks/useRequest";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import DisconnectAccountDialog from "./components/DisconnectAccountDialog";
|
||||
import Integration from "./components/Integration";
|
||||
|
||||
function LinkedAccounts() {
|
||||
const { t } = useTranslation();
|
||||
const { integrations, authenticationProviders } = useStores();
|
||||
|
||||
const { loading: loadingAuthenticationProviders } = useRequest(
|
||||
authenticationProviders.fetchPage,
|
||||
true
|
||||
);
|
||||
const { loading: loadingIntegrations } = useRequest(
|
||||
integrations.fetchPage,
|
||||
true
|
||||
);
|
||||
|
||||
const accounts = partition(
|
||||
[
|
||||
{
|
||||
isEnabled: authenticationProviders.getByName("google")?.isEnabled,
|
||||
isActive: authenticationProviders.getByName("google")?.isActive,
|
||||
Component: GoogleAuthAccount,
|
||||
},
|
||||
{
|
||||
isEnabled: authenticationProviders.getByName("slack")?.isEnabled,
|
||||
isActive: authenticationProviders.getByName("slack")?.isActive,
|
||||
Component: SlackAuthAccount,
|
||||
},
|
||||
],
|
||||
"isActive"
|
||||
);
|
||||
|
||||
const appName = env.APP_NAME;
|
||||
const loading = loadingAuthenticationProviders || loadingIntegrations;
|
||||
const isEmpty =
|
||||
authenticationProviders.orderedData.length === 0 &&
|
||||
integrations.orderedData.length === 0;
|
||||
const activeIntegrations = accounts[0].filter((account) => account.isEnabled);
|
||||
const inactiveIntegrations = accounts[1].filter(
|
||||
(account) => account.isEnabled
|
||||
);
|
||||
|
||||
return (
|
||||
<Scene title={t("Linked Accounts")} icon={<LinkIcon />}>
|
||||
<Heading>{t("Linked Accounts")}</Heading>
|
||||
<Text type="secondary">
|
||||
<Trans>
|
||||
Manage the third-party services that are connected to {{ appName }}.
|
||||
</Trans>
|
||||
</Text>
|
||||
{loading && isEmpty ? (
|
||||
<PlaceholderList count={5} />
|
||||
) : (
|
||||
<>
|
||||
{activeIntegrations.length > 0 && (
|
||||
<List>
|
||||
<Heading as="h2">{t("Connected")}</Heading>
|
||||
{activeIntegrations.map(({ Component }, index) => (
|
||||
<Component key={index} />
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
{inactiveIntegrations.length > 0 && (
|
||||
<List>
|
||||
<Heading as="h2">{t("Available")}</Heading>
|
||||
{inactiveIntegrations.map(({ Component }, index) => (
|
||||
<Component key={index} />
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
function GoogleAuthAccount() {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const { dialogs, authenticationProviders } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
const integration = authenticationProviders.getByName("google");
|
||||
const isActive = integration?.isActive ?? false;
|
||||
|
||||
const connect = () => {
|
||||
setCookie("postLoginRedirectPath", location.pathname);
|
||||
window.location.href = "/auth/google";
|
||||
};
|
||||
|
||||
const disconnect = () => {
|
||||
dialogs.openModal({
|
||||
title: t("Disconnect account"),
|
||||
isCentered: true,
|
||||
content: (
|
||||
<DisconnectAccountDialog
|
||||
title="Google"
|
||||
isAuthentication
|
||||
onSubmit={() => {
|
||||
void client.post("/userAuthentications.delete", {
|
||||
authenticationProviderId: integration?.id,
|
||||
});
|
||||
dialogs.closeAllModals();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Integration
|
||||
title="Google"
|
||||
subtitle={
|
||||
isActive
|
||||
? "Your Google account is connected and can be used for sign-in"
|
||||
: "Connect with Google"
|
||||
}
|
||||
icon={<GoogleIcon />}
|
||||
actions={
|
||||
isActive ? (
|
||||
<Button neutral onClick={disconnect} disabled={!team.guestSignin}>
|
||||
{t("Disconnect")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={connect}>{t("Connect")}</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SlackAuthAccount() {
|
||||
const { t } = useTranslation();
|
||||
const { dialogs, authenticationProviders } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
const integration = authenticationProviders.getByName("slack");
|
||||
const isActive = integration?.isActive ?? false;
|
||||
|
||||
const disconnect = () => {
|
||||
dialogs.openModal({
|
||||
title: t("Disconnect account"),
|
||||
isCentered: true,
|
||||
content: (
|
||||
<DisconnectAccountDialog
|
||||
title="Slack"
|
||||
isAuthentication
|
||||
onSubmit={() => {
|
||||
void client.post("/userAuthentications.delete", {
|
||||
authenticationProviderId: integration?.id,
|
||||
});
|
||||
dialogs.closeAllModals();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Integration
|
||||
title="Slack"
|
||||
subtitle={
|
||||
isActive
|
||||
? "Your Slack account is connected and can be used for sign-in"
|
||||
: "Connect with Slack"
|
||||
}
|
||||
icon={<SlackLogo size={16} fill="currentColor" />}
|
||||
actions={
|
||||
isActive ? (
|
||||
<Button neutral onClick={disconnect} disabled={!team.guestSignin}>
|
||||
{t("Disconnect")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button as="a" href="/auth/slack">
|
||||
{t("Connect")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(LinkedAccounts);
|
||||
@@ -42,7 +42,7 @@ function Security() {
|
||||
data: providers,
|
||||
loading,
|
||||
request,
|
||||
} = useRequest(() => authenticationProviders.fetchPage({}));
|
||||
} = useRequest(authenticationProviders.fetchPage);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!providers && !loading) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { sortBy } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
import { LinkIcon, WarningIcon } from "outline-icons";
|
||||
import { GlobeIcon, WarningIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -67,7 +67,7 @@ function Shares() {
|
||||
}, [shares.orderedData, shareIds]);
|
||||
|
||||
return (
|
||||
<Scene title={t("Shared Links")} icon={<LinkIcon />}>
|
||||
<Scene title={t("Shared Links")} icon={<GlobeIcon />}>
|
||||
<Heading>{t("Shared Links")}</Heading>
|
||||
|
||||
{can.manage && !canShareDocuments && (
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import ConfirmationDialog from "~/components/ConfirmationDialog";
|
||||
|
||||
type Props = React.ComponentProps<typeof ConfirmationDialog> & {
|
||||
title: string;
|
||||
isAuthentication?: boolean;
|
||||
};
|
||||
|
||||
function DisconnectAccountDialog({
|
||||
title,
|
||||
isAuthentication,
|
||||
onSubmit,
|
||||
...props
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ConfirmationDialog
|
||||
onSubmit={onSubmit}
|
||||
submitText={t("Confirm")}
|
||||
savingText={`${t("Disconnecting")}…`}
|
||||
{...props}
|
||||
>
|
||||
<Trans>
|
||||
Are you sure you want to disconnect your account from {{ title }}?
|
||||
</Trans>{" "}
|
||||
{isAuthentication ? (
|
||||
<Trans>You will no longer be able to sign in with this account.</Trans>
|
||||
) : (
|
||||
<Trans>Associated functionality will be disabled.</Trans>
|
||||
)}
|
||||
</ConfirmationDialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default DisconnectAccountDialog;
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Flex from "~/components/Flex";
|
||||
import Item from "~/components/List/Item";
|
||||
|
||||
const Integration = ({
|
||||
icon,
|
||||
...props
|
||||
}: Omit<React.ComponentProps<typeof Item>, "image"> & {
|
||||
icon: React.ReactNode;
|
||||
}) => <Item image={<IconBackground>{icon}</IconBackground>} {...props} />;
|
||||
|
||||
const IconBackground = styled(Flex)`
|
||||
background: ${(props) => props.theme.secondaryBackground};
|
||||
border-radius: 4px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export default Integration;
|
||||
@@ -8,4 +8,12 @@ export default class AuthenticationProvidersStore extends BaseStore<Authenticati
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, AuthenticationProvider);
|
||||
}
|
||||
|
||||
getAllByName(name: string) {
|
||||
return this.orderedData.filter((provider) => provider.name === name);
|
||||
}
|
||||
|
||||
getByName(name: string) {
|
||||
return this.orderedData.find((provider) => provider.name === name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ export default abstract class BaseStore<T extends BaseModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
fetchPage = async (params: FetchPageParams | undefined): Promise<T[]> => {
|
||||
fetchPage = async (params?: FetchPageParams | undefined): Promise<T[]> => {
|
||||
if (!this.actions.includes(RPCAction.List)) {
|
||||
throw new Error(`Cannot list ${this.modelName}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up (queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn("authentications", "refreshToken", {
|
||||
type: Sequelize.BLOB,
|
||||
allowNull: true,
|
||||
});
|
||||
},
|
||||
|
||||
async down (queryInterface) {
|
||||
await queryInterface.removeColumn("authentications", "refreshToken");
|
||||
}
|
||||
};
|
||||
@@ -65,7 +65,7 @@ class AuthenticationProvider extends Model {
|
||||
@Column(DataType.UUID)
|
||||
teamId: string;
|
||||
|
||||
@HasMany(() => UserAuthentication, "providerId")
|
||||
@HasMany(() => UserAuthentication, "authenticationProviderId")
|
||||
userAuthentications: UserAuthentication[];
|
||||
|
||||
// instance methods
|
||||
|
||||
@@ -34,6 +34,16 @@ class IntegrationAuthentication extends IdModel {
|
||||
setEncryptedColumn(this, "token", value);
|
||||
}
|
||||
|
||||
@Column(DataType.BLOB)
|
||||
@Encrypted
|
||||
get refreshToken() {
|
||||
return getEncryptedColumn(this, "refreshToken");
|
||||
}
|
||||
|
||||
set refreshToken(value: string) {
|
||||
setEncryptedColumn(this, "refreshToken", value);
|
||||
}
|
||||
|
||||
// associations
|
||||
|
||||
@BelongsTo(() => User, "userId")
|
||||
|
||||
@@ -8,6 +8,6 @@ export default function presentAuthenticationProvider(
|
||||
name: authenticationProvider.name,
|
||||
createdAt: authenticationProvider.createdAt,
|
||||
isEnabled: authenticationProvider.enabled,
|
||||
isConnected: true,
|
||||
isConnected: authenticationProvider.userAuthentications.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ import Router from "koa-router";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { transaction } from "@server/middlewares/transaction";
|
||||
import validate from "@server/middlewares/validate";
|
||||
import { AuthenticationProvider, Event } from "@server/models";
|
||||
import {
|
||||
AuthenticationProvider,
|
||||
Event,
|
||||
UserAuthentication,
|
||||
} from "@server/models";
|
||||
import AuthenticationHelper from "@server/models/helpers/AuthenticationHelper";
|
||||
import { authorize } from "@server/policies";
|
||||
import {
|
||||
@@ -16,7 +20,7 @@ const router = new Router();
|
||||
|
||||
router.post(
|
||||
"authenticationProviders.info",
|
||||
auth({ admin: true }),
|
||||
auth(),
|
||||
validate(T.AuthenticationProvidersInfoSchema),
|
||||
async (ctx: APIContext<T.AuthenticationProvidersInfoReq>) => {
|
||||
const { id } = ctx.input.body;
|
||||
@@ -77,37 +81,44 @@ router.post(
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
"authenticationProviders.list",
|
||||
auth({ admin: true }),
|
||||
async (ctx: APIContext) => {
|
||||
const { user } = ctx.state.auth;
|
||||
authorize(user, "read", user.team);
|
||||
router.post("authenticationProviders.list", auth(), async (ctx: APIContext) => {
|
||||
const { user } = ctx.state.auth;
|
||||
authorize(user, "read", user.team);
|
||||
|
||||
const teamAuthenticationProviders = (await user.team.$get(
|
||||
"authenticationProviders"
|
||||
)) as AuthenticationProvider[];
|
||||
const teamAuthenticationProviders = await AuthenticationProvider.findAll({
|
||||
where: {
|
||||
teamId: user.teamId,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: UserAuthentication,
|
||||
required: false,
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const data = AuthenticationHelper.providers
|
||||
.filter((p) => p.id !== "email")
|
||||
.map((p) => {
|
||||
const row = teamAuthenticationProviders.find((t) => t.name === p.id);
|
||||
const data = AuthenticationHelper.providers
|
||||
.filter((p) => p.id !== "email")
|
||||
.map((p) => {
|
||||
const row = teamAuthenticationProviders.find((t) => t.name === p.id);
|
||||
|
||||
return {
|
||||
id: p.id,
|
||||
name: p.id,
|
||||
displayName: p.name,
|
||||
isEnabled: false,
|
||||
isConnected: false,
|
||||
...(row ? presentAuthenticationProvider(row) : {}),
|
||||
};
|
||||
})
|
||||
.sort((a) => (a.isEnabled ? -1 : 1));
|
||||
return {
|
||||
id: p.id,
|
||||
name: p.id,
|
||||
displayName: p.name,
|
||||
isEnabled: false,
|
||||
isConnected: false,
|
||||
...(row ? presentAuthenticationProvider(row) : {}),
|
||||
};
|
||||
})
|
||||
.sort((a) => (a.isEnabled ? -1 : 1));
|
||||
|
||||
ctx.body = {
|
||||
data,
|
||||
};
|
||||
}
|
||||
);
|
||||
ctx.body = {
|
||||
data,
|
||||
};
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -33,6 +33,7 @@ import stars from "./stars";
|
||||
import subscriptions from "./subscriptions";
|
||||
import teams from "./teams";
|
||||
import urls from "./urls";
|
||||
import userAuthentications from "./userAuthentications";
|
||||
import users from "./users";
|
||||
import views from "./views";
|
||||
|
||||
@@ -68,7 +69,6 @@ glob
|
||||
router.use("/", auth.routes());
|
||||
router.use("/", authenticationProviders.routes());
|
||||
router.use("/", events.routes());
|
||||
router.use("/", users.routes());
|
||||
router.use("/", collections.routes());
|
||||
router.use("/", comments.routes());
|
||||
router.use("/", documents.routes());
|
||||
@@ -88,6 +88,8 @@ router.use("/", cron.routes());
|
||||
router.use("/", groups.routes());
|
||||
router.use("/", fileOperationsRoute.routes());
|
||||
router.use("/", urls.routes());
|
||||
router.use("/", userAuthentications.routes());
|
||||
router.use("/", users.routes());
|
||||
|
||||
if (env.ENVIRONMENT === "development") {
|
||||
router.use("/", developer.routes());
|
||||
|
||||
@@ -21,7 +21,7 @@ export const IntegrationsListSchema = BaseSchema.extend({
|
||||
.default("updatedAt"),
|
||||
|
||||
/** Integration type */
|
||||
type: z.nativeEnum(IntegrationType),
|
||||
type: z.nativeEnum(IntegrationType).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./userAuthentications";
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from "zod";
|
||||
import BaseSchema from "../BaseSchema";
|
||||
|
||||
export const UserAuthenticationsDeleteSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
/** Associated provider id of user authentication to be deleted */
|
||||
authenticationProviderId: z.string().uuid(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type UserAuthenticationsDeleteReq = z.infer<
|
||||
typeof UserAuthenticationsDeleteSchema
|
||||
>;
|
||||
@@ -0,0 +1,35 @@
|
||||
import Router from "koa-router";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { transaction } from "@server/middlewares/transaction";
|
||||
import validate from "@server/middlewares/validate";
|
||||
import { UserAuthentication } from "@server/models";
|
||||
import { APIContext } from "@server/types";
|
||||
import * as T from "./schema";
|
||||
|
||||
const router = new Router();
|
||||
|
||||
router.post(
|
||||
"userAuthentications.delete",
|
||||
auth(),
|
||||
validate(T.UserAuthenticationsDeleteSchema),
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.UserAuthenticationsDeleteReq>) => {
|
||||
const { authenticationProviderId } = ctx.input.body;
|
||||
const { user } = ctx.state.auth;
|
||||
const { transaction } = ctx.state;
|
||||
|
||||
await UserAuthentication.destroy({
|
||||
where: {
|
||||
userId: user.id,
|
||||
authenticationProviderId,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -333,6 +333,7 @@
|
||||
"Outdent": "Outdent",
|
||||
"Could not import file": "Could not import file",
|
||||
"Account": "Account",
|
||||
"Linked Accounts": "Linked Accounts",
|
||||
"API Tokens": "API Tokens",
|
||||
"Details": "Details",
|
||||
"Security": "Security",
|
||||
@@ -708,6 +709,10 @@
|
||||
"Copied": "Copied",
|
||||
"Revoking": "Revoking",
|
||||
"Are you sure you want to revoke the {{ tokenName }} token?": "Are you sure you want to revoke the {{ tokenName }} token?",
|
||||
"Disconnecting": "Disconnecting",
|
||||
"Are you sure you want to disconnect your account from {{title}}?": "Are you sure you want to disconnect your account from {{title}}?",
|
||||
"You will no longer be able to sign in with this account.": "You will no longer be able to sign in with this account.",
|
||||
"Associated functionality will be disabled.": "Associated functionality will be disabled.",
|
||||
"Allowed domains": "Allowed domains",
|
||||
"The domains which should be allowed to create new accounts using SSO. Changing this setting does not affect existing user accounts.": "The domains which should be allowed to create new accounts using SSO. Changing this setting does not affect existing user accounts.",
|
||||
"Remove domain": "Remove domain",
|
||||
@@ -784,6 +789,12 @@
|
||||
"Import pages from a Confluence instance": "Import pages from a Confluence instance",
|
||||
"Enterprise": "Enterprise",
|
||||
"Recent imports": "Recent imports",
|
||||
"Manage the third-party services that are connected to {{appName}}.": "Manage the third-party services that are connected to {{appName}}.",
|
||||
"Connected": "Connected",
|
||||
"Available": "Available",
|
||||
"Disconnect account": "Disconnect account",
|
||||
"Disconnect": "Disconnect",
|
||||
"Connect": "Connect",
|
||||
"Everyone that has signed into {{appName}} is listed here. It’s possible that there are other users who have access through {team.signinMethods} but haven’t signed in yet.": "Everyone that has signed into {{appName}} is listed here. It’s possible that there are other users who have access through {team.signinMethods} but haven’t signed in yet.",
|
||||
"Filter": "Filter",
|
||||
"Receive a notification whenever a new document is published": "Receive a notification whenever a new document is published",
|
||||
@@ -833,7 +844,6 @@
|
||||
"New users will first need to be invited to create an account. <em>Default role</em> and <em>Allowed domains</em> will no longer apply.": "New users will first need to be invited to create an account. <em>Default role</em> and <em>Allowed domains</em> will no longer apply.",
|
||||
"Settings that impact the access, security, and content of your knowledge base.": "Settings that impact the access, security, and content of your knowledge base.",
|
||||
"Allow members to sign-in with {{ authProvider }}": "Allow members to sign-in with {{ authProvider }}",
|
||||
"Connected": "Connected",
|
||||
"Disabled": "Disabled",
|
||||
"Allow members to sign-in using their email address": "Allow members to sign-in using their email address",
|
||||
"The server must have SMTP configured to enable this setting": "The server must have SMTP configured to enable this setting",
|
||||
@@ -877,12 +887,10 @@
|
||||
"document updated": "document updated",
|
||||
"Posting to the <em>{{ channelName }}</em> channel on": "Posting to the <em>{{ channelName }}</em> channel on",
|
||||
"These events should be posted to Slack": "These events should be posted to Slack",
|
||||
"Disconnect": "Disconnect",
|
||||
"Whoops, you need to accept the permissions in Slack to connect {{appName}} to your team. Try again?": "Whoops, you need to accept the permissions in Slack to connect {{appName}} to your team. Try again?",
|
||||
"Something went wrong while authenticating your request. Please try logging in again?": "Something went wrong while authenticating your request. Please try logging in again?",
|
||||
"Get rich previews of {{ appName }} links shared in Slack and use the <em>{{ command }}</em> slash command to search for documents without leaving your chat.": "Get rich previews of {{ appName }} links shared in Slack and use the <em>{{ command }}</em> slash command to search for documents without leaving your chat.",
|
||||
"Connect {{appName}} collections to Slack channels. Messages will be automatically posted to Slack when documents are published or updated.": "Connect {{appName}} collections to Slack channels. Messages will be automatically posted to Slack when documents are published or updated.",
|
||||
"Connect": "Connect",
|
||||
"The Slack integration is currently disabled. Please set the associated environment variables and restart the server to enable the integration.": "The Slack integration is currently disabled. Please set the associated environment variables and restart the server to enable the integration.",
|
||||
"How to use {{ command }}": "How to use {{ command }}",
|
||||
"To search your knowledgebase use {{ command }}. \nYou’ve already learned how to get help with {{ command2 }}.": "To search your knowledgebase use {{ command }}. \nYou’ve already learned how to get help with {{ command2 }}.",
|
||||
|
||||
Reference in New Issue
Block a user