mirror of
https://github.com/outline/outline.git
synced 2026-06-13 19:35:02 +03:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4f92fc4d7 | |||
| d764c8a302 | |||
| 611b584ca4 | |||
| c221b96a74 |
@@ -0,0 +1,111 @@
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMenuState } from "reakit/Menu";
|
||||
import WebhookSubscription from "~/models/WebhookSubscription";
|
||||
import WebhookDeliveries from "~/scenes/Settings/components/WebhookDeliveries";
|
||||
import WebhookSubscriptionRevokeDialog from "~/scenes/Settings/components/WebhookSubscriptionDeleteDialog";
|
||||
import WebhookSubscriptionEdit from "~/scenes/Settings/components/WebhookSubscriptionEdit";
|
||||
import ContextMenu from "~/components/ContextMenu";
|
||||
import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton";
|
||||
import Template from "~/components/ContextMenu/Template";
|
||||
import Modal from "~/components/Modal";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useStores from "~/hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
webhook: WebhookSubscription;
|
||||
};
|
||||
|
||||
function WebhookMenu({ webhook }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const menu = useMenuState();
|
||||
|
||||
const { dialogs } = useStores();
|
||||
const showDeletionConfirmation = React.useCallback(() => {
|
||||
dialogs.openModal({
|
||||
title: t("Delete webhook"),
|
||||
isCentered: true,
|
||||
content: (
|
||||
<WebhookSubscriptionRevokeDialog
|
||||
onSubmit={dialogs.closeAllModals}
|
||||
webhook={webhook}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, [t, dialogs, webhook]);
|
||||
|
||||
const [
|
||||
editModalOpen,
|
||||
handleEditModalOpen,
|
||||
handleEditModalClose,
|
||||
] = useBoolean();
|
||||
|
||||
const renderEditModal = React.useCallback(
|
||||
() => (
|
||||
<Modal
|
||||
title={t("Edit webhook")}
|
||||
onRequestClose={handleEditModalClose}
|
||||
isOpen={editModalOpen}
|
||||
>
|
||||
<WebhookSubscriptionEdit
|
||||
onSubmit={handleEditModalClose}
|
||||
webhookSubscription={webhook}
|
||||
/>
|
||||
</Modal>
|
||||
),
|
||||
[t, handleEditModalClose, editModalOpen, webhook]
|
||||
);
|
||||
|
||||
const [
|
||||
viewModalOpen,
|
||||
handleViewModalOpen,
|
||||
handleViewModalClose,
|
||||
] = useBoolean();
|
||||
|
||||
const renderViewModal = React.useCallback(
|
||||
() => (
|
||||
<Modal
|
||||
title={t("Webhook Deliveries")}
|
||||
onRequestClose={handleViewModalClose}
|
||||
isOpen={viewModalOpen}
|
||||
>
|
||||
<WebhookDeliveries webhook={webhook} />
|
||||
</Modal>
|
||||
),
|
||||
[t, handleViewModalClose, viewModalOpen]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<OverflowMenuButton aria-label={t("Show menu")} {...menu} />
|
||||
<ContextMenu {...menu} aria-label={t("Member options")}>
|
||||
<Template
|
||||
{...menu}
|
||||
items={[
|
||||
{
|
||||
type: "button",
|
||||
title: t("View Deliveries"),
|
||||
onClick: handleViewModalOpen,
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
title: t("Edit"),
|
||||
dangerous: false,
|
||||
onClick: handleEditModalOpen,
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
title: t("Delete"),
|
||||
dangerous: true,
|
||||
onClick: showDeletionConfirmation,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{renderEditModal()}
|
||||
{renderViewModal()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default WebhookMenu;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { observable } from "mobx";
|
||||
import BaseModel from "./BaseModel";
|
||||
import Field from "./decorators/Field";
|
||||
|
||||
class WebhookDelivery extends BaseModel {
|
||||
@Field
|
||||
@observable
|
||||
id: string;
|
||||
|
||||
@Field
|
||||
@observable
|
||||
status: string;
|
||||
}
|
||||
|
||||
export default WebhookDelivery;
|
||||
@@ -0,0 +1,75 @@
|
||||
import { SmileyIcon, WarningIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import WebhookDelivery from "~/models/WebhookDelivery";
|
||||
import WebhookSubscription from "~/models/WebhookSubscription";
|
||||
import Flex from "~/components/Flex";
|
||||
import TableFromParams from "~/components/TableFromParams";
|
||||
import Time from "~/components/Time";
|
||||
import useStores from "~/hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
webhook: WebhookSubscription;
|
||||
};
|
||||
|
||||
type CellProps<T> = {
|
||||
value: T;
|
||||
row: { original: WebhookDelivery };
|
||||
};
|
||||
|
||||
const WebhookDeliveries = ({ webhook }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { webhookDeliveries } = useStores();
|
||||
|
||||
const [deliveries, setDeliveries] = React.useState<WebhookDelivery[]>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
async function fetch() {
|
||||
const resp = await webhookDeliveries.fetchPage({
|
||||
webhookSubscriptionId: webhook.id,
|
||||
});
|
||||
|
||||
setDeliveries(resp);
|
||||
}
|
||||
fetch();
|
||||
}, [webhookDeliveries, webhook]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableFromParams
|
||||
columns={[
|
||||
{
|
||||
id: "status",
|
||||
Header: t("Status"),
|
||||
accessor: "status",
|
||||
Cell: ({ value }: CellProps<string>) => (
|
||||
<Flex align="center" gap={5}>
|
||||
{value === "failed" ? <WarningIcon /> : <SmileyIcon />}
|
||||
<span>{value}</span>
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "id",
|
||||
Header: t("ID"),
|
||||
accessor: "id",
|
||||
Cell: ({ value }: CellProps<string>) => value,
|
||||
},
|
||||
{
|
||||
id: "createdAt",
|
||||
Header: "",
|
||||
accessor: "createdAt",
|
||||
Cell: ({ value }: CellProps<string>) => <Time dateTime={value} />,
|
||||
},
|
||||
]}
|
||||
data={deliveries}
|
||||
isLoading={false}
|
||||
page={0}
|
||||
defaultSortDirection="DESC"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebhookDeliveries;
|
||||
@@ -1,16 +1,10 @@
|
||||
import { EditIcon, TrashIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import WebhookSubscription from "~/models/WebhookSubscription";
|
||||
import Badge from "~/components/Badge";
|
||||
import Button from "~/components/Button";
|
||||
import ListItem from "~/components/List/Item";
|
||||
import Modal from "~/components/Modal";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import WebhookSubscriptionRevokeDialog from "./WebhookSubscriptionDeleteDialog";
|
||||
import WebhookSubscriptionEdit from "./WebhookSubscriptionEdit";
|
||||
import WebhookMenu from "~/menus/WebhookMenu";
|
||||
|
||||
type Props = {
|
||||
webhook: WebhookSubscription;
|
||||
@@ -18,25 +12,6 @@ type Props = {
|
||||
|
||||
const WebhookSubscriptionListItem = ({ webhook }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { dialogs } = useStores();
|
||||
const [
|
||||
editModalOpen,
|
||||
handleEditModalOpen,
|
||||
handleEditModalClose,
|
||||
] = useBoolean();
|
||||
|
||||
const showDeletionConfirmation = React.useCallback(() => {
|
||||
dialogs.openModal({
|
||||
title: t("Delete webhook"),
|
||||
isCentered: true,
|
||||
content: (
|
||||
<WebhookSubscriptionRevokeDialog
|
||||
onSubmit={dialogs.closeAllModals}
|
||||
webhook={webhook}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, [t, dialogs, webhook]);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
@@ -54,30 +29,7 @@ const WebhookSubscriptionListItem = ({ webhook }: Props) => {
|
||||
{t("Subscribed events")}: <code>{webhook.events.join(", ")}</code>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
onClick={showDeletionConfirmation}
|
||||
icon={<TrashIcon />}
|
||||
neutral
|
||||
>
|
||||
{t("Delete")}
|
||||
</Button>
|
||||
<Button icon={<EditIcon />} onClick={handleEditModalOpen} neutral>
|
||||
{t("Edit")}
|
||||
</Button>
|
||||
<Modal
|
||||
title={t("Edit webhook")}
|
||||
onRequestClose={handleEditModalClose}
|
||||
isOpen={editModalOpen}
|
||||
>
|
||||
<WebhookSubscriptionEdit
|
||||
onSubmit={handleEditModalClose}
|
||||
webhookSubscription={webhook}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
}
|
||||
actions={<WebhookMenu webhook={webhook} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ import ToastsStore from "./ToastsStore";
|
||||
import UiStore from "./UiStore";
|
||||
import UsersStore from "./UsersStore";
|
||||
import ViewsStore from "./ViewsStore";
|
||||
import WebhookDeliveriesStore from "./WebhookDeliveriesStore";
|
||||
import WebhookSubscriptionsStore from "./WebhookSubscriptionStore";
|
||||
|
||||
export default class RootStore {
|
||||
@@ -50,6 +51,7 @@ export default class RootStore {
|
||||
toasts: ToastsStore;
|
||||
fileOperations: FileOperationsStore;
|
||||
webhookSubscriptions: WebhookSubscriptionsStore;
|
||||
webhookDeliveries: WebhookDeliveriesStore;
|
||||
|
||||
constructor() {
|
||||
// PoliciesStore must be initialized before AuthStore
|
||||
@@ -78,6 +80,7 @@ export default class RootStore {
|
||||
this.fileOperations = new FileOperationsStore(this);
|
||||
this.toasts = new ToastsStore();
|
||||
this.webhookSubscriptions = new WebhookSubscriptionsStore(this);
|
||||
this.webhookDeliveries = new WebhookDeliveriesStore(this);
|
||||
}
|
||||
|
||||
logout() {
|
||||
@@ -104,5 +107,6 @@ export default class RootStore {
|
||||
this.users.clear();
|
||||
this.views.clear();
|
||||
this.webhookSubscriptions.clear();
|
||||
this.webhookDeliveries.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import WebhookDelivery from "~/models/WebhookDelivery";
|
||||
import BaseStore, { RPCAction } from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
|
||||
export default class WebhookDerliveriesStore extends BaseStore<
|
||||
WebhookDelivery
|
||||
> {
|
||||
actions = [RPCAction.List];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, WebhookDelivery);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import presentTeam from "./team";
|
||||
import presentUser from "./user";
|
||||
import presentView from "./view";
|
||||
import presentWebhook from "./webhook";
|
||||
import presentWebhookDelivery from "./webhookDelivery";
|
||||
import presentWebhookSubscription from "./webhookSubscription";
|
||||
|
||||
export {
|
||||
@@ -48,4 +49,5 @@ export {
|
||||
presentCollectionGroupMembership,
|
||||
presentWebhook,
|
||||
presentWebhookSubscription,
|
||||
presentWebhookDelivery,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { WebhookDelivery } from "@server/models";
|
||||
|
||||
export default function present(webhook: WebhookDelivery) {
|
||||
return {
|
||||
id: webhook.id,
|
||||
status: webhook.status,
|
||||
createdAt: webhook.createdAt,
|
||||
updatedAt: webhook.updatedAt,
|
||||
};
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import stars from "./stars";
|
||||
import team from "./team";
|
||||
import users from "./users";
|
||||
import views from "./views";
|
||||
import webhookDeliveries from "./webhookDeliveries";
|
||||
import webhookSubscriptions from "./webhookSubscriptions";
|
||||
|
||||
const api = new Koa();
|
||||
@@ -71,6 +72,7 @@ router.use("/", utils.routes());
|
||||
router.use("/", groups.routes());
|
||||
router.use("/", fileOperationsRoute.routes());
|
||||
router.use("/", webhookSubscriptions.routes());
|
||||
router.use("/", webhookDeliveries.routes());
|
||||
|
||||
if (env.ENVIRONMENT === "development") {
|
||||
router.use("/", developer.routes());
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import Router from "koa-router";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { WebhookDelivery } from "@server/models";
|
||||
import { authorize } from "@server/policies";
|
||||
import { presentWebhookDelivery } from "@server/presenters";
|
||||
import pagination from "./middlewares/pagination";
|
||||
|
||||
const router = new Router();
|
||||
|
||||
router.post(
|
||||
"webhookDeliverys.list",
|
||||
auth({ admin: true }),
|
||||
pagination(),
|
||||
async (ctx) => {
|
||||
const { user } = ctx.state;
|
||||
const { webhookSubscriptionId } = ctx.request.body;
|
||||
authorize(user, "listWebhookDeliveries", user.team);
|
||||
|
||||
const deliveries = await WebhookDelivery.findAll({
|
||||
where: {
|
||||
webhookSubscriptionId,
|
||||
},
|
||||
order: [["createdAt", "DESC"]],
|
||||
offset: ctx.state.pagination.offset,
|
||||
limit: ctx.state.pagination.limit,
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
pagination: ctx.state.pagination,
|
||||
data: deliveries.map(presentWebhookDelivery),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -321,6 +321,10 @@
|
||||
"Revoke invite": "Revoke invite",
|
||||
"Activate account": "Activate account",
|
||||
"Suspend account": "Suspend account",
|
||||
"Delete webhook": "Delete webhook",
|
||||
"Edit webhook": "Edit webhook",
|
||||
"Webhook Deliveries": "Webhook Deliveries",
|
||||
"View Deliveries": "View Deliveries",
|
||||
"API token created": "API token created",
|
||||
"Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".": "Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".",
|
||||
"The document archive is empty at the moment.": "The document archive is empty at the moment.",
|
||||
@@ -592,6 +596,8 @@
|
||||
"Active": "Active",
|
||||
"Everyone": "Everyone",
|
||||
"Admins": "Admins",
|
||||
"Status": "Status",
|
||||
"ID": "ID",
|
||||
"Are you sure you want to delete the {{ name }} webhook?": "Are you sure you want to delete the {{ name }} webhook?",
|
||||
"Webhook updated": "Webhook updated",
|
||||
"Update": "Update",
|
||||
@@ -601,10 +607,8 @@
|
||||
"URL": "URL",
|
||||
"All events": "All events",
|
||||
"All {{ groupName }} events": "All {{ groupName }} events",
|
||||
"Delete webhook": "Delete webhook",
|
||||
"Disabled": "Disabled",
|
||||
"Subscribed events": "Subscribed events",
|
||||
"Edit webhook": "Edit webhook",
|
||||
"Webhook created": "Webhook created",
|
||||
"Logo updated": "Logo updated",
|
||||
"Unable to upload new logo": "Unable to upload new logo",
|
||||
|
||||
Reference in New Issue
Block a user