mirror of
https://github.com/whyour/qinglong.git
synced 2026-04-29 00:45:11 +08:00
feat: add notification audit log feature
Agent-Logs-Url: https://github.com/whyour/qinglong/sessions/4c9f0ab1-8b0e-4b94-b295-39c90ed942c2 Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
parent
bcb2471768
commit
79964f149c
|
|
@ -374,6 +374,19 @@ export default (app: Router) => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
'/notify-log',
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const data = await systemService.getNotifyLog();
|
||||||
|
res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
'/log',
|
'/log',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,12 @@ export enum AuthDataType {
|
||||||
'removeLogFrequency' = 'removeLogFrequency',
|
'removeLogFrequency' = 'removeLogFrequency',
|
||||||
'systemConfig' = 'systemConfig',
|
'systemConfig' = 'systemConfig',
|
||||||
'authConfig' = 'authConfig',
|
'authConfig' = 'authConfig',
|
||||||
|
'notifyLog' = 'notifyLog',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum NotifyStatus {
|
||||||
|
'success',
|
||||||
|
'fail',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SystemConfigInfo {
|
export interface SystemConfigInfo {
|
||||||
|
|
@ -49,6 +55,14 @@ export interface LoginLogInfo {
|
||||||
status?: LoginStatus;
|
status?: LoginStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NotifyLogInfo {
|
||||||
|
timestamp?: number;
|
||||||
|
title?: string;
|
||||||
|
content?: string;
|
||||||
|
status?: NotifyStatus;
|
||||||
|
notifyType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TokenInfo {
|
export interface TokenInfo {
|
||||||
value: string;
|
value: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
|
@ -81,6 +95,7 @@ export interface AuthInfo {
|
||||||
export type SystemModelInfo = SystemConfigInfo &
|
export type SystemModelInfo = SystemConfigInfo &
|
||||||
Partial<NotificationInfo> &
|
Partial<NotificationInfo> &
|
||||||
LoginLogInfo &
|
LoginLogInfo &
|
||||||
|
Partial<NotifyLogInfo> &
|
||||||
Partial<AuthInfo>;
|
Partial<AuthInfo>;
|
||||||
|
|
||||||
export interface SystemInstance
|
export interface SystemInstance
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ import {
|
||||||
SystemInstance,
|
SystemInstance,
|
||||||
SystemModel,
|
SystemModel,
|
||||||
SystemModelInfo,
|
SystemModelInfo,
|
||||||
|
NotifyStatus,
|
||||||
|
NotifyLogInfo,
|
||||||
} from '../data/system';
|
} from '../data/system';
|
||||||
import taskLimit from '../shared/pLimit';
|
import taskLimit from '../shared/pLimit';
|
||||||
import NotificationService from './notify';
|
import NotificationService from './notify';
|
||||||
|
|
@ -389,11 +391,33 @@ export default class SystemService {
|
||||||
if (notificationInfo && typeString) {
|
if (notificationInfo && typeString) {
|
||||||
notificationInfo.type = typeString;
|
notificationInfo.type = typeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let notifyType: string | undefined;
|
||||||
|
try {
|
||||||
|
const notifConfig = await this.getDb({ type: AuthDataType.notification });
|
||||||
|
notifyType = notifConfig.info?.type as string | undefined;
|
||||||
|
} catch (e) {}
|
||||||
|
if (notificationInfo?.type) {
|
||||||
|
notifyType = typeString || (notificationInfo.type as string);
|
||||||
|
}
|
||||||
|
|
||||||
const isSuccess = await this.notificationService.notify(
|
const isSuccess = await this.notificationService.notify(
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
notificationInfo,
|
notificationInfo,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await SystemModel.create({
|
||||||
|
type: AuthDataType.notifyLog,
|
||||||
|
info: {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
status: isSuccess ? NotifyStatus.success : NotifyStatus.fail,
|
||||||
|
notifyType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
return { code: 200, message: '通知发送成功' };
|
return { code: 200, message: '通知发送成功' };
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -401,6 +425,18 @@ export default class SystemService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getNotifyLog(): Promise<Array<NotifyLogInfo>> {
|
||||||
|
const docs = await SystemModel.findAll({
|
||||||
|
where: { type: AuthDataType.notifyLog },
|
||||||
|
order: [['id', 'DESC']],
|
||||||
|
});
|
||||||
|
if (docs.length > 200) {
|
||||||
|
const ids = docs.slice(200).map((x) => x.id!);
|
||||||
|
await SystemModel.destroy({ where: { id: ids } });
|
||||||
|
}
|
||||||
|
return docs.slice(0, 200).map((x) => ({ ...x.info, id: x.id }));
|
||||||
|
}
|
||||||
|
|
||||||
public async run({ command, logPath }: { command: string; logPath?: string }, callback: TaskCallbacks) {
|
public async run({ command, logPath }: { command: string; logPath?: string }, callback: TaskCallbacks) {
|
||||||
if (!command.startsWith(TASK_COMMAND)) {
|
if (!command.startsWith(TASK_COMMAND)) {
|
||||||
command = `${TASK_COMMAND} ${command}`;
|
command = `${TASK_COMMAND} ${command}`;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import SecuritySettings from './security';
|
import SecuritySettings from './security';
|
||||||
import LoginLog from './loginLog';
|
import LoginLog from './loginLog';
|
||||||
|
import NotifyLog from './notifyLog';
|
||||||
import NotificationSetting from './notification';
|
import NotificationSetting from './notification';
|
||||||
import Other from './other';
|
import Other from './other';
|
||||||
import About from './about';
|
import About from './about';
|
||||||
|
|
@ -125,6 +126,7 @@ const Setting = () => {
|
||||||
const [editedApp, setEditedApp] = useState<any>();
|
const [editedApp, setEditedApp] = useState<any>();
|
||||||
const [tabActiveKey, setTabActiveKey] = useState('security');
|
const [tabActiveKey, setTabActiveKey] = useState('security');
|
||||||
const [loginLogData, setLoginLogData] = useState<any[]>([]);
|
const [loginLogData, setLoginLogData] = useState<any[]>([]);
|
||||||
|
const [notifyLogData, setNotifyLogData] = useState<any[]>([]);
|
||||||
const [notificationInfo, setNotificationInfo] = useState<any>();
|
const [notificationInfo, setNotificationInfo] = useState<any>();
|
||||||
const containergRef = useRef<HTMLDivElement>(null);
|
const containergRef = useRef<HTMLDivElement>(null);
|
||||||
const [height, setHeight] = useState<number>(0);
|
const [height, setHeight] = useState<number>(0);
|
||||||
|
|
@ -253,6 +255,8 @@ const Setting = () => {
|
||||||
getApps();
|
getApps();
|
||||||
} else if (activeKey === 'login') {
|
} else if (activeKey === 'login') {
|
||||||
getLoginLog();
|
getLoginLog();
|
||||||
|
} else if (activeKey === 'notifylog') {
|
||||||
|
getNotifyLog();
|
||||||
} else if (activeKey === 'notification') {
|
} else if (activeKey === 'notification') {
|
||||||
getNotification();
|
getNotification();
|
||||||
}
|
}
|
||||||
|
|
@ -271,6 +275,19 @@ const Setting = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getNotifyLog = () => {
|
||||||
|
request
|
||||||
|
.get(`${config.apiPrefix}system/notify-log`)
|
||||||
|
.then(({ code, data }) => {
|
||||||
|
if (code === 200) {
|
||||||
|
setNotifyLogData(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDemoEnv) {
|
if (isDemoEnv) {
|
||||||
getApps();
|
getApps();
|
||||||
|
|
@ -344,6 +361,11 @@ const Setting = () => {
|
||||||
label: intl.get('登录日志'),
|
label: intl.get('登录日志'),
|
||||||
children: <LoginLog height={height} data={loginLogData} />,
|
children: <LoginLog height={height} data={loginLogData} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'notifylog',
|
||||||
|
label: intl.get('通知日志'),
|
||||||
|
children: <NotifyLog height={height} data={notifyLogData} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'dependence',
|
key: 'dependence',
|
||||||
label: intl.get('依赖设置'),
|
label: intl.get('依赖设置'),
|
||||||
|
|
|
||||||
93
src/pages/setting/notifyLog.tsx
Normal file
93
src/pages/setting/notifyLog.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import React from 'react';
|
||||||
|
import { Table, Tag } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
enum NotifyStatus {
|
||||||
|
'成功',
|
||||||
|
'失败',
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotifyStatusColor {
|
||||||
|
'success',
|
||||||
|
'error',
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: intl.get('序号'),
|
||||||
|
width: 50,
|
||||||
|
render: (text: string, record: any, index: number) => {
|
||||||
|
return index + 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('发送时间'),
|
||||||
|
dataIndex: 'timestamp',
|
||||||
|
key: 'timestamp',
|
||||||
|
width: 160,
|
||||||
|
render: (text: string, record: any) => {
|
||||||
|
return dayjs(record.timestamp).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('标题'),
|
||||||
|
dataIndex: 'title',
|
||||||
|
key: 'title',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('内容'),
|
||||||
|
dataIndex: 'content',
|
||||||
|
key: 'content',
|
||||||
|
render: (text: string) => {
|
||||||
|
if (!text) return '';
|
||||||
|
return text.length > 100 ? text.slice(0, 100) + '...' : text;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('推送渠道'),
|
||||||
|
dataIndex: 'notifyType',
|
||||||
|
key: 'notifyType',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('发送状态'),
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
width: 90,
|
||||||
|
render: (text: string, record: any) => {
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
color={NotifyStatusColor[record.status]}
|
||||||
|
style={{ marginRight: 0 }}
|
||||||
|
>
|
||||||
|
{intl.get(NotifyStatus[record.status])}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const NotifyLog = ({
|
||||||
|
data,
|
||||||
|
height,
|
||||||
|
}: {
|
||||||
|
data: Array<object>;
|
||||||
|
height: number;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={data}
|
||||||
|
rowKey="id"
|
||||||
|
size="middle"
|
||||||
|
scroll={{ x: 1000, y: height }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotifyLog;
|
||||||
Loading…
Reference in New Issue
Block a user