mirror of
				https://github.com/whyour/qinglong.git
				synced 2025-10-31 00:46:08 +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) => { | ||||
|   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); | ||||
|       } | ||||
|     }, | ||||
|   ); | ||||
| }; | ||||
|  |  | |||
|  | @ -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'); | ||||
|     } | ||||
|     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); | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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,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> | ||||
|   ); | ||||
| }; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 whyour
						whyour