-
风控管理
-
记录访客对本站的动态请求(含后台),完整模式逐条展示访问明细,精简模式按 IP 汇总次数;黑名单 / 自动风控对所有访问统一生效,命中即返回 403 风控提示页,后台与登录页不例外。
+
+
+
风控管理
+
记录访客对本站的动态请求(含后台),完整模式逐条展示访问明细,精简模式按 IP 汇总次数;黑名单 / 自动风控对所有访问统一生效,命中即返回 403 风控提示页,后台与登录页不例外。
+
+
+
@@ -315,7 +320,7 @@ admin_topbar('visits');
diff --git a/article/data.php b/article/data.php
deleted file mode 100644
index 92c52dd..0000000
--- a/article/data.php
+++ /dev/null
@@ -1,70 +0,0 @@
- false, 'msg' => '缺少文章 id']);
- }
- $st = $pdo->prepare('SELECT id, part, title, summary, markdown, pinned, created_at, updated_at
- FROM articles WHERE id = ? AND enabled = 1 LIMIT 1');
- $st->execute([$id]);
- $row = $st->fetch();
- if (!$row) {
- json_out(['ok' => false, 'msg' => '文章不存在或已隐藏']);
- }
- json_out(['ok' => true, 'article' => $row]);
-}
-
-// ---- 默认:list 分组元数据(分区顺序取各分区首篇 id 序;分区内置顶 → sort → id) ----
-$rows = $pdo->query('SELECT id, part, title, summary, pinned, sort, created_at, updated_at
- FROM articles WHERE enabled = 1 ORDER BY id ASC')->fetchAll();
-$parts = [];
-if ($rows) {
- $partOrder = [];
- $byPart = [];
- foreach ($rows as $r) {
- $part = trim((string)$r['part']);
- if ($part === '') {
- $part = '未分类';
- }
- if (!isset($byPart[$part])) {
- $byPart[$part] = [];
- $partOrder[] = $part;
- }
- $byPart[$part][] = $r;
- }
- foreach ($partOrder as $part) {
- $items = $byPart[$part];
- usort($items, static function (array $a, array $b): int {
- if ((int)$a['pinned'] !== (int)$b['pinned']) {
- return (int)$b['pinned'] - (int)$a['pinned'];
- }
- if ((int)$a['sort'] !== (int)$b['sort']) {
- return (int)$a['sort'] <=> (int)$b['sort'];
- }
- return (int)$a['id'] <=> (int)$b['id'];
- });
- $parts[] = ['part' => $part, 'articles' => $items];
- }
-}
-json_out(['ok' => true, 'parts' => $parts]);
diff --git a/assets/css/common.css b/assets/css/common.css
index ddbd9e3..c6e0bf8 100644
--- a/assets/css/common.css
+++ b/assets/css/common.css
@@ -184,6 +184,89 @@ select { width: auto; min-width: 150px; }
/* ---------- 首页顶部组件:空白精简模式(hero_mode=blank) ---------- */
.hero-slim { min-height: 0; padding: 16px 0 12px; }
+/* ---------- 首页顶部组件:红队/蓝队渗透测试流程模式(hero_mode=red|blue,动态步进) ---------- */
+.ptflow { display: flex; flex-direction: column; gap: 10px; }
+.ptflow-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
+.ptflow-title { font-size: 15px; font-weight: 700; color: #fff; }
+.ptflow-title .ptflow-tag { display: inline-block; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; margin-left: 8px; vertical-align: middle; letter-spacing: .5px; }
+.ptflow-red .ptflow-title .ptflow-tag { background: rgba(239, 68, 68, .22); color: #fecaca; border: 1px solid rgba(239, 68, 68, .5); }
+.ptflow-blue .ptflow-title .ptflow-tag { background: rgba(37, 99, 235, .22); color: #bfdbfe; border: 1px solid rgba(59, 130, 246, .5); }
+.ptflow-phase { margin-left: auto; font-size: 12.5px; color: rgba(255, 255, 255, .85); font-variant-numeric: tabular-nums; }
+.ptflow-track { display: flex; gap: 8px; margin: 0; padding: 0; list-style: none; position: relative; overflow: hidden; }
+.ptflow-track::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, .08) 50%, transparent 100%); animation: ptflowSweep 3.2s linear infinite; }
+@keyframes ptflowSweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
+.ptflow-step { flex: 1 1 0; min-width: 0; position: relative; padding: 9px 10px; border-radius: 10px; background: rgba(10, 22, 44, .32); border: 1px solid rgba(255, 255, 255, .18); transition: transform .35s ease, border-color .35s ease, background .35s ease, box-shadow .35s ease; }
+.ptflow-step .ptflow-ic { font-size: 16px; display: block; line-height: 1; }
+.ptflow-step b { display: block; color: #fff; font-size: 12.5px; margin-top: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.ptflow-step small { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; color: rgba(255, 255, 255, .68); font-size: 11px; line-height: 1.35; margin-top: 2px; }
+.ptflow-step.active { transform: translateY(-3px); background: rgba(10, 22, 44, .5); }
+.ptflow-red .ptflow-step.active { border-color: #ef4444; box-shadow: 0 8px 22px rgba(239, 68, 68, .35); }
+.ptflow-blue .ptflow-step.active { border-color: #3b82f6; box-shadow: 0 8px 22px rgba(59, 130, 246, .35); }
+.ptflow-step.done { opacity: .55; }
+.ptflow-step.done::after { content: '✓'; position: absolute; top: 6px; right: 8px; font-size: 10px; color: #34d399; }
+@media (max-width: 767px) {
+ .ptflow-track { flex-wrap: wrap; }
+ .ptflow-step { flex: 1 1 30%; }
+ .ptflow-phase { margin-left: 0; }
+}
+
+/* ---------- 首页顶部组件:安全态势简报(hero_mode=posture,数字增长) ---------- */
+.js-posture .vb-cell b.js-count { color: #fff; font-variant-numeric: tabular-nums; }
+
+/* ---------- 首页顶部组件:访问趋势(hero_mode=trend) ---------- */
+.trend-box { display: flex; flex-direction: column; gap: 10px; }
+.trend-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
+.trend-title { font-size: 15px; font-weight: 700; color: #fff; }
+.trend-sub { margin-left: auto; font-size: 12.5px; color: rgba(255, 255, 255, .82); }
+.trend-sub b { color: #fff; font-variant-numeric: tabular-nums; }
+.trend-chart { display: flex; align-items: flex-end; gap: 8px; height: 132px; }
+.trend-col { flex: 1 1 0; min-width: 0; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; }
+.trend-val { font-size: 12px; color: rgba(255, 255, 255, .85); font-variant-numeric: tabular-nums; margin-bottom: 4px; }
+.trend-bar-wrap { flex: 1; width: 100%; display: flex; align-items: flex-end; justify-content: center; }
+.trend-bar { display: block; width: 62%; max-width: 34px; height: 0; border-radius: 6px 6px 3px 3px; background: linear-gradient(180deg, #7dd3fc, #38bdf8); box-shadow: 0 0 12px rgba(56, 189, 248, .45); animation: trendGrow .9s cubic-bezier(.2, .8, .2, 1) forwards; }
+@keyframes trendGrow { from { height: 0; } to { height: var(--h); } }
+.trend-lbl { font-size: 11px; color: rgba(255, 255, 255, .8); margin-top: 5px; font-variant-numeric: tabular-nums; }
+.trend-wk { font-size: 10.5px; color: rgba(255, 255, 255, .55); }
+
+/* ---------- 首页顶部组件:大字时钟(hero_mode=clock) ---------- */
+.clock-box { display: flex; flex-direction: column; align-items: center; gap: 2px; }
+.ch-greet { font-size: 13px; letter-spacing: 2px; color: rgba(255, 255, 255, .8); }
+.ch-time { font-size: clamp(34px, 8vw, 58px); font-weight: 700; line-height: 1.05; color: #fff; font-variant-numeric: tabular-nums; letter-spacing: 2px; text-shadow: 0 2px 18px rgba(0, 0, 0, .35); }
+.ch-date { font-size: 13.5px; color: rgba(255, 255, 255, .85); display: flex; gap: 10px; }
+.ch-date .js-ch-week { color: rgba(255, 255, 255, .7); }
+.ch-extra { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 3px; flex-wrap: wrap; }
+.ch-lunar { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; color: rgba(255, 255, 255, .85); flex-wrap: wrap; }
+.ch-lunar .js-ch-lunar { letter-spacing: .5px; }
+.ch-fest { display: none; font-size: 12px; padding: 1px 8px; border-radius: 999px; background: rgba(239, 68, 68, .22); border: 1px solid rgba(239, 68, 68, .5); color: #fecaca; white-space: nowrap; }
+.ch-fest.on { display: inline-block; }
+.ch-count { display: inline-flex; align-items: baseline; gap: 6px; font-size: 13px; color: rgba(255, 255, 255, .85); flex-wrap: wrap; }
+.ch-count .cc-label { color: rgba(255, 255, 255, .72); }
+.ch-count .cc-time { font-weight: 700; color: #7dd3fc; font-variant-numeric: tabular-nums; letter-spacing: 1px; }
+
+/* ---------- 首页顶部组件:公告滚动条(hero_mode=notice) ---------- */
+.ticker-box { display: flex; align-items: center; gap: 10px; background: rgba(10, 22, 44, .32); border: 1px solid rgba(255, 255, 255, .18); border-radius: 10px; padding: 9px 12px; overflow: hidden; }
+.tk-label { flex: none; font-size: 12px; font-weight: 700; color: #fff; background: var(--brand); border-radius: 6px; padding: 2px 8px; letter-spacing: 1px; }
+.tk-viewport { position: relative; flex: 1; overflow: hidden; white-space: nowrap; }
+.tk-track { display: inline-flex; align-items: center; will-change: transform; }
+.tk-item { display: inline-flex; align-items: center; gap: 6px; color: #fff; text-decoration: none; font-size: 13.5px; padding: 0 26px; position: relative; }
+.tk-item:hover { color: #bae6fd; }
+.tk-item::after { content: '·'; position: absolute; right: 0; color: rgba(255, 255, 255, .4); }
+.tk-pin { font-size: 10.5px; padding: 1px 6px; border-radius: 999px; background: rgba(239, 68, 68, .25); border: 1px solid rgba(239, 68, 68, .5); color: #fecaca; }
+.tk-empty { font-size: 13.5px; color: rgba(255, 255, 255, .75); }
+@keyframes tkScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
+
+/* ---------- 首页顶部组件:打字机标语(hero_mode=slogan) ---------- */
+.slogan-box { display: flex; align-items: center; gap: 10px; min-height: 40px; font-size: clamp(16px, 2.6vw, 22px); color: #fff; }
+.sg-mark { color: #7dd3fc; font-weight: 700; }
+.sg-text { font-weight: 600; letter-spacing: .5px; }
+.sg-caret { display: inline-block; width: 9px; height: 1.05em; background: #7dd3fc; margin-left: 2px; transform: translateY(2px); animation: sgBlink 1s steps(1) infinite; }
+@keyframes sgBlink { 50% { opacity: 0; } }
+@media (max-width: 767px) {
+ .trend-chart { height: 108px; gap: 5px; }
+ .trend-wk { display: none; }
+ .slogan-box { font-size: 16px; }
+}
+
/* ============ 后台风控管理页 ============ */
.vis-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.vis-stat { background: var(--bg-2); border: 1px solid var(--line); border-radius: 12px; padding: 12px 16px; }
@@ -221,9 +304,12 @@ select { width: auto; min-width: 150px; }
.quote-text {
font-size: 17px; color: var(--text-2); cursor: pointer; user-select: none;
padding: 2px 8px; border-radius: 8px;
- transition: color .15s ease, background .15s ease;
+ transition: color .15s ease, background .15s ease, opacity .14s ease, transform .14s ease;
}
.quote-text:hover { color: var(--brand); background: var(--hover-bg); }
+/* 点击切换名言时先淡出再淡入,避免文字瞬变 */
+.quote-text.quote-fade { opacity: 0; transform: translateY(-3px); }
+@media (prefers-reduced-motion: reduce) { .quote-text { transition: color .15s ease, background .15s ease; } }
/* ============ 首页四大板块 ============ */
/* 首页板块区:内容放得下时填满视口剩余高度(不出现竖向滚动条),内容超高时自然滚动 */
@@ -253,8 +339,6 @@ select { width: auto; min-width: 150px; }
/* 通用图标
![]()
(site_icon 输出)在各容器中的尺寸 */
.icon-img { object-fit: contain; vertical-align: middle; }
.board-icon img.icon-img { width: 30px; height: 30px; }
-.board-section .section-title { text-align: center; font-size: 22px; margin-bottom: 6px; }
-.board-section .section-sub { text-align: center; color: var(--text-3); font-size: 14px; margin-bottom: 18px; }
.boards { position: relative; display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 18px; }
.board-card {
position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
@@ -275,21 +359,22 @@ select { width: auto; min-width: 150px; }
font-size: 13px; color: var(--text-3); line-height: 1.5; max-width: 100%;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
-.func-card .fc-icon img.icon-img { width: 24px; height: 24px; }
-
-/* 板块展开说明浮层 */
-.board-panel {
- position: absolute; top: 6px; bottom: 6px; width: 52%; z-index: 5;
- background: var(--card); border: 1px solid var(--brand); border-radius: 16px;
- padding: 24px 26px; box-shadow: var(--shadow); pointer-events: none;
- opacity: 0; transform: translateY(8px) scale(.98); transition: opacity .18s ease, transform .18s ease;
- display: flex; flex-direction: column; justify-content: center;
+/* 首页磁贴 / 板块卡入场动画:依次上浮浮现(fill-mode 用 backwards,动画结束后归还 :hover 位移) */
+@keyframes hpRise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }
+.quick-strip .quick-tile, .boards .board-card { animation: hpRise .46s cubic-bezier(.2, .8, .25, 1) backwards; }
+.quick-strip > :nth-child(1), .boards > :nth-child(1) { animation-delay: .02s; }
+.quick-strip > :nth-child(2), .boards > :nth-child(2) { animation-delay: .07s; }
+.quick-strip > :nth-child(3), .boards > :nth-child(3) { animation-delay: .12s; }
+.quick-strip > :nth-child(4), .boards > :nth-child(4) { animation-delay: .17s; }
+.quick-strip > :nth-child(5), .boards > :nth-child(5) { animation-delay: .22s; }
+.quick-strip > :nth-child(6), .boards > :nth-child(6) { animation-delay: .27s; }
+.quick-strip > :nth-child(7), .boards > :nth-child(7) { animation-delay: .32s; }
+.quick-strip > :nth-child(8), .boards > :nth-child(8) { animation-delay: .37s; }
+.quick-strip > :nth-child(n+9), .boards > :nth-child(n+9) { animation-delay: .42s; }
+@media (prefers-reduced-motion: reduce) {
+ .quick-strip .quick-tile, .boards .board-card { animation: none; }
}
-.board-panel.visible { opacity: 1; transform: none; }
-.board-panel.panel-left { left: 0; }
-.board-panel.panel-right { right: 0; }
-.board-panel-title { font-size: 20px; font-weight: 700; color: var(--brand); margin-bottom: 10px; }
-.board-panel-desc { font-size: 15px; color: var(--text-2); }
+.func-card .fc-icon img.icon-img { width: 24px; height: 24px; }
/* 名言隐藏池 */
.quote-pool { display: none; }
@@ -474,6 +559,20 @@ a.nav-btn[data-note]:hover::after { opacity: 1; transform: translateX(-50%) tran
.admin-nav .brand .site-name { color: #fff; }
.admin-nav a.active { text-decoration: underline; text-underline-offset: 4px; font-weight: 600; }
+/* 后台页面标题区(统一各页标题 / 说明 / 操作区) */
+.admin-head {
+ display: flex; align-items: flex-end; justify-content: space-between; gap: 14px; flex-wrap: wrap;
+ margin-bottom: 18px; padding-bottom: 12px; border-bottom: 1px solid var(--line);
+}
+.admin-head .ah-main { min-width: 0; }
+.admin-head .ah-title { font-size: 20px; font-weight: 700; margin: 0; }
+.admin-head .ah-sub { margin: 6px 0 0; font-size: 13px; color: var(--text-3); line-height: 1.7; }
+.admin-head .ah-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
+@media (max-width: 600px) {
+ .admin-head { align-items: flex-start; }
+ .admin-head .ah-title { font-size: 18px; }
+}
+
.fieldset-card { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 18px; margin-bottom: 20px; box-shadow: var(--shadow-sm); }
.fieldset-card > .fs-title { font-weight: 700; font-size: 16px; margin: 0 0 12px; padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 18px; }
@@ -638,7 +737,6 @@ a.nav-btn[data-note]:hover::after { opacity: 1; transform: translateX(-50%) tran
.v-filter input[type=text] { width: 100%; }
.quote-inner { flex-wrap: wrap; }
.boards { grid-template-columns: repeat(2, 1fr); gap: 12px; min-height: 0; }
- .board-panel { width: 100%; }
.nav-cols { grid-template-columns: 1fr; gap: 14px; }
.nav-area { margin: 18px 12px 30px; }
.search-zone { padding-top: 6vh; }
diff --git a/assets/img/logo.jpg b/assets/img/logo.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1abc0c33974e74cb4ac178e605dc7965e2605bfa
GIT binary patch
literal 33699
zcmdSBXH=72w=Nt6MX7=yRe}Q2L_j(M5tSwaQWT^`LApp2X@O8w5D^3flopXLLg*br
zFVbt24x!hC8b~|0&wk&%&-d&f`|PvNc*poc*5F5SueD}fbIxn7P<~P7K
S%(f
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
' . "\n";
-}
diff --git a/includes/auth.php b/includes/auth.php
index 7345510..90d740a 100644
--- a/includes/auth.php
+++ b/includes/auth.php
@@ -1,7 +1,7 @@
'', // 搜索引擎列表 JSON;空 = 使用默认 4 个
'last_updated' => '',
'hero_bg' => '', // 首页顶部标识栏背景(纯色 / CSS 渐变),空 = 主题默认
- 'hero_mode' => 'weather', // 首页顶部组件模式:weather=天气(默认) / info=访客信息 / blank=空白精简
+ 'hero_mode' => 'weather', // 首页顶部组件模式:weather=天气(默认) / info=信息 / blank=精简 / red=渗透测试 / blue=安全巡检 / posture=安全态势 / trend=访问趋势 / clock=大字时钟 / notice=公告滚动 / slogan=打字机标语
'cdn_ranges' => '', // CDN 段库文本;空 = 使用默认内置列表
'article_note' => '', // 文章区首页顶部注要(Markdown 风格,纯文本)
];
@@ -568,6 +568,72 @@ function visitors_entries(): array
return array_reverse($out); // 新→旧
}
+/**
+ * 首页顶部组件「安全态势 / 访问趋势」所需的汇总数据(一次遍历访问日志 + 两条轻量查询)。
+ * 返回:今日/累计访问、今日/累计独立 IP、拦截次数、黑名单条数、近 N 天每日访问、最近一次成功登录。
+ */
+function home_security_stats(int $days = 7): array
+{
+ $entries = visitors_entries();
+ $total = count($entries);
+ $today = 0;
+ $allIps = [];
+ $todayIps = [];
+ $blockedTotal = 0;
+ $blockedToday = 0;
+ $todayStart = date('Y-m-d 00:00:00');
+ // 近 N 天日期桶(含今天,由旧到新)
+ $buckets = [];
+ $order = [];
+ for ($i = $days - 1; $i >= 0; $i--) {
+ $d = date('Y-m-d', strtotime('-' . $i . ' day'));
+ $buckets[$d] = 0;
+ $order[] = $d;
+ }
+ foreach ($entries as $e) {
+ $allIps[$e['ip']] = 1;
+ if ((int) $e['status'] === 403) {
+ $blockedTotal++;
+ }
+ $day = substr((string) $e['created_at'], 0, 10);
+ if (isset($buckets[$day])) {
+ $buckets[$day]++;
+ }
+ if ((string) $e['created_at'] >= $todayStart) {
+ $today++;
+ $todayIps[$e['ip']] = 1;
+ if ((int) $e['status'] === 403) {
+ $blockedToday++;
+ }
+ }
+ }
+ $daily = [];
+ foreach ($order as $d) {
+ $daily[] = ['day' => $d, 'cnt' => $buckets[$d]];
+ }
+ $ban = (int) db()->query('SELECT COUNT(*) FROM ip_blacklist')->fetchColumn();
+ $lastLogin = null;
+ $row = db()->query("SELECT username, ip, created_at FROM login_log WHERE status = 'ok' ORDER BY created_at DESC, id DESC LIMIT 1")->fetch();
+ if ($row) {
+ $lastLogin = [
+ 'username' => (string) ($row['username'] ?? ''),
+ 'ip' => (string) ($row['ip'] ?? ''),
+ 'time' => (string) ($row['created_at'] ?? ''),
+ ];
+ }
+ return [
+ 'total' => $total,
+ 'today' => $today,
+ 'ips_total' => count($allIps),
+ 'ips_today' => count($todayIps),
+ 'blocked_total' => $blockedTotal,
+ 'blocked_today' => $blockedToday,
+ 'ban' => $ban,
+ 'daily' => $daily,
+ 'last_login' => $lastLogin,
+ ];
+}
+
/** 自动启动:注册访问日志;黑名单对所有请求(前台与后台登录页)统一拦截;自动风控自检仅针对前台 */
function access_boot(): void
{
diff --git a/includes/layout.php b/includes/layout.php
index 0accea3..f368a72 100644
--- a/includes/layout.php
+++ b/includes/layout.php
@@ -146,28 +146,6 @@ function layout_footer(): void
echo '