PrivHunterAI/index.html
2025-04-24 11:22:55 +08:00

572 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PrivHunterAI</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<style>
body {
padding: 20px;
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
font-family: 'Roboto', sans-serif;
}
.header {
background: linear-gradient(135deg, #4a90e2, #1f78d1);
border-radius: 10px;
padding: 30px;
color: white;
position: relative;
overflow: hidden;
}
.header::after {
content: "";
position: absolute;
bottom: -50px;
right: -50px;
width: 300px;
height: 300px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
}
.header h1 {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 5px;
}
.header p {
font-size: 1rem;
opacity: 0.9;
}
.refresh-btn {
background: white;
color: #4a90e2;
border-radius: 5px;
padding: 8px 20px;
border: none;
font-size: 0.9rem;
cursor: pointer;
margin-top: 20px;
transition: all 0.3s ease;
}
.refresh-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.stats-container {
display: flex;
gap: 20px;
margin-top: 20px;
}
.stat-card {
flex: 1;
background: white;
border-radius: 10px;
padding: 25px 20px;
text-align: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 12px rgba(0,0,0,0.08);
}
.stat-number {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 10px;
}
.total .stat-number { color: #4a90e2; }
.vulnerable .stat-number { color: #e74c3c; }
.unknown .stat-number { color: #f39c12; }
.safe .stat-number { color: #2ecc71; }
.stat-label {
font-size: 0.9rem;
color: #666;
}
.results-section {
background: white;
border-radius: 10px;
padding: 20px;
margin-top: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.filter-select {
padding: 8px 15px;
border-radius: 5px;
border: 1px solid #ddd;
margin-right: 10px;
outline: none;
transition: all 0.3s ease;
}
.filter-select:focus {
border-color: #4a90e2;
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
}
.filter-btn {
background: #4a90e2;
color: white;
border: none;
border-radius: 5px;
padding: 8px 20px;
cursor: pointer;
transition: all 0.3s ease;
}
.filter-btn:hover {
background: #3a7bc8;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(74, 144, 226, 0.3);
}
.empty-state {
text-align: center;
padding: 50px 20px;
color: #666;
}
.empty-state i {
font-size: 50px;
color: #f39c12;
margin-bottom: 20px;
}
footer {
text-align: center;
padding: 20px 0;
color: #666;
font-size: 0.9rem;
margin-top: 30px;
}
/* 表格样式 */
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th {
padding: 1.5rem;
text-align: center;
vertical-align: middle;
max-width: 150px;
word-wrap: break-word;
border-bottom: 0.1rem solid #e0e0e0;
}
.data-table td {
padding: 1.5rem;
text-align: left;
vertical-align: middle;
max-width: 150px;
word-wrap: break-word;
border-bottom: 0.1rem solid #e0e0e0;
}
.data-table tr:hover {
background-color: rgba(0,0,0,0.02);
}
.foldable-row {
cursor: pointer;
}
.foldable-row .result-summary {
display: flex;
align-items: center;
}
.foldable-row .result-url {
text-align: left;
font-weight: bold;
word-wrap: break-word;
max-width: 550px;
margin-right: 15px;
flex-grow: 1;
}
.foldable-row .fa-chevron-down{
text-align: right;
/* font-weight: bold; */
/* word-wrap: break-word; */
/* max-width: 550px; */
margin-right: 10px;
/* flex-grow: 1; */
}
.foldable-row .result-status {
padding: 4px 10px;
border-radius: 15px;
font-size: 0.8rem;
font-weight: bold;
}
.status-vulnerable {
margin-left: 100px;
background-color: #ffebee;
color: #e53935;
}
.status-safe {
margin-left: 100px;
background-color: #e8f5e9;
color: #388e3c;
}
.status-unknown {
margin-left: 100px;
background-color: #fff8e1;
color: #ffa000;
}
.foldable-row .result-confidence {
margin-left: 50px;
font-size: 0.9rem;
color: #666;
}
.foldable-row .result-timestamp {
margin-right: 50px;
font-size: 0.9rem;
color: #666;
}
.details-row {
white-space: pre-line;
display: none;
padding-left: 20px;
}
.details-row table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.details-row table td {
padding: 8px 0;
vertical-align: top;
}
.details-row table td:first-child {
font-weight: bold;
width: 120px;
}
.details-row.active {
display: table-row;
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: center;
margin-top: 20px;
}
.pagination button {
background: #f8f9fa;
border: 1px solid #dee2e6;
padding: 8px 15px;
margin: 0 5px;
cursor: pointer;
border-radius: 5px;
transition: all 0.3s ease;
}
.pagination button:hover {
background: #e9ecef;
}
.pagination button.active {
background: #4a90e2;
color: white;
border-color: #4a90e2;
}
.pagination button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.page-info {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
color: #666;
}
.page-info select {
margin: 0 10px;
padding: 5px 10px;
border-radius: 5px;
border: 1px solid #ddd;
}
</style>
</head>
<body>
<div class="container">
<!-- Header Section -->
<div class="header">
<h1>PrivHunterAI <small>v1.0.0</small></h1>
<p>高级智能 API 安全扫描工具。</p>
<button class="refresh-btn" onclick="fetchData()">刷新数据</button>
</div>
<!-- Statistics Cards -->
<div class="stats-container">
<div class="stat-card total">
<div class="stat-number" id="total-requests">0</div>
<div class="stat-label">总扫描请求</div>
</div>
<div class="stat-card vulnerable">
<div class="stat-number" id="vulnerable-requests">0</div>
<div class="stat-label">漏洞请求</div>
</div>
<div class="stat-card unknown">
<div class="stat-number" id="unknown-requests">0</div>
<div class="stat-label">未知状态</div>
</div>
<div class="stat-card safe">
<div class="stat-number" id="safe-requests">0</div>
<div class="stat-label">安全请求</div>
</div>
</div>
<!-- Results Section -->
<div class="results-section">
<div class="results-header">
<h3>漏洞扫描结果</h3>
<div class="filters">
<select class="filter-select" id="filter-result">
<option value="">所有状态</option>
<option value="true">漏洞请求</option>
<option value="unknown">未知状态</option>
<option value="false">安全请求</option>
</select>
<button class="filter-btn" onclick="filterData()">筛选</button>
</div>
</div>
<table class="data-table">
<tbody id="data-body">
<!-- 表格数据将通过 JavaScript 动态添加 -->
</tbody>
</table>
<!-- 分页控制 -->
<div class="pagination" id="pagination">
<select id="page-size">
<option value="5">5 条/页</option>
<option value="10" selected>10 条/页</option>
<option value="20">20 条/页</option>
<option value="50">50 条/页</option>
<option value="100">100 条/页</option>
</select>
<button onclick="prevPage()" id="prev-btn" disabled>上一页</button>
<span id="page-info">第 1 页,共 1 页</span>
<button onclick="nextPage()" id="next-btn" disabled>下一页</button>
</div>
</div>
</div>
<footer>
© 2025 PrivHunterAI专注智能安全检测领航 API 安全未来。
</footer>
<script>
// 分页相关变量
let currentPage = 1;
let itemsPerPage = 10;
let totalPages = 1;
let currentData = [];
async function fetchData(page = 1, pageSize = itemsPerPage, resultFilter = '') {
try {
document.body.style.cursor = 'wait';
// 获取统计数据
const statsResponse = await fetch('/stats');
if (!statsResponse.ok) throw new Error('Failed to fetch statistics');
const stats = await statsResponse.json();
updateStatistics(stats);
// 获取分页数据
const dataResponse = await fetch(`/data?page=${page}&pageSize=${pageSize}&result=${resultFilter}`);
if (!dataResponse.ok) throw new Error('Failed to fetch data');
const data = await dataResponse.json();
currentData = data.data;
currentPage = data.currentPage;
itemsPerPage = data.pageSize;
totalPages = data.totalPages;
updateTableData();
updatePaginationControls();
} finally {
document.body.style.cursor = 'default';
}
}
function updateStatistics(stats) {
document.getElementById('total-requests').textContent = stats.total;
document.getElementById('vulnerable-requests').textContent = stats.vulnerable;
document.getElementById('unknown-requests').textContent = stats.unknown;
document.getElementById('safe-requests').textContent = stats.safe;
}
function updateTableData() {
const tableBody = document.getElementById('data-body');
tableBody.innerHTML = '';
if (currentData.length === 0) {
const emptyRow = document.createElement('tr');
emptyRow.innerHTML = `
<td colspan="3" class="empty-state">
<i class="fas fa-search"></i>
<p>没有找到符合条件的数据</p>
</td>
`;
tableBody.appendChild(emptyRow);
return;
}
currentData.forEach(item => {
const foldableRow = document.createElement('tr');
foldableRow.className = 'foldable-row';
foldableRow.onclick = function() {
const detailsRow = this.nextElementSibling;
detailsRow.classList.toggle('active');
this.style.fontWeight = detailsRow.classList.contains('active') ? 'bold' : 'normal';
};
if (item.result === 'true') {
foldableRow.style.borderLeft = '4px solid #e74c3c';
} else if (item.result === 'unknown') {
foldableRow.style.borderLeft = '4px solid #f39c12';
} else if (item.result === 'false') {
foldableRow.style.borderLeft = '4px solid #2ecc71';
}
foldableRow.innerHTML = `
<td>
<div class="result-summary">
<span class="result-url">${item.url.split('?')[0]}</span>
</div>
</td>
<td>
<div class="result-summary">
<span class="result-status status-${item.result === 'true' ? 'vulnerable' : item.result === 'unknown' ? 'unknown' : 'safe'}">
${item.result === 'true' ? '漏洞' : item.result === 'unknown' ? '未知' : '安全'}
</span>
<span class="result-confidence">${item.confidence}</span>
</div>
</td>
<td>
<div class="fa-chevron-down">
<span class="result-timestamp">${item.timestamp}</span>
<i class="fas fa-chevron-down"></i>
</div>
</td>
`;
tableBody.appendChild(foldableRow);
const detailsRow = document.createElement('tr');
detailsRow.className = 'details-row';
detailsRow.innerHTML = `
<td colspan="3">
<table>
<tr>
<td>Method:</td>
<td>${item.method || ''}</td>
</tr>
<tr>
<td>URL:</td>
<td>${item.url || ''}</td>
</tr>
<tr>
<td>RequestA:</td>
<td>${item.requestA || ''}</td>
</tr>
<tr>
<td>RespBodyA:</td>
<td>${item.respBodyA || ''}</td>
</tr>
<tr>
<td>RequestB:</td>
<td>${item.requestB || ''}</td>
</tr>
<tr>
<td>RespBodyB:</td>
<td>${item.respBodyB || ''}</td>
</tr>
<tr>
<td>Result:</td>
<td>${item.result === 'unknown' ? '未知' : item.result === 'false' ? '安全' : '漏洞'}</td>
</tr>
<tr>
<td>Reason:</td>
<td>${item.reason || ''}</td>
</tr>
<tr>
<td>Confidence:</td>
<td>${item.confidence || ''}</td>
</tr>
</table>
</td>
`;
tableBody.appendChild(detailsRow);
});
}
function updatePaginationControls() {
document.getElementById('page-info').textContent =
`${currentPage} 页,共 ${totalPages}`;
document.getElementById('prev-btn').disabled = currentPage === 1;
document.getElementById('next-btn').disabled = currentPage === totalPages;
}
function prevPage() {
if (currentPage > 1) {
fetchData(currentPage - 1, itemsPerPage, document.getElementById('filter-result').value);
}
}
function nextPage() {
if (currentPage < totalPages) {
fetchData(currentPage + 1, itemsPerPage, document.getElementById('filter-result').value);
}
}
function filterData() {
const resultFilter = document.getElementById('filter-result').value;
fetchData(1, itemsPerPage, resultFilter);
}
document.getElementById('page-size').addEventListener('change', function() {
itemsPerPage = parseInt(this.value);
fetchData(1, itemsPerPage, document.getElementById('filter-result').value);
});
document.addEventListener('DOMContentLoaded', () => {
fetchData();
});
</script>
</body>
</html>