mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-22 14:26:07 +08:00
系统日志增加时间筛选和清空
This commit is contained in:
parent
f6021c8157
commit
f4cb3eacf8
|
@ -340,12 +340,41 @@ export default (app: Router) => {
|
|||
},
|
||||
);
|
||||
|
||||
route.get('/log', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const systemService = Container.get(SystemService);
|
||||
await systemService.getSystemLog(res);
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
}
|
||||
});
|
||||
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,
|
||||
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);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -41,8 +41,8 @@ function run() {
|
|||
.replace(/"/g, '\\"')
|
||||
.replace(/\$/g, '\\$');
|
||||
command = `${command} && eval '${escapeTaskBefore}'`;
|
||||
console.log('执行前置命令\n');
|
||||
}
|
||||
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);
|
||||
console.log('执行前置命令结束\n');
|
||||
if (task_before) {
|
||||
console.log('执行前置命令结束\n');
|
||||
}
|
||||
} catch (error) {
|
||||
if (!error.message.includes('spawnSync /bin/sh E2BIG')) {
|
||||
console.log(`run task before error: `, error);
|
||||
|
|
|
@ -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,7 +60,8 @@ def run():
|
|||
os.environ[key] = value
|
||||
|
||||
print(output)
|
||||
print("执行前置命令结束")
|
||||
if task_before:
|
||||
print("执行前置命令结束")
|
||||
|
||||
except subprocess.CalledProcessError as error:
|
||||
print(f"run task before error: {error}")
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,42 +41,93 @@ const SystemLog = ({ data, height, theme }: any) => {
|
|||
});
|
||||
};
|
||||
|
||||
const deleteLog = () => {
|
||||
request.delete(`${config.apiPrefix}system/log`).then((x) => {
|
||||
message.success('删除成功');
|
||||
refresh();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<CodeMirror
|
||||
maxHeight={`${height}px`}
|
||||
value={data}
|
||||
onCreateEditor={(view) => {
|
||||
editorRef.current = view;
|
||||
}}
|
||||
readOnly={true}
|
||||
theme={theme.includes('dark') ? 'dark' : 'light'}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size='small'
|
||||
icon={<VerticalAlignTopOutlined />}
|
||||
onClick={() => {
|
||||
scrollTo('start');
|
||||
<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
|
||||
size='small'
|
||||
icon={<VerticalAlignBottomOutlined />}
|
||||
onClick={() => {
|
||||
scrollTo('end');
|
||||
deleteLog();
|
||||
}}
|
||||
/>
|
||||
>
|
||||
清空日志
|
||||
</Button>
|
||||
</div>
|
||||
{systemLogData ? (
|
||||
<>
|
||||
<CodeMirror
|
||||
maxHeight={`${height}px`}
|
||||
value={systemLogData}
|
||||
onCreateEditor={(view) => {
|
||||
editorRef.current = view;
|
||||
}}
|
||||
readOnly={true}
|
||||
theme={theme.includes('dark') ? 'dark' : 'light'}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<VerticalAlignTopOutlined />}
|
||||
onClick={() => {
|
||||
scrollTo('start');
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<VerticalAlignBottomOutlined />}
|
||||
onClick={() => {
|
||||
scrollTo('end');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : loading ? (
|
||||
<Spin />
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user