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

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 {
const systemService = Container.get(SystemService);
await systemService.getSystemLog(res);
await systemService.getSystemLog(
res,
req.query as {
startTime?: string;
endTime?: string;
},
);
} catch (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,
promiseExec,
readDirs,
rmPath,
} from '../config/util';
import {
DependenceModel,
@ -34,6 +35,7 @@ import NotificationService from './notify';
import ScheduleService, { TaskCallbacks } from './schedule';
import SockService from './sock';
import os from 'os';
import dayjs from 'dayjs';
@Service()
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 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({
'Content-Length': sum(logs.map((x) => x.size)),
});
@ -433,4 +451,12 @@ export default class SystemService {
}
})(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,
},
});
this.getLoginLog();
return {
code: 200,
data: { token, lastip, lastaddr, lastlogon, retries, platform },
@ -182,6 +183,7 @@ export default class UserService {
status: LoginStatus.fail,
},
});
this.getLoginLog();
if (retries > 2) {
const waitTime = Math.round(Math.pow(3, retries + 1));
return {
@ -215,8 +217,9 @@ export default class UserService {
(a, b) => b.info!.timestamp! - a.info!.timestamp!,
);
if (result.length > 100) {
const ids = result.slice(0, result.length - 100).map((x) => x.id!);
await SystemModel.destroy({
where: { id: result[result.length - 1].id },
where: { id: ids },
});
}
return result.map((x) => x.info);

View File

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

View File

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

View File

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

View File

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

View File

@ -462,3 +462,8 @@ body[data-dark='true'] {
--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 [tabActiveKey, setTabActiveKey] = useState('security');
const [loginLogData, setLoginLogData] = useState<any[]>([]);
const [systemLogData, setSystemLogData] = useState<string>('');
const [notificationInfo, setNotificationInfo] = useState<any>();
const containergRef = useRef<HTMLDivElement>(null);
const [height, setHeight] = useState<number>(0);
useResizeObserver(containergRef, (entry) => {
const _height = entry.target.parentElement?.parentElement?.offsetHeight;
if (_height && height !== _height - 66) {
setHeight(_height - 66);
if (_height && height !== _height - 110) {
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) => {
setTabActiveKey(activeKey);
if (activeKey === 'app') {
@ -274,8 +260,6 @@ const Setting = () => {
getLoginLog();
} else if (activeKey === 'notification') {
getNotification();
} else if (activeKey === 'syslog') {
getSystemLog();
}
};
@ -315,12 +299,14 @@ const Setting = () => {
: []
}
>
<div ref={containergRef}>
<div ref={containergRef} style={{ height: '100%' }}>
<Tabs
style={{ height: '100%' }}
defaultActiveKey="security"
size="small"
tabPosition="top"
onChange={tabChange}
destroyInactiveTabPane
items={[
...(!isDemoEnv
? [
@ -356,9 +342,7 @@ const Setting = () => {
{
key: 'syslog',
label: intl.get('系统日志'),
children: (
<SystemLog data={systemLogData} height={height} theme={theme} />
),
children: <SystemLog height={height} theme={theme} />,
},
{
key: 'login',
@ -383,7 +367,7 @@ const Setting = () => {
children: <About systemInfo={systemInfo} />,
},
]}
></Tabs>
/>
</div>
<AppModal
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 { Button } from 'antd';
import { Button, DatePicker, Empty, message, Spin } from 'antd';
import {
VerticalAlignBottomOutlined,
VerticalAlignTopOutlined,
} 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 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') => {
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 (
<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
maxHeight={`${height}px`}
value={data}
value={systemLogData}
onCreateEditor={(view) => {
editorRef.current = view;
}}
@ -37,20 +108,26 @@ const SystemLog = ({ data, height, theme }: any) => {
}}
>
<Button
size='small'
size="small"
icon={<VerticalAlignTopOutlined />}
onClick={() => {
scrollTo('start');
}}
/>
<Button
size='small'
size="small"
icon={<VerticalAlignBottomOutlined />}
onClick={() => {
scrollTo('end');
}}
/>
</div>
</>
) : loading ? (
<Spin />
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</div>
);
};