fix(ui): webhooks ui fixes and improvements
This commit is contained in:
parent
6bd5b60b1e
commit
4b21194ec5
5 changed files with 168 additions and 218 deletions
|
|
@ -1,18 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Button } from 'UI';
|
||||
|
||||
const ListItem = ({ webhook, onEdit }) => {
|
||||
return (
|
||||
<div className="border-t group hover:bg-active-blue flex items-center justify-between py-3 px-5 cursor-pointer" onClick={onEdit}>
|
||||
<div>
|
||||
<span>{webhook.name}</span>
|
||||
<div className="color-gray-medium">{webhook.endpoint}</div>
|
||||
</div>
|
||||
<div className="invisible group-hover:visible">
|
||||
<Button variant="text-primary" icon="pencil" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListItem;
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Input } from 'UI';
|
||||
import { Button, Form } from 'antd';
|
||||
import styles from './webhookForm.module.css';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
function WebhookForm(props) {
|
||||
const { settingsStore } = useStore();
|
||||
const { webhookInst: webhook, saveWebhook, editWebhook, saving } = settingsStore;
|
||||
const write = ({ target: { value, name } }) => editWebhook({ [name]: value });
|
||||
|
||||
const save = () => {
|
||||
saveWebhook(webhook)
|
||||
.then(() => {
|
||||
props.onClose();
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error(e.message || 'Failed to save webhook');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white h-screen overflow-y-auto" style={{ width: '350px' }}>
|
||||
<h3 className="p-5 text-2xl">{webhook.exists() ? 'Update' : 'Add'} Webhook</h3>
|
||||
<Form className={styles.wrapper}>
|
||||
<Form.Item>
|
||||
<label>{'Name'}</label>
|
||||
<Input
|
||||
name="name"
|
||||
value={webhook.name}
|
||||
onChange={write}
|
||||
placeholder="Name"
|
||||
maxLength={50}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<label>{'Endpoint'}</label>
|
||||
<Input name="endpoint" value={webhook.endpoint} onChange={write} placeholder="Endpoint" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<label>{'Auth Header (optional)'}</label>
|
||||
<Input
|
||||
name="authHeader"
|
||||
value={webhook.authHeader}
|
||||
onChange={write}
|
||||
placeholder="Auth Header"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
onClick={save}
|
||||
disabled={!webhook.validate()}
|
||||
loading={saving}
|
||||
type="primary"
|
||||
className="float-left mr-2"
|
||||
>
|
||||
{webhook.exists() ? 'Update' : 'Add'}
|
||||
</Button>
|
||||
{webhook.exists() && <Button onClick={props.onClose}>{'Cancel'}</Button>}
|
||||
</div>
|
||||
{webhook.exists() && (
|
||||
<Button
|
||||
icon="trash"
|
||||
type="text"
|
||||
onClick={() => props.onDelete(webhook.webhookId)}
|
||||
></Button>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(WebhookForm);
|
||||
83
frontend/app/components/Client/Webhooks/WebhookForm.tsx
Normal file
83
frontend/app/components/Client/Webhooks/WebhookForm.tsx
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import React from 'react';
|
||||
import { Input } from 'UI';
|
||||
import { Button, Form } from 'antd';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { toast } from 'react-toastify';
|
||||
import { TrashIcon } from 'lucide-react';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
function WebhookForm({ onClose, onDelete }: Props) {
|
||||
const { settingsStore } = useStore();
|
||||
const { webhookInst: webhook, saveWebhook, editWebhook, saving } = settingsStore;
|
||||
const write = ({ target: { value, name } }) => editWebhook({ [name]: value });
|
||||
|
||||
const save = () => {
|
||||
saveWebhook(webhook)
|
||||
.then(() => {
|
||||
onClose();
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error(e.message || 'Failed to save webhook');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onFinish={save} layout="vertical">
|
||||
<Form.Item>
|
||||
<label>{'Name'}</label>
|
||||
<Input
|
||||
name="name"
|
||||
defaultValue={webhook.name}
|
||||
onChange={write}
|
||||
placeholder="Name"
|
||||
maxLength={50}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<label>{'Endpoint'}</label>
|
||||
<Input name="endpoint" defaultValue={webhook.endpoint} onChange={write} placeholder="Endpoint" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<label>{'Auth Header (optional)'}</label>
|
||||
<Input
|
||||
name="authHeader"
|
||||
defaultValue={webhook.authHeader}
|
||||
onChange={write}
|
||||
placeholder="Auth Header"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
// onClick={save}
|
||||
disabled={!webhook.validate()}
|
||||
loading={saving}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="float-left mr-2"
|
||||
>
|
||||
{webhook.exists() ? 'Update' : 'Add'}
|
||||
</Button>
|
||||
{webhook.exists() && <Button onClick={onClose}>{'Cancel'}</Button>}
|
||||
</div>
|
||||
{webhook.exists() && (
|
||||
<Button
|
||||
icon={<TrashIcon size={16} />}
|
||||
type="text"
|
||||
onClick={() => onDelete(webhook.webhookId)}
|
||||
></Button>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(WebhookForm);
|
||||
|
|
@ -1,83 +1,100 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import cn from 'classnames';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import { Button, Loader, NoContent, Icon, Divider } from 'UI';
|
||||
import { Loader, NoContent, Icon } from 'UI';
|
||||
import WebhookForm from './WebhookForm';
|
||||
import ListItem from './ListItem';
|
||||
import styles from './webhooks.module.css';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
import { confirm } from 'UI';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { IWebhook } from 'Types/webhook';
|
||||
import { App, List, Button, Typography, Space } from 'antd';
|
||||
import { PencilIcon } from '.store/lucide-react-virtual-b029c146a4/package';
|
||||
import usePageTitle from '@/hooks/usePageTitle';
|
||||
import { useModal } from 'Components/ModalContext';
|
||||
|
||||
function Webhooks() {
|
||||
const { settingsStore } = useStore()
|
||||
const { webhooks, hooksLoading: loading } = settingsStore;
|
||||
const { showModal, hideModal } = useModal();
|
||||
const { settingsStore } = useStore();
|
||||
const { webhooks, hooksLoading: loading } = settingsStore;
|
||||
const { openModal, closeModal } = useModal();
|
||||
const { modal } = App.useApp();
|
||||
usePageTitle('Webhooks - OpenReplay Preferences');
|
||||
const customWebhooks = webhooks.filter((h) => h.type === 'webhook');
|
||||
|
||||
const customWebhooks = webhooks.filter((hook) => hook.type === 'webhook');
|
||||
useEffect(() => {
|
||||
void settingsStore.fetchWebhooks();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
void settingsStore.fetchWebhooks();
|
||||
}, []);
|
||||
|
||||
const init = (webhookInst?: Partial<IWebhook>) => {
|
||||
settingsStore.initWebhook(webhookInst);
|
||||
showModal(<WebhookForm onClose={hideModal} onDelete={removeWebhook} />, { right: true });
|
||||
};
|
||||
const init = (w?: Partial<IWebhook>) => {
|
||||
settingsStore.initWebhook({...w});
|
||||
openModal(<WebhookForm onClose={closeModal}
|
||||
onDelete={removeWebhook} />, { title: w ? 'Edit Webhook' : 'Add Webhook' });
|
||||
};
|
||||
|
||||
const removeWebhook = async (id: string) => {
|
||||
if (
|
||||
await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, delete',
|
||||
confirmation: `Are you sure you want to remove this webhook?`,
|
||||
})
|
||||
) {
|
||||
settingsStore.removeWebhook(id).then(() => {
|
||||
toast.success('Webhook removed successfully');
|
||||
});
|
||||
hideModal();
|
||||
}
|
||||
};
|
||||
const removeWebhook = async (id: string) => {
|
||||
modal.confirm({
|
||||
title: 'Confirm',
|
||||
content: 'Are you sure you want to remove this webhook?',
|
||||
onOk: () => {
|
||||
settingsStore.removeWebhook(id).then(() => toast.success('Webhook removed successfully'));
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-sm border p-5">
|
||||
<div className={cn(styles.tabHeader)}>
|
||||
<h3 className={cn(styles.tabTitle, 'text-2xl')}>{'Webhooks'}</h3>
|
||||
<Button className="ml-auto" variant="primary" onClick={() => init()}>Add Webhook</Button>
|
||||
</div>
|
||||
|
||||
<div className="text-base text-disabled-text flex items-center my-3 px-5">
|
||||
<Icon name="info-circle-fill" className="mr-2" size={16} />
|
||||
Leverage webhook notifications on alerts to trigger custom callbacks.
|
||||
</div>
|
||||
|
||||
<Loader loading={loading}>
|
||||
<NoContent
|
||||
title={
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<AnimatedSVG name={ICONS.NO_WEBHOOKS} size={60} />
|
||||
<div className="text-center my-4">None added yet</div>
|
||||
</div>
|
||||
}
|
||||
size="small"
|
||||
show={customWebhooks.length === 0}
|
||||
>
|
||||
<div className="cursor-pointer">
|
||||
{customWebhooks.map((webhook) => (
|
||||
<>
|
||||
<ListItem key={webhook.webhookId} webhook={webhook} onEdit={() => init(webhook)} />
|
||||
<Divider className="m-0" />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-sm border p-5">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<Typography.Title level={4} style={{ marginBottom: 0 }}>Webhooks</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
<Space><Icon name="info-circle-fill" size={16} />
|
||||
Leverage webhook notifications on alerts to trigger custom callbacks.</Space>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
<Button type="primary" onClick={() => init()}>Add Webhook</Button>
|
||||
</div>
|
||||
|
||||
<Loader loading={loading}>
|
||||
<NoContent
|
||||
title={
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<AnimatedSVG name={ICONS.NO_WEBHOOKS} size={60} />
|
||||
<div className="text-center my-4">None added yet</div>
|
||||
</div>
|
||||
}
|
||||
size="small"
|
||||
show={customWebhooks.length === 0}
|
||||
>
|
||||
<List
|
||||
size="small"
|
||||
dataSource={customWebhooks}
|
||||
renderItem={(w) => (
|
||||
<List.Item
|
||||
onClick={() => init(w)}
|
||||
className="p-2! group flex justify-between items-center cursor-pointer hover:bg-active-blue transition"
|
||||
>
|
||||
<Space direction="vertical" className="overflow-hidden w-full">
|
||||
<Typography.Text style={{ textTransform: 'capitalize' }}>{w.name}</Typography.Text>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
ellipsis={{ tooltip: w.endpoint }}
|
||||
style={{
|
||||
width: '90%',
|
||||
display: 'inline-block',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
{w.endpoint}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<Button type="text" className="invisible group-hover:visible" icon={<PencilIcon size={16} />} />
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withPageTitle('Webhooks - OpenReplay Preferences')(observer(Webhooks));
|
||||
export default observer(Webhooks);
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
|
||||
@import 'mixins.css';
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
padding: 15px 10px;
|
||||
border-bottom: solid thin $gray-light;
|
||||
transition: all 0.4s;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
transition: all 0.2s;
|
||||
& .actions {
|
||||
opacity: 1;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-left: auto;
|
||||
opacity: 0;
|
||||
transition: all 0.4s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& .button {
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
&:hover {
|
||||
& svg {
|
||||
fill: $teal-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
background-color: $gray-lightest;
|
||||
box-shadow: 0 0 0 1px $gray-light inset;
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
font-size: 12px;
|
||||
color: $gray-medium;
|
||||
margin-top: 5px;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue