|
|
|
@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
|
|
|
import { Input, Button, Card, Typography, Space, Alert, Spin, Table, Tag } from 'antd';
|
|
|
|
|
|
|
|
import { useSearchParams } from 'react-router-dom';
|
|
|
|
|
|
|
|
import { postJSON } from "@haina/utils-request";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { Title, Text } = Typography;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const AkamaiLog = () => {
|
|
|
|
|
|
|
|
const [searchParams] = useSearchParams();
|
|
|
|
|
|
|
|
const ip = searchParams.get('ip');
|
|
|
|
|
|
|
|
const [log, setLog] = useState([]);// 日志数据
|
|
|
|
|
|
|
|
const [loading, setLoading] = useState(false);// 加载状态
|
|
|
|
|
|
|
|
const [error, setError] = useState('');// 错误信息
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 静态资源扩展名过滤
|
|
|
|
|
|
|
|
const STATIC_EXTENSIONS = [
|
|
|
|
|
|
|
|
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico',
|
|
|
|
|
|
|
|
'.css', '.scss', '.less',
|
|
|
|
|
|
|
|
'.js', '.jsx', '.ts', '.tsx', '.map',
|
|
|
|
|
|
|
|
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
|
|
|
|
|
|
'.mp4', '.mp3', '.avi', '.mov', '.flv', '.webm', '.m3u8',
|
|
|
|
|
|
|
|
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
|
|
|
|
|
|
'.zip', '.tar', '.gz', '.rar', '.7z', '.xml', '.json', '.txt',
|
|
|
|
|
|
|
|
'.php'
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// URL路径过滤
|
|
|
|
|
|
|
|
const FILTER_URLS = [
|
|
|
|
|
|
|
|
'/api/', '/admin/', '/wp-admin/',
|
|
|
|
|
|
|
|
'robots.txt', 'sitemap.xml', '/404', '/.well-known/'
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤日志数据
|
|
|
|
|
|
|
|
const filterLogData = (data) => {
|
|
|
|
|
|
|
|
if (!Array.isArray(data)) return [];
|
|
|
|
|
|
|
|
return data.filter(item => {
|
|
|
|
|
|
|
|
if (!item.request_path) return true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const fullUrl = `${item.request_host || ''}${item.request_path}`;
|
|
|
|
|
|
|
|
const requestPath = item.request_path.toLowerCase();
|
|
|
|
|
|
|
|
const hasStaticExtension = STATIC_EXTENSIONS.some(ext =>
|
|
|
|
|
|
|
|
requestPath.endsWith(ext)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
const hasFilteredUrl = FILTER_URLS.some(url =>
|
|
|
|
|
|
|
|
fullUrl.includes(url) || requestPath.includes(url)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return !hasStaticExtension && !hasFilteredUrl;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
|
|
const fetchLog = async () => {
|
|
|
|
|
|
|
|
if (!ip) {
|
|
|
|
|
|
|
|
setError('未获取到 IP 地址');
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
|
|
setError('');
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const res = await postJSON(`http://202.103.69.110:8010/webhook/60601af9-dbd7-4b01-8718-a1c05018b4be`,
|
|
|
|
|
|
|
|
{ ip: ip }
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
// console.log('日志数据:', res);
|
|
|
|
|
|
|
|
const filteredData = filterLogData(res);
|
|
|
|
|
|
|
|
setLog(filteredData);
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
console.error('请求日志失败:', err);
|
|
|
|
|
|
|
|
setError('日志加载失败,请稍后重试');
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
fetchLog();
|
|
|
|
|
|
|
|
}, [ip]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 表格列配置
|
|
|
|
|
|
|
|
const columns = [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
title: '来源地址',
|
|
|
|
|
|
|
|
dataIndex: 'referer',
|
|
|
|
|
|
|
|
key: 'referer',
|
|
|
|
|
|
|
|
width: 250,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
// {
|
|
|
|
|
|
|
|
// title: '流量(bytes)',
|
|
|
|
|
|
|
|
// dataIndex: 'bytes',
|
|
|
|
|
|
|
|
// key: 'bytes',
|
|
|
|
|
|
|
|
// width: 100,
|
|
|
|
|
|
|
|
// },
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
title: '请求路径',
|
|
|
|
|
|
|
|
key: 'request_path',
|
|
|
|
|
|
|
|
width: 300,
|
|
|
|
|
|
|
|
render: (_, record) => {
|
|
|
|
|
|
|
|
return record.request_host + '/' + record.request_path + (record.query_str !== '-' ? '?' + record.query_str : '');
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
title: '请求方法',
|
|
|
|
|
|
|
|
dataIndex: 'request_method',
|
|
|
|
|
|
|
|
key: 'request_method',
|
|
|
|
|
|
|
|
width: 80,
|
|
|
|
|
|
|
|
render: (method) => <Tag color="blue">{method}</Tag>,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
title: '状态码',
|
|
|
|
|
|
|
|
dataIndex: 'status_code',
|
|
|
|
|
|
|
|
key: 'status_code',
|
|
|
|
|
|
|
|
width: 80,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
title: '请求时间',
|
|
|
|
|
|
|
|
dataIndex: 'request_time',
|
|
|
|
|
|
|
|
key: 'request_time',
|
|
|
|
|
|
|
|
width: 180,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
<div style={{ padding: '20px' }}>
|
|
|
|
|
|
|
|
<Title level={2}>Akamai 日志查询</Title>
|
|
|
|
|
|
|
|
{error && <Alert message={error} type="error" showIcon style={{ marginBottom: 16 }} />}
|
|
|
|
|
|
|
|
<Card size="small" style={{ marginBottom: 16 }}>
|
|
|
|
|
|
|
|
<Text strong>当前查询 IP:</Text>
|
|
|
|
|
|
|
|
<Text>{ip || '未获取到ip'}</Text>
|
|
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
<Spin spinning={loading}>
|
|
|
|
|
|
|
|
<Table
|
|
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
|
|
columns={columns}
|
|
|
|
|
|
|
|
dataSource={log}
|
|
|
|
|
|
|
|
bordered
|
|
|
|
|
|
|
|
size="middle"
|
|
|
|
|
|
|
|
pagination={false}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</Spin>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export default AkamaiLog;
|