fix(ui): webhooks ui fixes and improvements

This commit is contained in:
Shekar Siri 2025-02-13 12:27:27 +01:00
parent 6bd5b60b1e
commit 4b21194ec5
5 changed files with 168 additions and 218 deletions

View file

@ -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;

View file

@ -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);

View 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);

View file

@ -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);

View file

@ -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;
}