SecHub/index.php
2026-06-01 00:24:51 +08:00

829 lines
26 KiB
PHP
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.

<?php
/**
* SecHub - 网络安全工具导航页(紧凑版)
*/
// 定义路径
$jsonDir = __DIR__ . '/assets/json/';
$dbDir = __DIR__ . '/assets/db/';
$dbPath = $dbDir . 'sechub.db';
// 确保数据库目录存在
if (!is_dir($dbDir)) {
mkdir($dbDir, 0755, true);
}
// 引入数据库类
require_once __DIR__ . '/db.php';
// 初始化数据库并同步数据
try {
$database = new SecHubDatabase($dbPath, $jsonDir);
if (!file_exists($dbPath)) {
error_log("数据库文件不存在,将在同步时创建");
}
$database->syncJsonToDatabase();
// 获取栏目配置
$sections = $database->getSectionsConfig();
} catch (Exception $e) {
error_log("数据库初始化失败: " . $e->getMessage());
$sections = [];
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="./assets/imgs/favicon.ico" type="image/x-icon">
<title>SecHub简洁版</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #f5f7fa;
--bg-secondary: #ffffff;
--text-primary: #2c3e50;
--text-secondary: #7f8c8d;
--border-color: #e0e6ed;
--accent-color: #3498db;
--hover-bg: #f0f4f8;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
body.dark-mode {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--text-primary: #e0e0e0;
--text-secondary: #999999;
--border-color: #404040;
--accent-color: #4a9eff;
--hover-bg: #3a3a3a;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.4;
transition: all 0.3s ease;
}
.container {
max-width: 1600px;
margin: 0 auto;
padding: 15px;
}
header {
text-align: center;
margin-bottom: 20px;
position: relative;
}
h1 {
font-size: 1.8em;
margin-bottom: 5px;
color: var(--text-primary);
}
header p {
font-size: 0.9em;
color: var(--text-secondary);
margin-bottom: 15px;
}
/* 全局搜索框 */
.search-container {
position: relative;
max-width: 600px;
margin: 0 auto 15px;
}
.search-input {
width: 100%;
padding: 10px 15px;
border: 2px solid var(--border-color);
border-radius: 6px;
font-size: 0.95em;
background-color: var(--bg-secondary);
color: var(--text-primary);
transition: all 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
.search-results {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 6px;
box-shadow: var(--shadow);
max-height: 500px;
overflow-y: auto;
z-index: 1000;
margin-top: 5px;
}
.search-result-section {
padding: 10px;
border-bottom: 1px solid var(--border-color);
}
.search-result-title {
font-size: 0.9em;
color: var(--accent-color);
margin-bottom: 8px;
font-weight: 600;
}
.no-results {
padding: 20px;
text-align: center;
color: var(--text-secondary);
}
/* 主题切换按钮 */
.theme-toggle {
position: fixed;
top: 15px;
right: 65px;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 50%;
width: 40px;
height: 40px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
box-shadow: var(--shadow);
transition: all 0.3s ease;
z-index: 100;
}
.theme-toggle:hover {
transform: scale(1.1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* 视图切换按钮 */
.view-toggle {
position: fixed;
top: 15px;
right: 15px;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 50%;
width: 40px;
height: 40px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
box-shadow: var(--shadow);
transition: all 0.3s ease;
z-index: 100;
}
.view-toggle:hover {
transform: scale(1.1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.view-tooltip {
position: absolute;
bottom: -35px;
left: 50%;
transform: translateX(-50%);
background-color: var(--text-primary);
color: var(--bg-secondary);
padding: 4px 8px;
border-radius: 4px;
font-size: 0.75em;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
pointer-events: none;
}
.view-toggle:hover .view-tooltip {
opacity: 1;
visibility: visible;
}
/* 视图切换下拉菜单 */
.view-menu {
position: fixed;
top: 60px;
right: 15px;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 8px 0;
min-width: 150px;
z-index: 101;
display: none;
}
.view-menu.show {
display: block;
}
.view-menu-item {
display: flex;
align-items: center;
padding: 10px 16px;
color: var(--text-primary);
text-decoration: none;
transition: background-color 0.2s ease;
cursor: pointer;
border: none;
background: none;
width: 100%;
font-size: 0.9em;
text-align: left;
}
.view-menu-item:hover {
background-color: var(--hover-bg);
}
.view-menu-item.active {
background-color: var(--accent-color);
color: white;
}
.view-menu-icon {
margin-right: 10px;
font-size: 1.1em;
}
/* 栏目区域 */
.section {
margin-bottom: 20px;
background-color: var(--bg-secondary);
border-radius: 8px;
padding: 12px;
box-shadow: var(--shadow);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid var(--accent-color);
}
.section-title {
font-size: 1.1em;
font-weight: 600;
color: var(--text-primary);
}
.section-search {
width: 200px;
padding: 6px 10px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 0.85em;
background-color: var(--bg-primary);
color: var(--text-primary);
transition: all 0.3s ease;
}
.section-search:focus {
outline: none;
border-color: var(--accent-color);
}
/* 紧凑网格布局 */
.compact-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 8px;
}
/* 紧凑卡片 */
.compact-card {
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 10px 12px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.compact-card:hover {
background-color: var(--hover-bg);
border-color: var(--accent-color);
transform: translateY(-2px);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.12);
}
.compact-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 3px;
height: 100%;
background-color: var(--accent-color);
opacity: 0;
transition: opacity 0.2s ease;
}
.compact-card:hover::before {
opacity: 1;
}
.compact-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 6px;
}
.compact-card-title {
font-size: 0.95em;
font-weight: 600;
color: var(--text-primary);
margin: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 8px;
}
.compact-card-link {
font-size: 0.75em;
color: var(--accent-color);
text-decoration: none;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
.compact-card-link:hover {
text-decoration: underline;
}
.compact-card-description {
font-size: 0.8em;
color: var(--text-secondary);
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 20px;
color: var(--text-secondary);
font-size: 0.9em;
}
/* 页脚 */
.panel-footer {
text-align: center;
padding: 20px 0;
margin-top: 30px;
border-top: 1px solid var(--border-color);
}
.footer-content {
color: var(--text-secondary);
font-size: 0.85em;
}
.footer-content a {
color: var(--accent-color);
text-decoration: none;
}
.footer-content a:hover {
text-decoration: underline;
}
.beian-info {
margin-top: 8px;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.beian-link {
color: var(--text-secondary);
text-decoration: none;
display: flex;
align-items: center;
gap: 4px;
}
.beian-link:hover {
color: var(--accent-color);
}
.beian-divider {
color: var(--text-secondary);
}
.beian-icon {
vertical-align: middle;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.compact-grid {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
h1 {
font-size: 1.5em;
}
.compact-grid {
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 6px;
}
.section-header {
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.section-search {
width: 100%;
}
.theme-toggle {
top: 10px;
right: 60px;
width: 35px;
height: 35px;
font-size: 1em;
}
.view-toggle {
top: 10px;
right: 10px;
width: 35px;
height: 35px;
font-size: 1em;
}
}
@media (max-width: 480px) {
.compact-grid {
grid-template-columns: 1fr;
}
.compact-card {
padding: 8px 10px;
}
.compact-card-title {
font-size: 0.9em;
}
.compact-card-description {
font-size: 0.75em;
}
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-primary);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>SecHub 网安工具集</h1>
<p>高密度浏览模式 | 一屏查看更多工具</p>
<!-- 全局搜索框 -->
<div class="search-container">
<input type="text"
id="global-search"
class="search-input"
placeholder="🔍 搜索工具名称、描述或链接..."
autocomplete="off">
<div id="search-results" class="search-results"></div>
</div>
<button id="theme-toggle" class="theme-toggle" title="点击切换白天/黑夜模式">
<span class="theme-icon">🌙</span>
</button>
<button id="view-toggle" class="view-toggle" title="切换视图">
<span class="view-icon">📑</span>
</button>
<div id="view-menu" class="view-menu">
<button class="view-menu-item active">
<span class="view-menu-icon">📋</span>
<span>简洁视图</span>
</button>
<button class="view-menu-item" onclick="window.location.href='full.php'">
<span class="view-menu-icon">📄</span>
<span>标准视图</span>
</button>
<button class="view-menu-item" onclick="window.location.href='table.php'">
<span class="view-menu-icon">📊</span>
<span>表格视图</span>
</button>
</div>
</header>
<?php foreach ($sections as $key => $config): ?>
<?php
$items = $database->getItemsBySection($key);
$jsonFile = $key . '.json';
?>
<section class="section" data-section="<?= htmlspecialchars($key) ?>">
<div class="section-header">
<h2 class="section-title">
<?= htmlspecialchars($config['title']) ?>
</h2>
<!-- 单项搜索框 -->
<input type="text"
class="section-search"
data-section="<?= htmlspecialchars($key) ?>"
placeholder="🔍 在此栏目中搜索..."
autocomplete="off">
</div>
<?php if (empty($items)): ?>
<div class="empty-state">
<p>暂无数据</p>
</div>
<?php else: ?>
<div class="compact-grid" data-section-items="<?= htmlspecialchars($key) ?>">
<?php foreach ($items as $item): ?>
<div class="compact-card" onclick="window.open('<?= htmlspecialchars($item['url'] ?? '#') ?>', '_blank')">
<div class="compact-card-header">
<h3 class="compact-card-title"><?= htmlspecialchars($item['name'] ?? '未命名') ?></h3>
<a href="<?= htmlspecialchars($item['url'] ?? '#') ?>" target="_blank" class="compact-card-link" onclick="event.stopPropagation()"><?= htmlspecialchars($item['url'] ?? '') ?></a>
</div>
<p class="compact-card-description"><?= htmlspecialchars($item['description'] ?? '暂无简介') ?></p>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
<?php endforeach; ?>
<footer class="panel-footer">
<div class="footer-content">
<p class="copyright">&copy; <?= date('Y') ?> <a href="https://git.masonliu.com/MasonLiu/SecHub" target="_blank" rel="noopener noreferrer">SecHub - 网络安全工具集</a></p>
<p class="copyright">Gaming Master Cybersecurity | MasonLiu</p>
<div class="beian-info">
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer" class="beian-link">蜀ICP备2026025173号</a>
<span class="beian-divider">|</span>
<a href="https://beian.mps.gov.cn/#/query/webSearch?code=51162302000285" target="_blank" rel="noopener noreferrer" class="beian-link">
<img src="/assets/imgs/beian.png" alt="公安备案" width="16" height="16" class="beian-icon"/>
<span>川公网安备51162302000285号</span>
</a>
</div>
</div>
</footer>
</div>
<script>
// HTML转义函数
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 渲染紧凑卡片(用于搜索结果)
function renderCompactCardJS(item) {
const name = item.name || '未命名';
const url = item.url || '#';
const description = item.description || '暂无简介';
return `
<div class="compact-card" onclick="window.open('${url}', '_blank')">
<div class="compact-card-header">
<h3 class="compact-card-title">${escapeHtml(name)}</h3>
<a href="${url}" target="_blank" class="compact-card-link" onclick="event.stopPropagation()">${escapeHtml(url)}</a>
</div>
<p class="compact-card-description">${escapeHtml(description)}</p>
</div>`;
}
// 全局搜索功能
const globalSearchInput = document.getElementById('global-search');
const searchResults = document.getElementById('search-results');
let searchTimeout;
globalSearchInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
const keyword = this.value.trim();
if (keyword.length === 0) {
searchResults.style.display = 'none';
return;
}
searchTimeout = setTimeout(() => {
performGlobalSearch(keyword);
}, 300);
});
function performGlobalSearch(keyword) {
fetch(`search.php?action=global&keyword=${encodeURIComponent(keyword)}`)
.then(response => response.json())
.then(data => {
displaySearchResults(data);
})
.catch(error => {
console.error('搜索失败:', error);
});
}
function displaySearchResults(results) {
searchResults.innerHTML = '';
if (Object.keys(results).length === 0) {
searchResults.innerHTML = '<div class="no-results">未找到相关结果</div>';
searchResults.style.display = 'block';
return;
}
Object.keys(results).forEach(tableName => {
const section = results[tableName];
const sectionDiv = document.createElement('div');
sectionDiv.className = 'search-result-section';
const title = document.createElement('h3');
title.className = 'search-result-title';
title.textContent = section.section;
sectionDiv.appendChild(title);
const cardsGrid = document.createElement('div');
cardsGrid.className = 'compact-grid';
section.items.forEach(item => {
const cardDiv = document.createElement('div');
cardDiv.innerHTML = renderCompactCardJS(item);
cardsGrid.appendChild(cardDiv.firstElementChild);
});
sectionDiv.appendChild(cardsGrid);
searchResults.appendChild(sectionDiv);
});
searchResults.style.display = 'block';
}
// 单项搜索功能
document.querySelectorAll('.section-search').forEach(input => {
input.addEventListener('input', function() {
const section = this.dataset.section;
const keyword = this.value.trim().toLowerCase();
const cardsGrid = document.querySelector(`[data-section-items="${section}"]`);
if (!cardsGrid) return;
const cards = cardsGrid.querySelectorAll('.compact-card');
cards.forEach(card => {
const title = card.querySelector('.compact-card-title').textContent.toLowerCase();
const description = card.querySelector('.compact-card-description').textContent.toLowerCase();
const link = card.querySelector('.compact-card-link').textContent.toLowerCase();
if (keyword === '' ||
title.includes(keyword) ||
description.includes(keyword) ||
link.includes(keyword)) {
card.style.display = 'block';
} else {
card.style.display = 'none';
}
});
});
});
// 点击其他地方关闭搜索结果
document.addEventListener('click', function(e) {
if (!e.target.closest('.search-container')) {
searchResults.style.display = 'none';
}
});
// 黑夜模式切换功能
(function() {
const themeToggle = document.getElementById('theme-toggle');
const themeIcon = themeToggle.querySelector('.theme-icon');
const body = document.body;
// 从 localStorage 读取主题设置
const currentTheme = localStorage.getItem('theme') || 'light';
// 应用保存的主题
if (currentTheme === 'dark') {
body.classList.add('dark-mode');
themeIcon.textContent = '☀️';
}
// 切换主题
themeToggle.addEventListener('click', function() {
body.classList.toggle('dark-mode');
if (body.classList.contains('dark-mode')) {
themeIcon.textContent = '☀️';
localStorage.setItem('theme', 'dark');
} else {
themeIcon.textContent = '🌙';
localStorage.setItem('theme', 'light');
}
});
})();
// 视图切换下拉菜单
(function() {
const viewToggle = document.getElementById('view-toggle');
const viewMenu = document.getElementById('view-menu');
// 点击按钮显示/隐藏菜单
viewToggle.addEventListener('click', function(e) {
e.stopPropagation();
viewMenu.classList.toggle('show');
});
// 点击其他地方关闭菜单
document.addEventListener('click', function(e) {
if (!viewToggle.contains(e.target) && !viewMenu.contains(e.target)) {
viewMenu.classList.remove('show');
}
});
// ESC 键关闭菜单
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
viewMenu.classList.remove('show');
}
});
})();
</script>
</body>
</html>