系统日志增加时间筛选和清空

This commit is contained in:
whyour 2024-08-22 00:47:24 +08:00
parent f6021c8157
commit f4cb3eacf8
10 changed files with 209 additions and 78 deletions

View File

@ -340,12 +340,41 @@ export default (app: Router) => {
}, },
); );
route.get('/log', async (req: Request, res: Response, next: NextFunction) => { route.get(
'/log',
celebrate({
query: {
startTime: Joi.string().allow('').optional(),
endTime: Joi.string().allow('').optional(),
t: Joi.string().optional(),
},
}),
async (req: Request, res: Response, next: NextFunction) => {
try { try {
const systemService = Container.get(SystemService); const systemService = Container.get(SystemService);
await systemService.getSystemLog(res); await systemService.getSystemLog(
res,
req.query as {
startTime?: string;
endTime?: string;
},
);
} catch (e) { } catch (e) {
return next(e); return next(e);
} }
}); },
);
route.delete(
'/log',
async (req: Request, res: Response, next: NextFunction) => {
try {
const systemService = Container.get(SystemService);
await systemService.deleteSystemLog();
res.send({ code: 200 });
} catch (e) {
return next(e);
}
},
);
}; };

View File

@ -15,6 +15,7 @@ import {
parseVersion, parseVersion,
promiseExec, promiseExec,
readDirs, readDirs,
rmPath,
} from '../config/util'; } from '../config/util';
import { import {
DependenceModel, DependenceModel,
@ -34,6 +35,7 @@ import NotificationService from './notify';
import ScheduleService, { TaskCallbacks } from './schedule'; import ScheduleService, { TaskCallbacks } from './schedule';
import SockService from './sock'; import SockService from './sock';
import os from 'os'; import os from 'os';
import dayjs from 'dayjs';
@Service() @Service()
export default class SystemService { export default class SystemService {
@ -409,9 +411,25 @@ export default class SystemService {
} }
} }
public async getSystemLog(res: Response) { public async getSystemLog(
res: Response,
query: {
startTime?: string;
endTime?: string;
},
) {
const startTime = dayjs(query.startTime || undefined)
.startOf('d')
.valueOf();
const endTime = dayjs(query.endTime || undefined)
.endOf('d')
.valueOf();
const result = await readDirs(config.systemLogPath, config.systemLogPath); const result = await readDirs(config.systemLogPath, config.systemLogPath);
const logs = result.reverse().filter((x) => x.title.endsWith('.log')); const logs = result
.reverse()
.filter((x) => x.title.endsWith('.log'))
.filter((x) => x.mtime >= startTime && x.mtime <= endTime);
res.set({ res.set({
'Content-Length': sum(logs.map((x) => x.size)), 'Content-Length': sum(logs.map((x) => x.size)),
}); });
@ -433,4 +451,12 @@ export default class SystemService {
} }
})(res, logs); })(res, logs);
} }
public async deleteSystemLog() {
const result = await readDirs(config.systemLogPath, config.systemLogPath);
const logs = result.reverse().filter((x) => x.title.endsWith('.log'));
for (const log of logs) {
await rmPath(path.join(config.systemLogPath, log.title));
}
}
} }

View File

@ -154,6 +154,7 @@ export default class UserService {
status: LoginStatus.success, status: LoginStatus.success,
}, },
}); });
this.getLoginLog();
return { return {
code: 200, code: 200,
data: { token, lastip, lastaddr, lastlogon, retries, platform }, data: { token, lastip, lastaddr, lastlogon, retries, platform },
@ -182,6 +183,7 @@ export default class UserService {
status: LoginStatus.fail, status: LoginStatus.fail,
}, },
}); });
this.getLoginLog();
if (retries > 2) { if (retries > 2) {
const waitTime = Math.round(Math.pow(3, retries + 1)); const waitTime = Math.round(Math.pow(3, retries + 1));
return { return {
@ -215,8 +217,9 @@ export default class UserService {
(a, b) => b.info!.timestamp! - a.info!.timestamp!, (a, b) => b.info!.timestamp! - a.info!.timestamp!,
); );
if (result.length > 100) { if (result.length > 100) {
const ids = result.slice(0, result.length - 100).map((x) => x.id!);
await SystemModel.destroy({ await SystemModel.destroy({
where: { id: result[result.length - 1].id }, where: { id: ids },
}); });
} }
return result.map((x) => x.info); return result.map((x) => x.info);

View File

@ -103,6 +103,7 @@
"ip2region": "2.3.0" "ip2region": "2.3.0"
}, },
"devDependencies": { "devDependencies": {
"moment": "2.30.1",
"@ant-design/icons": "^4.7.0", "@ant-design/icons": "^4.7.0",
"@ant-design/pro-layout": "6.38.22", "@ant-design/pro-layout": "6.38.22",
"@monaco-editor/react": "4.2.1", "@monaco-editor/react": "4.2.1",

View File

@ -262,6 +262,9 @@ devDependencies:
lint-staged: lint-staged:
specifier: ^13.0.3 specifier: ^13.0.3
version: 13.2.2 version: 13.2.2
moment:
specifier: 2.30.1
version: 2.30.1
monaco-editor: monaco-editor:
specifier: 0.33.0 specifier: 0.33.0
version: 0.33.0 version: 0.33.0
@ -855,7 +858,7 @@ packages:
'@babel/runtime': 7.23.1 '@babel/runtime': 7.23.1
antd: 4.24.10(react-dom@18.2.0)(react@18.2.0) antd: 4.24.10(react-dom@18.2.0)(react@18.2.0)
classnames: 2.3.2 classnames: 2.3.2
moment: 2.29.4 moment: 2.30.1
rc-util: 5.37.0(react-dom@18.2.0)(react@18.2.0) rc-util: 5.37.0(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
@ -6263,7 +6266,7 @@ packages:
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
intl: 1.2.5 intl: 1.2.5
lodash: 4.17.21 lodash: 4.17.21
moment: 2.29.4 moment: 2.30.1
qiankun: 2.10.8 qiankun: 2.10.8
react-intl: 3.12.1(react@18.2.0) react-intl: 3.12.1(react@18.2.0)
react-redux: 8.0.7(@types/react-dom@18.2.4)(@types/react@18.2.8)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) react-redux: 8.0.7(@types/react-dom@18.2.4)(@types/react@18.2.8)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1)
@ -6849,7 +6852,7 @@ packages:
classnames: 2.3.2 classnames: 2.3.2
copy-to-clipboard: 3.3.3 copy-to-clipboard: 3.3.3
lodash: 4.17.21 lodash: 4.17.21
moment: 2.29.4 moment: 2.30.1
rc-cascader: 3.7.2(react-dom@18.2.0)(react@18.2.0) rc-cascader: 3.7.2(react-dom@18.2.0)(react@18.2.0)
rc-checkbox: 3.0.1(react-dom@18.2.0)(react@18.2.0) rc-checkbox: 3.0.1(react-dom@18.2.0)(react@18.2.0)
rc-collapse: 3.4.2(react-dom@18.2.0)(react@18.2.0) rc-collapse: 3.4.2(react-dom@18.2.0)(react@18.2.0)
@ -9394,7 +9397,7 @@ packages:
/file-stream-rotator@0.6.1: /file-stream-rotator@0.6.1:
resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==}
dependencies: dependencies:
moment: 2.29.4 moment: 2.30.1
dev: false dev: false
/file-uri-to-path@2.0.0: /file-uri-to-path@2.0.0:
@ -11619,11 +11622,11 @@ packages:
/moment-timezone@0.5.43: /moment-timezone@0.5.43:
resolution: {integrity: sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==} resolution: {integrity: sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==}
dependencies: dependencies:
moment: 2.29.4 moment: 2.30.1
dev: false dev: false
/moment@2.29.4: /moment@2.30.1:
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
/monaco-editor@0.33.0: /monaco-editor@0.33.0:
resolution: {integrity: sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==} resolution: {integrity: sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==}
@ -13757,7 +13760,7 @@ packages:
classnames: 2.3.2 classnames: 2.3.2
date-fns: 2.30.0 date-fns: 2.30.0
dayjs: 1.11.8 dayjs: 1.11.8
moment: 2.29.4 moment: 2.30.1
rc-trigger: 5.3.4(react-dom@18.2.0)(react@18.2.0) rc-trigger: 5.3.4(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.37.0(react-dom@18.2.0)(react@18.2.0) rc-util: 5.37.0(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0 react: 18.2.0
@ -15115,7 +15118,7 @@ packages:
dottie: 2.0.3 dottie: 2.0.3
inflection: 1.13.4 inflection: 1.13.4
lodash: 4.17.21 lodash: 4.17.21
moment: 2.29.4 moment: 2.30.1
moment-timezone: 0.5.43 moment-timezone: 0.5.43
pg-connection-string: 2.6.0 pg-connection-string: 2.6.0
retry-as-promised: 7.0.4 retry-as-promised: 7.0.4

View File

@ -41,8 +41,8 @@ function run() {
.replace(/"/g, '\\"') .replace(/"/g, '\\"')
.replace(/\$/g, '\\$'); .replace(/\$/g, '\\$');
command = `${command} && eval '${escapeTaskBefore}'`; command = `${command} && eval '${escapeTaskBefore}'`;
}
console.log('执行前置命令\n'); console.log('执行前置命令\n');
}
const res = execSync( const res = execSync(
`${command} && echo -e '${splitStr}' && NODE_OPTIONS= node -p 'JSON.stringify(process.env)'"`, `${command} && echo -e '${splitStr}' && NODE_OPTIONS= node -p 'JSON.stringify(process.env)'"`,
{ {
@ -55,7 +55,9 @@ function run() {
process.env[key] = newEnvObject[key]; process.env[key] = newEnvObject[key];
} }
console.log(output); console.log(output);
if (task_before) {
console.log('执行前置命令结束\n'); console.log('执行前置命令结束\n');
}
} catch (error) { } catch (error) {
if (!error.message.includes('spawnSync /bin/sh E2BIG')) { if (!error.message.includes('spawnSync /bin/sh E2BIG')) {
console.log(`run task before error: `, error); console.log(`run task before error: `, error);

View File

@ -46,11 +46,11 @@ def run():
if task_before: if task_before:
escape_task_before = task_before.replace('"', '\\"').replace("$", "\\$") escape_task_before = task_before.replace('"', '\\"').replace("$", "\\$")
command += f" && eval '{escape_task_before}'" command += f" && eval '{escape_task_before}'"
print("执行前置命令\n")
python_command = "PYTHONPATH= python3 -c 'import os, json; print(json.dumps(dict(os.environ)))'" python_command = "PYTHONPATH= python3 -c 'import os, json; print(json.dumps(dict(os.environ)))'"
command += f" && echo -e '{split_str}' && {python_command}\"" command += f" && echo -e '{split_str}' && {python_command}\""
print("执行前置命令\n")
res = subprocess.check_output(command, shell=True, encoding="utf-8") res = subprocess.check_output(command, shell=True, encoding="utf-8")
output, env_str = res.split(split_str) output, env_str = res.split(split_str)
@ -60,6 +60,7 @@ def run():
os.environ[key] = value os.environ[key] = value
print(output) print(output)
if task_before:
print("执行前置命令结束") print("执行前置命令结束")
except subprocess.CalledProcessError as error: except subprocess.CalledProcessError as error:

View File

@ -462,3 +462,8 @@ body[data-dark='true'] {
--antd-arrow-background-color: rgb(24, 26, 27); --antd-arrow-background-color: rgb(24, 26, 27);
} }
} }
.ant-tabs-content-holder {
flex: 1;
overflow-y: auto;
}

View File

@ -124,15 +124,14 @@ 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 [systemLogData, setSystemLogData] = useState<string>('');
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);
useResizeObserver(containergRef, (entry) => { useResizeObserver(containergRef, (entry) => {
const _height = entry.target.parentElement?.parentElement?.offsetHeight; const _height = entry.target.parentElement?.parentElement?.offsetHeight;
if (_height && height !== _height - 66) { if (_height && height !== _height - 110) {
setHeight(_height - 66); setHeight(_height - 110);
} }
}); });
@ -253,19 +252,6 @@ const Setting = () => {
}); });
}; };
const getSystemLog = () => {
request
.get<Blob>(`${config.apiPrefix}system/log`, {
responseType: 'blob',
})
.then(async (res) => {
setSystemLogData(await res.text());
})
.catch((error: any) => {
console.log(error);
});
};
const tabChange = (activeKey: string) => { const tabChange = (activeKey: string) => {
setTabActiveKey(activeKey); setTabActiveKey(activeKey);
if (activeKey === 'app') { if (activeKey === 'app') {
@ -274,8 +260,6 @@ const Setting = () => {
getLoginLog(); getLoginLog();
} else if (activeKey === 'notification') { } else if (activeKey === 'notification') {
getNotification(); getNotification();
} else if (activeKey === 'syslog') {
getSystemLog();
} }
}; };
@ -315,12 +299,14 @@ const Setting = () => {
: [] : []
} }
> >
<div ref={containergRef}> <div ref={containergRef} style={{ height: '100%' }}>
<Tabs <Tabs
style={{ height: '100%' }}
defaultActiveKey="security" defaultActiveKey="security"
size="small" size="small"
tabPosition="top" tabPosition="top"
onChange={tabChange} onChange={tabChange}
destroyInactiveTabPane
items={[ items={[
...(!isDemoEnv ...(!isDemoEnv
? [ ? [
@ -356,9 +342,7 @@ const Setting = () => {
{ {
key: 'syslog', key: 'syslog',
label: intl.get('系统日志'), label: intl.get('系统日志'),
children: ( children: <SystemLog height={height} theme={theme} />,
<SystemLog data={systemLogData} height={height} theme={theme} />
),
}, },
{ {
key: 'login', key: 'login',
@ -383,7 +367,7 @@ const Setting = () => {
children: <About systemInfo={systemInfo} />, children: <About systemInfo={systemInfo} />,
}, },
]} ]}
></Tabs> />
</div> </div>
<AppModal <AppModal
visible={isModalVisible} visible={isModalVisible}

View File

@ -1,13 +1,39 @@
import React, { useRef } from 'react'; import React, { useRef, useState } from 'react';
import CodeMirror from '@uiw/react-codemirror'; import CodeMirror from '@uiw/react-codemirror';
import { Button } from 'antd'; import { Button, DatePicker, Empty, message, Spin } from 'antd';
import { import {
VerticalAlignBottomOutlined, VerticalAlignBottomOutlined,
VerticalAlignTopOutlined, VerticalAlignTopOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { request } from '@/utils/http';
import config from '@/utils/config';
import { useRequest } from 'ahooks';
import moment from 'moment';
const SystemLog = ({ data, height, theme }: any) => { const { RangePicker } = DatePicker;
const SystemLog = ({ height, theme }: any) => {
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
const panelVisiableRef = useRef<[string, string] | false>();
const [range, setRange] = useState<string[]>(['', '']);
const [systemLogData, setSystemLogData] = useState<string>('');
const { loading, refresh } = useRequest(
() => {
return request.get<Blob>(
`${config.apiPrefix}system/log?startTime=${range[0]}&endTime=${range[1]}`,
{
responseType: 'blob',
},
);
},
{
refreshDeps: [range],
async onSuccess(res) {
setSystemLogData(await res.text());
},
},
);
const scrollTo = (position: 'start' | 'end') => { const scrollTo = (position: 'start' | 'end') => {
editorRef.current.scrollDOM.scrollTo({ editorRef.current.scrollDOM.scrollTo({
@ -15,11 +41,56 @@ const SystemLog = ({ data, height, theme }: any) => {
}); });
}; };
const deleteLog = () => {
request.delete(`${config.apiPrefix}system/log`).then((x) => {
message.success('删除成功');
refresh();
});
};
return ( return (
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<div>
<RangePicker
style={{ marginBottom: 12, marginRight: 12 }}
disabledDate={(date) =>
date > moment() || date < moment().subtract(7, 'days')
}
defaultValue={[moment(), moment()]}
onOpenChange={(v) => {
panelVisiableRef.current = v ? ['', ''] : false;
}}
onCalendarChange={(_, dates, { range }) => {
if (
!panelVisiableRef.current ||
typeof panelVisiableRef.current === 'boolean'
) {
return;
}
if (range === 'start') {
panelVisiableRef.current[0] = dates[0];
}
if (range === 'end') {
panelVisiableRef.current[1] = dates[1];
}
if (panelVisiableRef.current[0] && panelVisiableRef.current[1]) {
setRange(dates);
}
}}
/>
<Button
onClick={() => {
deleteLog();
}}
>
</Button>
</div>
{systemLogData ? (
<>
<CodeMirror <CodeMirror
maxHeight={`${height}px`} maxHeight={`${height}px`}
value={data} value={systemLogData}
onCreateEditor={(view) => { onCreateEditor={(view) => {
editorRef.current = view; editorRef.current = view;
}} }}
@ -37,20 +108,26 @@ const SystemLog = ({ data, height, theme }: any) => {
}} }}
> >
<Button <Button
size='small' size="small"
icon={<VerticalAlignTopOutlined />} icon={<VerticalAlignTopOutlined />}
onClick={() => { onClick={() => {
scrollTo('start'); scrollTo('start');
}} }}
/> />
<Button <Button
size='small' size="small"
icon={<VerticalAlignBottomOutlined />} icon={<VerticalAlignBottomOutlined />}
onClick={() => { onClick={() => {
scrollTo('end'); scrollTo('end');
}} }}
/> />
</div> </div>
</>
) : loading ? (
<Spin />
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</div> </div>
); );
}; };