LogModal.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { Button, Checkbox, Form, Modal, Select, Space } from 'antd';
  4. import { DownloadOutlined, SyncOutlined } from '@ant-design/icons';
  5. import { HttpUtil, FileManager, PromiseUtil } from '@/utils';
  6. import { activateOnKey } from '@/utils/a11y';
  7. import { useMediaQuery } from '@/hooks/useMediaQuery';
  8. import { parseLogLine } from './logParse';
  9. import './LogModal.css';
  10. interface LogModalProps {
  11. open: boolean;
  12. onClose: () => void;
  13. }
  14. const AUTO_UPDATE_INTERVAL = 5000;
  15. export default function LogModal({ open, onClose }: LogModalProps) {
  16. const { t } = useTranslation();
  17. const { isMobile } = useMediaQuery();
  18. const [rows, setRows] = useState('20');
  19. const [level, setLevel] = useState('info');
  20. const [syslog, setSyslog] = useState(false);
  21. const [autoUpdate, setAutoUpdate] = useState(false);
  22. const [loading, setLoading] = useState(false);
  23. const [logs, setLogs] = useState<string[]>([]);
  24. const openRef = useRef(open);
  25. const refresh = useCallback(async () => {
  26. setLoading(true);
  27. try {
  28. const msg = await HttpUtil.post<string[]>(`/panel/api/server/logs/${rows}`, {
  29. level,
  30. syslog,
  31. });
  32. if (msg?.success) {
  33. setLogs(msg.obj || []);
  34. }
  35. await PromiseUtil.sleep(300);
  36. } finally {
  37. setLoading(false);
  38. }
  39. }, [rows, level, syslog]);
  40. const refreshRef = useRef(refresh);
  41. useEffect(() => {
  42. refreshRef.current = refresh;
  43. }, [refresh]);
  44. useEffect(() => {
  45. openRef.current = open;
  46. if (open) refresh();
  47. }, [open, refresh]);
  48. useEffect(() => {
  49. if (openRef.current) refresh();
  50. }, [rows, level, syslog, refresh]);
  51. useEffect(() => {
  52. if (!open || !autoUpdate) return;
  53. const id = setInterval(() => refreshRef.current(), AUTO_UPDATE_INTERVAL);
  54. return () => clearInterval(id);
  55. }, [open, autoUpdate]);
  56. const parsedLogs = useMemo(() => logs.map(parseLogLine), [logs]);
  57. function download() {
  58. FileManager.downloadTextFile(logs.join('\n'), 'x-ui.log');
  59. }
  60. const titleNode = (
  61. <>
  62. {t('pages.index.logs')}
  63. <SyncOutlined spin={loading} className="reload-icon" role="button" tabIndex={0} aria-label={t('refresh')} onClick={refresh} onKeyDown={activateOnKey(refresh)} />
  64. </>
  65. );
  66. return (
  67. <Modal
  68. open={open}
  69. footer={null}
  70. width={isMobile ? '100vw' : 800}
  71. style={isMobile ? { top: 0, paddingBottom: 0, maxWidth: '100vw' } : undefined}
  72. className={isMobile ? 'logmodal-mobile' : undefined}
  73. onCancel={onClose}
  74. title={titleNode}
  75. >
  76. <Form layout="inline" className="log-toolbar">
  77. <Form.Item>
  78. <Space.Compact>
  79. <Select
  80. value={rows}
  81. size="small"
  82. style={{ width: 70 }}
  83. onChange={setRows}
  84. options={[
  85. { value: '20', label: '20' },
  86. { value: '50', label: '50' },
  87. { value: '100', label: '100' },
  88. { value: '500', label: '500' },
  89. { value: '1000', label: '1000' },
  90. ]}
  91. />
  92. <Select
  93. value={level}
  94. size="small"
  95. style={{ minWidth: 95 }}
  96. onChange={setLevel}
  97. options={[
  98. { value: 'debug', label: t('pages.index.logLevelDebug') },
  99. { value: 'info', label: t('pages.index.logLevelInfo') },
  100. { value: 'notice', label: t('pages.index.logLevelNotice') },
  101. { value: 'warning', label: t('pages.index.logLevelWarning') },
  102. { value: 'err', label: t('pages.index.logLevelError') },
  103. ]}
  104. />
  105. </Space.Compact>
  106. </Form.Item>
  107. <Form.Item>
  108. <Checkbox checked={syslog} onChange={(e) => setSyslog(e.target.checked)}>
  109. SysLog
  110. </Checkbox>
  111. <Checkbox checked={autoUpdate} onChange={(e) => setAutoUpdate(e.target.checked)}>
  112. {t('pages.index.autoUpdate')}
  113. </Checkbox>
  114. </Form.Item>
  115. <Form.Item className="download-item">
  116. <Button type="primary" onClick={download} icon={<DownloadOutlined />} aria-label={t('download')} />
  117. </Form.Item>
  118. </Form>
  119. <div className={`log-container ${isMobile ? 'log-container-mobile' : ''}`}>
  120. {parsedLogs.length === 0 ? (
  121. <div className="log-empty">No Record...</div>
  122. ) : isMobile ? (
  123. parsedLogs.map((log, idx) => (
  124. <div key={idx} className="log-card">
  125. <div className="log-card-head">
  126. {log.stamp && (
  127. <span className="log-time">
  128. {log.time && <span>{log.time}</span>}
  129. {log.time && log.date ? ' ' : ''}
  130. {log.date && <span className="log-date">{log.date}</span>}
  131. </span>
  132. )}
  133. {log.levelText && (
  134. <span className={`log-level-badge ${log.levelClass}`}>{log.levelText}</span>
  135. )}
  136. </div>
  137. {(log.body || log.service) && (
  138. <div className="log-body">
  139. {log.service && <b>{log.service}</b>}
  140. {log.service && log.body ? ' ' : ''}
  141. {log.body && <span className="log-body-text">{log.body}</span>}
  142. </div>
  143. )}
  144. </div>
  145. ))
  146. ) : (
  147. parsedLogs.map((log, idx) => (
  148. <div key={idx} className="log-line">
  149. {log.stamp && <span className="log-stamp">{log.stamp}</span>}
  150. {log.stamp && log.levelText ? ' ' : ''}
  151. {log.levelText && <span className={`log-level ${log.levelClass}`}>{log.levelText}</span>}
  152. {(log.body || log.service) && (
  153. <>
  154. {(log.stamp || log.levelText) && <span> - </span>}
  155. {log.service && <b>{log.service}</b>}
  156. {log.service && log.body ? ' ' : ''}
  157. <span>{log.body}</span>
  158. </>
  159. )}
  160. </div>
  161. ))
  162. )}
  163. </div>
  164. </Modal>
  165. );
  166. }