2024-02-28 14:28:12 +00:00
|
|
|
import { Trans, t } from '@i18next-toolkit/react';
|
2024-03-29 11:59:42 +00:00
|
|
|
import {
|
|
|
|
Alert,
|
|
|
|
Button,
|
|
|
|
Collapse,
|
|
|
|
Form,
|
|
|
|
Input,
|
|
|
|
Modal,
|
|
|
|
Popconfirm,
|
|
|
|
Table,
|
|
|
|
Typography,
|
|
|
|
} from 'antd';
|
2024-02-17 16:47:22 +00:00
|
|
|
import React, { useMemo, useState } from 'react';
|
2024-04-03 12:08:09 +00:00
|
|
|
import {
|
|
|
|
AppRouterOutput,
|
|
|
|
defaultErrorHandler,
|
|
|
|
defaultSuccessHandler,
|
|
|
|
trpc,
|
|
|
|
} from '../../api/trpc';
|
2024-02-17 16:47:22 +00:00
|
|
|
import { useCurrentWorkspaceId } from '../../store/user';
|
|
|
|
import { type ColumnsType } from 'antd/es/table/interface';
|
|
|
|
import {
|
|
|
|
BarChartOutlined,
|
2024-03-03 10:51:10 +00:00
|
|
|
CodeOutlined,
|
2024-02-17 16:47:22 +00:00
|
|
|
EditOutlined,
|
|
|
|
PlusOutlined,
|
2024-03-29 11:59:42 +00:00
|
|
|
DeleteOutlined,
|
2024-02-17 16:47:22 +00:00
|
|
|
} from '@ant-design/icons';
|
|
|
|
import { useNavigate } from 'react-router';
|
|
|
|
import { PageHeader } from '../PageHeader';
|
|
|
|
import { useEvent } from '../../hooks/useEvent';
|
2024-02-22 16:07:30 +00:00
|
|
|
import { TelemetryCounter } from './TelemetryCounter';
|
2024-03-29 11:59:42 +00:00
|
|
|
import { LuDelete, LuTrash } from 'react-icons/lu';
|
2024-02-17 16:47:22 +00:00
|
|
|
|
|
|
|
type TelemetryInfo = AppRouterOutput['telemetry']['all'][number];
|
|
|
|
|
|
|
|
export const TelemetryList: React.FC = React.memo(() => {
|
|
|
|
const workspaceId = useCurrentWorkspaceId();
|
2024-03-03 10:51:10 +00:00
|
|
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
2024-02-17 16:47:22 +00:00
|
|
|
const [form] = Form.useForm<{ id?: string; name: string }>();
|
|
|
|
const upsertTelemetryMutation = trpc.telemetry.upsert.useMutation();
|
|
|
|
const utils = trpc.useUtils();
|
2024-03-03 10:51:10 +00:00
|
|
|
const [modal, contextHolder] = Modal.useModal();
|
2024-02-17 16:47:22 +00:00
|
|
|
|
|
|
|
const handleAddTelemetry = useEvent(async () => {
|
|
|
|
await form.validateFields();
|
|
|
|
const values = form.getFieldsValue();
|
|
|
|
|
|
|
|
await upsertTelemetryMutation.mutateAsync({
|
|
|
|
telemetryId: values.id,
|
|
|
|
workspaceId,
|
|
|
|
name: values.name,
|
|
|
|
});
|
|
|
|
|
|
|
|
utils.telemetry.all.refetch();
|
|
|
|
|
2024-03-03 10:51:10 +00:00
|
|
|
setIsEditModalOpen(false);
|
2024-02-17 16:47:22 +00:00
|
|
|
|
|
|
|
form.resetFields();
|
|
|
|
});
|
|
|
|
|
2024-03-03 10:51:10 +00:00
|
|
|
const handleShowUsage = useEvent((info: TelemetryInfo) => {
|
|
|
|
const blankGif = `${window.location.origin}/telemetry/${workspaceId}/${info.id}.gif`;
|
|
|
|
const countBadgeUrl = `${window.location.origin}/telemetry/${workspaceId}/${info.id}/badge.svg`;
|
|
|
|
modal.info({
|
|
|
|
maskClosable: true,
|
|
|
|
title: 'How to Use Telemetry',
|
|
|
|
content: (
|
|
|
|
<div>
|
|
|
|
<p>Here is some way to use telemetry:</p>
|
|
|
|
<h2>Insert to article:</h2>
|
|
|
|
<p>
|
|
|
|
if your article support raw html, you can direct insert it{' '}
|
|
|
|
<Typography.Text code={true} copyable={{ text: blankGif }}>
|
|
|
|
{blankGif}
|
|
|
|
</Typography.Text>
|
|
|
|
</p>
|
|
|
|
<Collapse
|
|
|
|
ghost
|
|
|
|
items={[
|
|
|
|
{
|
|
|
|
label: 'Advanced',
|
|
|
|
children: (
|
|
|
|
<div>
|
|
|
|
<p>
|
|
|
|
Some website will not allow send `referer` field. so its
|
|
|
|
maybe can not track source. so you can mark it by
|
|
|
|
yourself. for example:
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
<Typography.Text code={true}>
|
|
|
|
{blankGif}?url=https://xxxxxxxx
|
|
|
|
</Typography.Text>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
),
|
|
|
|
},
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<h2>Count your website visitor:</h2>
|
|
|
|
<p>
|
|
|
|
if your article support raw html, you can direct insert it{' '}
|
2024-03-09 10:27:02 +00:00
|
|
|
<Typography.Text code={true} copyable={{ text: countBadgeUrl }}>
|
2024-03-03 10:51:10 +00:00
|
|
|
{countBadgeUrl}
|
|
|
|
</Typography.Text>
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
Like this: <img src={countBadgeUrl} />
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-02-17 16:47:22 +00:00
|
|
|
const handleEditTelemetry = useEvent(async (info: TelemetryInfo) => {
|
2024-03-03 10:51:10 +00:00
|
|
|
setIsEditModalOpen(true);
|
2024-02-17 16:47:22 +00:00
|
|
|
form.setFieldsValue({
|
|
|
|
id: info.id,
|
|
|
|
name: info.name,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<PageHeader
|
|
|
|
title={t('Telemetry')}
|
2024-02-28 14:28:12 +00:00
|
|
|
desc={
|
|
|
|
<div>
|
|
|
|
<p>
|
|
|
|
<Trans>
|
|
|
|
Telemetry is a technology that reports access data even on pages
|
|
|
|
that are not under your control. As long as the other website
|
|
|
|
allows the insertion of third-party images (e.g., forums, blogs,
|
|
|
|
and various rich-text editors), then the data can be collected
|
|
|
|
and used to analyze the images when they are loaded by the user.
|
|
|
|
</Trans>
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<p>
|
|
|
|
<Trans>
|
|
|
|
Generally, we will use a one-pixel blank image so that it will
|
|
|
|
not affect the user's normal use.
|
|
|
|
</Trans>
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<p>
|
|
|
|
<Trans>
|
|
|
|
At the same time, we can also use it in some client-side
|
|
|
|
application scenarios, such as collecting the frequency of cli
|
|
|
|
usage, such as collecting the installation of selfhosted apps,
|
|
|
|
and so on.
|
|
|
|
</Trans>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
}
|
2024-02-17 16:47:22 +00:00
|
|
|
action={
|
|
|
|
<div>
|
|
|
|
<Button
|
|
|
|
type="primary"
|
|
|
|
icon={<PlusOutlined />}
|
|
|
|
size="large"
|
2024-03-03 10:51:10 +00:00
|
|
|
onClick={() => setIsEditModalOpen(true)}
|
2024-02-17 16:47:22 +00:00
|
|
|
>
|
|
|
|
{t('Add Telemetry')}
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
|
2024-03-03 10:51:10 +00:00
|
|
|
<TelemetryListTable
|
|
|
|
onShowUsage={handleShowUsage}
|
|
|
|
onEdit={handleEditTelemetry}
|
|
|
|
/>
|
2024-02-17 16:47:22 +00:00
|
|
|
|
|
|
|
<Modal
|
|
|
|
title={t('Add Telemetry')}
|
2024-03-03 10:51:10 +00:00
|
|
|
open={isEditModalOpen}
|
2024-02-17 16:47:22 +00:00
|
|
|
okButtonProps={{
|
|
|
|
loading: upsertTelemetryMutation.isLoading,
|
|
|
|
}}
|
|
|
|
onOk={() => handleAddTelemetry()}
|
2024-03-03 10:51:10 +00:00
|
|
|
onCancel={() => setIsEditModalOpen(false)}
|
2024-02-17 16:47:22 +00:00
|
|
|
>
|
|
|
|
<Form layout="vertical" form={form}>
|
|
|
|
<Form.Item name="id" hidden={true} />
|
|
|
|
<Form.Item
|
|
|
|
label={t('Telemetry Name')}
|
|
|
|
name="name"
|
|
|
|
tooltip={t('Telemetry Name to Display')}
|
|
|
|
rules={[{ required: true }]}
|
|
|
|
>
|
|
|
|
<Input />
|
|
|
|
</Form.Item>
|
|
|
|
</Form>
|
|
|
|
</Modal>
|
2024-03-03 10:51:10 +00:00
|
|
|
|
|
|
|
{contextHolder}
|
2024-02-17 16:47:22 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
TelemetryList.displayName = 'TelemetryList';
|
|
|
|
|
|
|
|
const TelemetryListTable: React.FC<{
|
|
|
|
onEdit: (info: TelemetryInfo) => void;
|
2024-03-03 10:51:10 +00:00
|
|
|
onShowUsage: (info: TelemetryInfo) => void;
|
2024-02-17 16:47:22 +00:00
|
|
|
}> = React.memo((props) => {
|
|
|
|
const workspaceId = useCurrentWorkspaceId();
|
2024-03-29 11:59:42 +00:00
|
|
|
const {
|
|
|
|
data = [],
|
|
|
|
isLoading,
|
|
|
|
refetch,
|
|
|
|
} = trpc.telemetry.all.useQuery({
|
2024-02-17 16:47:22 +00:00
|
|
|
workspaceId,
|
|
|
|
});
|
|
|
|
const navigate = useNavigate();
|
2024-04-03 12:08:09 +00:00
|
|
|
const deleteMutation = trpc.telemetry.delete.useMutation({
|
|
|
|
onSuccess: defaultSuccessHandler,
|
|
|
|
onError: defaultErrorHandler,
|
|
|
|
});
|
2024-02-17 16:47:22 +00:00
|
|
|
|
|
|
|
const columns = useMemo((): ColumnsType<TelemetryInfo> => {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
dataIndex: 'name',
|
|
|
|
title: t('Name'),
|
|
|
|
},
|
2024-02-22 16:07:30 +00:00
|
|
|
{
|
|
|
|
dataIndex: 'id',
|
|
|
|
title: t('Count'),
|
|
|
|
align: 'center',
|
|
|
|
width: 130,
|
|
|
|
render: (id) => {
|
|
|
|
return <TelemetryCounter telemetryId={id} />;
|
|
|
|
},
|
|
|
|
},
|
2024-02-17 16:47:22 +00:00
|
|
|
{
|
|
|
|
key: 'action',
|
2024-02-22 16:07:30 +00:00
|
|
|
title: t('Actions'),
|
|
|
|
align: 'right',
|
|
|
|
width: 240,
|
2024-02-17 16:47:22 +00:00
|
|
|
render: (_, record) => {
|
|
|
|
return (
|
2024-03-22 16:45:21 +00:00
|
|
|
<div className="flex justify-end gap-2">
|
2024-03-29 11:59:42 +00:00
|
|
|
<Popconfirm
|
|
|
|
title={t('Confirm to delete this telemetry: [{{name}}]', {
|
|
|
|
name: record.name,
|
|
|
|
})}
|
|
|
|
disabled={deleteMutation.isLoading}
|
|
|
|
onConfirm={async () => {
|
|
|
|
await deleteMutation.mutateAsync({
|
|
|
|
telemetryId: record.id,
|
|
|
|
workspaceId,
|
|
|
|
});
|
|
|
|
await refetch();
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Button
|
|
|
|
danger={true}
|
|
|
|
disabled={deleteMutation.isLoading}
|
|
|
|
icon={<DeleteOutlined />}
|
|
|
|
/>
|
|
|
|
</Popconfirm>
|
2024-03-03 10:51:10 +00:00
|
|
|
<Button
|
|
|
|
icon={<CodeOutlined />}
|
|
|
|
onClick={() => props.onShowUsage(record)}
|
|
|
|
>
|
|
|
|
{t('Usage')}
|
|
|
|
</Button>
|
2024-02-17 16:47:22 +00:00
|
|
|
<Button
|
|
|
|
icon={<EditOutlined />}
|
|
|
|
onClick={() => props.onEdit(record)}
|
|
|
|
>
|
|
|
|
{t('Edit')}
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
icon={<BarChartOutlined />}
|
|
|
|
onClick={() => {
|
|
|
|
navigate(`/telemetry/${record.id}`);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t('View')}
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
] as ColumnsType<TelemetryInfo>;
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Table
|
|
|
|
loading={isLoading}
|
|
|
|
dataSource={data}
|
|
|
|
columns={columns}
|
|
|
|
rowKey="id"
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
TelemetryListTable.displayName = 'TelemetryListTable';
|