| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>日志管理 - PlateRecApp</title>
- <style>
- *{margin:0;padding:0;box-sizing:border-box}
- body{background:#0f172a;color:#e2e8f0;font-family:'Segoe UI',system-ui,sans-serif;min-height:100vh}
- .header{background:#1e293b;padding:16px 24px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #334155}
- .header h1{font-size:20px;font-weight:600;color:#f8fafc}
- .nav{display:flex;gap:16px;align-items:center}
- .nav a{color:#60a5fa;text-decoration:none;font-size:14px;padding:6px 12px;border-radius:6px}
- .nav a:hover,.nav a.active{background:#334155}
- .container{max-width:1200px;margin:0 auto;padding:20px}
- .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:20px}
- .card{background:#1e293b;border-radius:12px;padding:20px;border:1px solid #334155}
- .card-title{font-size:13px;color:#94a3b8;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}
- .card-value{font-size:28px;font-weight:700}
- .card-sub{font-size:13px;color:#64748b;margin-top:4px}
- .log-card{background:#1e293b;border-radius:12px;padding:20px;border:1px solid #334155;margin-bottom:16px}
- .log-card-title{font-size:15px;font-weight:600;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center}
- pre{background:#0f172a;border:1px solid #334155;border-radius:8px;padding:12px;font-family:'Fira Code',monospace;font-size:12px;line-height:1.6;max-height:500px;overflow-y:auto;white-space:pre-wrap;word-break:break-all;color:#cbd5e1}
- .btn{padding:8px 16px;border-radius:8px;border:none;cursor:pointer;font-size:14px;font-weight:500;transition:all .2s}
- .btn-primary{background:#3b82f6;color:#fff}.btn-primary:hover{background:#2563eb}
- .btn-danger{background:#dc2626;color:#fff}.btn-danger:hover{background:#b91c1c}
- .btn-warning{background:#d97706;color:#fff}.btn-warning:hover{background:#b45309}
- .btn-group{display:flex;gap:8px;flex-wrap:wrap}
- .progress-bar{height:8px;background:#334155;border-radius:4px;margin-top:8px;overflow:hidden}
- .progress-fill{height:100%;border-radius:4px;transition:width .5s}
- .msg{padding:10px 14px;border-radius:8px;margin-top:10px;font-size:14px;display:none}
- .msg.success{display:block;background:#14532d;color:#86efac;border:1px solid #16a34a}
- .msg.error{display:block;background:#7f1d1d;color:#fca5a5;border:1px solid #dc2626}
- @media (max-width: 768px) {
- .header { flex-direction: column; gap: 8px; padding: 12px 16px; }
- .header h1 { font-size: 16px; }
- .nav { flex-wrap: wrap; gap: 8px; justify-content: center; }
- .nav a { font-size: 12px; padding: 4px 8px; }
- .container { padding: 12px; }
- .grid { grid-template-columns: 1fr 1fr; gap: 10px; }
- .card { padding: 14px; }
- .card-value { font-size: 22px; }
- .log-card { padding: 14px; }
- .log-card-title { font-size: 13px; flex-direction: column; gap: 8px; align-items: flex-start; }
- pre { font-size: 11px; max-height: 300px; padding: 10px; }
- .btn-group { flex-wrap: wrap; }
- .btn { flex: 1; min-width: 80px; text-align: center; font-size: 12px; }
- }
- @media (max-width: 480px) {
- .grid { grid-template-columns: 1fr; }
- .card-value { font-size: 20px; }
- }
- </style>
- </head>
- <body>
- <div class="header">
- <h1>📋 日志管理</h1>
- <div class="nav">
- <a href="/">监控台</a>
- <a href="/video">视频</a>
- <a href="/config">配置</a>
- <a href="/system">系统</a>
- <a href="/frpc">frpc</a>
- <a href="/ssh-keys">SSH</a>
- <a href="/logs" class="active">日志</a>
- </div>
- </div>
- <div class="container">
- <div class="grid">
- <div class="card">
- <div class="card-title">文件大小</div>
- <div class="card-value" id="file-size">-- MB</div>
- <div class="card-sub">缓冲区上限: <span id="buffer-max">-</span> MB</div>
- <div class="progress-bar"><div class="progress-fill" id="buffer-bar" style="width:0%"></div></div>
- </div>
- <div class="card">
- <div class="card-title">内存缓冲</div>
- <div class="card-value" id="memory-usage">-- MB</div>
- <div class="card-sub">模式: <span id="log-mode">-</span> | 条目: <span id="line-count">-</span></div>
- <div class="progress-bar"><div class="progress-fill" id="memory-bar" style="width:0%"></div></div>
- </div>
- <div class="card">
- <div class="card-title">定时刷盘</div>
- <div class="card-value" id="flush-time">--</div>
- <div class="card-sub">上次刷盘: <span id="last-flush">-</span> | 保留: <span id="retention">-</span> 天</div>
- </div>
- <div class="card">
- <div class="card-title">刷盘统计</div>
- <div class="card-value" id="flush-count">--</div>
- <div class="card-sub">磁盘文件: <span id="last-modified">-</span></div>
- </div>
- </div>
- <div class="btn-group" style="margin-bottom:16px">
- <button class="btn btn-primary" onclick="refreshStats()">🔄 刷新状态</button>
- <button class="btn btn-warning" onclick="flushLog()">⚡ 手动刷新日志</button>
- <button class="btn btn-danger" onclick="cleanupLogs()">🗑️ 清理过期日志</button>
- <button class="btn btn-primary" onclick="refreshTail()">📄 刷新日志尾部</button>
- </div>
- <div id="msg" class="msg"></div>
- <div class="log-card">
- <div class="log-card-title">
- <span>日志尾部 (最后 200 行)</span>
- <span style="font-size:13px;color:#64748b" id="log-path">-</span>
- </div>
- <pre id="log-content">加载中...</pre>
- </div>
- </div>
- <script>
- function getCookie(n){var m=document.cookie.match(new RegExp("(^| )"+n+"=([^;]+)"));return m?m[2]:"";}function authFetch(u,o){o=o||{};var h=o.headers||{};if(!(h instanceof Headers)){var nh=new Headers();for(var k in h)nh.set(k,h[k]);h=nh;}if(!h.has("X-CSRF-Token")){var t=getCookie("csrf_token");if(t)h.set("X-CSRF-Token",t);}o.headers=h;return fetch(u,o).then(function(r){if(r.status===401){window.location.href="/login?redirect="+encodeURIComponent(window.location.pathname);return new Promise(function(){});}return r;});}
- function showMsg(text,ok){
- let el=document.getElementById('msg');
- el.className='msg '+(ok?'success':'error');
- el.textContent=text;
- setTimeout(()=>el.className='msg',5000);
- }
- function refreshStats(){
- authFetch('/api/logs/stats').then(r=>r.json()).then(d=>{
- document.getElementById('file-size').textContent=d.size_mb+' MB';
- document.getElementById('buffer-max').textContent=d.buffer_mb;
- document.getElementById('line-count').textContent=d.line_count.toLocaleString();
- document.getElementById('last-modified').textContent=d.last_modified||'-';
- document.getElementById('flush-time').textContent=d.flush_time;
- document.getElementById('retention').textContent=d.retention_days;
- document.getElementById('log-path').textContent=d.file;
- // ✅ fix24-v36: 内存缓冲新增字段
- document.getElementById('log-mode').textContent=d.memory_enabled?'内存模式':'直写模式';
- document.getElementById('memory-usage').textContent=(d.memory_usage_mb||0).toFixed(2)+' MB';
- document.getElementById('last-flush').textContent=d.last_flush_time||'未刷盘';
- document.getElementById('flush-count').textContent=(d.total_flush_count||0)+' 次';
- // 磁盘文件进度条
- let bar=document.getElementById('buffer-bar');
- bar.style.width=Math.min(d.buffer_percent,100)+'%';
- if(d.buffer_percent<70) bar.style.background='#4ade80';
- else if(d.buffer_percent<90) bar.style.background='#facc15';
- else bar.style.background='#f87171';
- // 内存缓冲进度条
- let mbar=document.getElementById('memory-bar');
- let memPercent=d.buffer_percent||0;
- mbar.style.width=Math.min(memPercent,100)+'%';
- if(memPercent<70) mbar.style.background='#4ade80';
- else if(memPercent<90) mbar.style.background='#facc15';
- else mbar.style.background='#f87171';
- });
- }
- function refreshTail(){
- document.getElementById('log-content').textContent='加载中...';
- authFetch('/api/logs/tail?lines=200').then(r=>r.text()).then(t=>{
- document.getElementById('log-content').textContent=t||'(空日志)';
- });
- }
- function flushLog(){
- authFetch('/api/logs/flush',{method:'POST'}).then(r=>r.json()).then(d=>{
- showMsg(d.message||d.error,d.success);
- });
- }
- function cleanupLogs(){
- if(!confirm('确定要清理过期日志文件吗?')) return;
- authFetch('/api/logs/cleanup',{method:'POST'}).then(r=>r.json()).then(d=>{
- showMsg(d.message||d.error,d.success);
- });
- }
- refreshStats();
- refreshTail();
- </script>
- </body>
- </html>
|