feat: add monitor badge #6

This commit is contained in:
moonrailgun 2024-01-06 22:08:10 +08:00
parent 19e7ed516b
commit 3e9760d895
6 changed files with 178 additions and 1 deletions

View File

@ -42,6 +42,7 @@
"colord": "^2.9.3", "colord": "^2.9.3",
"compose-middleware": "^5.0.1", "compose-middleware": "^5.0.1",
"compression": "^1.7.4", "compression": "^1.7.4",
"copy-to-clipboard": "^3.3.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"croner": "^7.0.1", "croner": "^7.0.1",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",

View File

@ -61,6 +61,9 @@ dependencies:
compression: compression:
specifier: ^1.7.4 specifier: ^1.7.4
version: 1.7.4 version: 1.7.4
copy-to-clipboard:
specifier: ^3.3.3
version: 3.3.3
cors: cors:
specifier: ^2.8.5 specifier: ^2.8.5
version: 2.8.5 version: 2.8.5

View File

@ -0,0 +1,101 @@
import { Checkbox, Divider, Input, message } from 'antd';
import React, { useMemo, useState } from 'react';
import { useEvent } from '../../hooks/useEvent';
import copy from 'copy-to-clipboard';
export const MonitorBadgeView: React.FC<{
workspaceId: string;
monitorId: string;
}> = React.memo((props) => {
const { workspaceId, monitorId } = props;
const [showDetail, setShowDetail] = useState(false);
const url = useMemo(() => {
let url = `${window.location.origin}/monitor/${workspaceId}/${monitorId}/badge.svg`;
if (showDetail) {
url += `?showDetail=true`;
}
return url;
}, [showDetail]);
const handleCopy = useEvent((text: string) => {
copy(text);
message.success('Copy success!');
});
return (
<div>
<div>
<img src={url} />
</div>
<p>This will show your recent result of your monitor</p>
<div>
<Checkbox
checked={showDetail}
onChange={(e) => setShowDetail(e.target.checked)}
>
Show Detail Number
</Checkbox>
</div>
<Divider />
<p>Share with...</p>
<div className="flex flex-col gap-2">
<Input
addonBefore="HTML Embed"
value={`<img src="${url}" />`}
addonAfter={
<div
className="cursor-pointer"
onClick={() => handleCopy(`<img src="${url}" />`)}
>
Copy
</div>
}
/>
<Input
addonBefore="Markdown"
value={`![](${url})`}
addonAfter={
<div
className="cursor-pointer"
onClick={() => handleCopy(`![](${url})`)}
>
Copy
</div>
}
/>
<Input
addonBefore="BBCode"
value={`[img]${url}[/img]`}
addonAfter={
<div
className="cursor-pointer"
onClick={() => handleCopy(`[img]${url}[/img]`)}
>
Copy
</div>
}
/>
<Input
addonBefore="url"
value={url}
addonAfter={
<div className="cursor-pointer" onClick={() => handleCopy(url)}>
Copy
</div>
}
/>
</div>
</div>
);
});
MonitorBadgeView.displayName = 'MonitorBadgeView';

View File

@ -19,7 +19,8 @@ import { MonitorEventList } from './MonitorEventList';
import { useEvent } from '../../hooks/useEvent'; import { useEvent } from '../../hooks/useEvent';
import { MonitorDataMetrics } from './MonitorDataMetrics'; import { MonitorDataMetrics } from './MonitorDataMetrics';
import { MonitorDataChart } from './MonitorDataChart'; import { MonitorDataChart } from './MonitorDataChart';
import { DeleteOutlined } from '@ant-design/icons'; import { DeleteOutlined, MoreOutlined } from '@ant-design/icons';
import { MonitorBadgeView } from './MonitorBadgeView';
interface MonitorInfoProps { interface MonitorInfoProps {
monitorId: string; monitorId: string;
@ -29,6 +30,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
const { monitorId } = props; const { monitorId } = props;
const [currectResponse, setCurrentResponse] = useState(0); const [currectResponse, setCurrentResponse] = useState(0);
const navigate = useNavigate(); const navigate = useNavigate();
const [showBadge, setShowBadge] = useState(false);
const { const {
data: monitorInfo, data: monitorInfo,
@ -209,6 +211,35 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
> >
<Button danger={true}>Delete</Button> <Button danger={true}>Delete</Button>
</Popconfirm> </Popconfirm>
<Dropdown
trigger={['click']}
placement="bottomRight"
menu={{
items: [
{
key: 'badge',
label: 'Show Badge',
onClick: () => setShowBadge(true),
},
],
}}
>
<Button icon={<MoreOutlined />} />
</Dropdown>
<Modal
open={showBadge}
onCancel={() => setShowBadge(false)}
onOk={() => setShowBadge(false)}
destroyOnClose={true}
centered={true}
>
<MonitorBadgeView
workspaceId={workspaceId}
monitorId={monitorId}
/>
</Modal>
</div> </div>
<Card> <Card>

View File

@ -25,6 +25,7 @@ import cors from 'cors';
import { serverStatusRouter } from './router/serverStatus'; import { serverStatusRouter } from './router/serverStatus';
import { initCronjob } from './cronjob'; import { initCronjob } from './cronjob';
import { logger } from './utils/logger'; import { logger } from './utils/logger';
import { monitorRouter } from './router/monitor';
const port = settings.port; const port = settings.port;
@ -57,6 +58,7 @@ app.use(
app.use('/api/website', websiteRouter); app.use('/api/website', websiteRouter);
app.use('/api/workspace', workspaceRouter); app.use('/api/workspace', workspaceRouter);
app.use('/monitor', monitorRouter);
app.use('/telemetry', telemetryRouter); app.use('/telemetry', telemetryRouter);
app.use('/serverStatus', serverStatusRouter); app.use('/serverStatus', serverStatusRouter);

View File

@ -0,0 +1,39 @@
import { Router } from 'express';
import { param, validate, query } from '../middleware/validate';
import { numify } from '../utils/common';
import { makeBadge } from 'badge-maker';
import { getMonitorPublicInfos, getMonitorRecentData } from '../model/monitor';
import { checkEnvTrusty } from '../utils/env';
export const monitorRouter = Router();
monitorRouter.get(
'/:workspaceId/:monitorId/badge.svg',
validate(
param('workspaceId').isString(),
param('monitorId').isString(),
query('showDetail').optional().isString()
),
async (req, res) => {
const { workspaceId, monitorId } = req.params;
const showDetail = checkEnvTrusty(String(req.query.showDetail));
const [info] = await getMonitorPublicInfos([monitorId]);
const [{ value }] = await getMonitorRecentData(workspaceId, monitorId, 1);
const svg =
value >= 0
? makeBadge({
label: info.name,
message: showDetail ? numify(value) : 'Health',
color: 'green',
})
: makeBadge({
label: info.name,
message: 'Error',
color: 'red',
});
res.header('Content-Type', 'image/svg+xml').send(svg);
}
);