Agents / logs.html
Skydata001's picture
Create logs.html
c1a3a23 verified
Raw
History Blame Contribute Delete
11.8 kB
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Video Swarm — السجلات</title>
<style>
:root {
--bg: #0a0a0f; --bg-card: rgba(15,15,25,0.95);
--primary: #00ffaa; --secondary: #00aaff;
--text: #e0e0e0; --text-dim: #888;
--border: rgba(0,255,170,0.15);
}
* { margin:0; padding:0; box-sizing:border-box; }
body {
background: var(--bg); color: var(--text);
font-family: 'Segoe UI', system-ui, sans-serif;
min-height: 100vh;
}
header {
position: sticky; top: 0; z-index: 100;
background: rgba(10,10,15,0.95); backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border); padding: 12px 24px;
display: flex; align-items: center; justify-content: space-between;
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand h1 {
font-size: 1.2rem;
background: linear-gradient(90deg, var(--primary), var(--secondary));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
.controls { display: flex; gap: 8px; }
.btn {
padding: 8px 16px; border: 1px solid var(--border);
background: rgba(255,255,255,0.03); color: var(--text);
border-radius: 8px; cursor: pointer; transition: all 0.3s;
font-size: 0.85rem; font-family: inherit;
}
.btn:hover { border-color: var(--primary); color: var(--primary); }
.btn.active { background: var(--primary); color: #0a0a0f; border-color: var(--primary); }
.filters {
padding: 16px 24px; display: flex; gap: 12px; align-items: center;
border-bottom: 1px solid var(--border); flex-wrap: wrap;
}
.filters input {
padding: 8px 14px; background: rgba(255,255,255,0.03);
border: 1px solid var(--border); border-radius: 8px;
color: var(--text); outline: none; font-family: inherit; min-width: 200px;
}
.filters input:focus { border-color: var(--primary); }
.level-filter { display: flex; gap: 4px; }
.level-filter .btn { padding: 6px 12px; font-size: 0.8rem; }
.log-container {
padding: 0; max-height: calc(100vh - 140px);
overflow-y: auto; font-family: 'Consolas', 'Monaco', monospace;
}
.log-row {
padding: 8px 24px; border-bottom: 1px solid rgba(255,255,255,0.03);
display: flex; gap: 16px; align-items: flex-start;
font-size: 0.85rem; transition: background 0.2s;
}
.log-row:hover { background: rgba(255,255,255,0.02); }
.log-time { color: #666; white-space: nowrap; min-width: 180px; }
.log-level {
padding: 2px 8px; border-radius: 4px; font-size: 0.7rem;
font-weight: bold; min-width: 60px; text-align: center;
}
.log-agent { color: var(--secondary); min-width: 140px; }
.log-msg { color: var(--text); flex: 1; word-break: break-word; }
/* Level colors */
.level-INFO { background: rgba(0,255,170,0.1); color: var(--primary); }
.level-WARNING { background: rgba(255,200,0,0.1); color: #ffcc00; }
.level-ERROR { background: rgba(255,0,0,0.15); color: #ff4444; }
.level-TASK { background: rgba(0,170,255,0.1); color: var(--secondary); }
.level-AGENT { background: rgba(170,0,255,0.1); color: #aa55ff; }
.level-DEBUG { background: rgba(255,255,255,0.05); color: #aaa; }
.stats-bar {
padding: 8px 24px; background: rgba(255,255,255,0.02);
border-bottom: 1px solid var(--border);
display: flex; gap: 24px; font-size: 0.8rem; color: var(--text-dim);
}
.stats-bar span { display: flex; align-items: center; gap: 6px; }
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: rgba(0,255,170,0.2); border-radius: 4px; }
.conn-status {
display: flex; align-items: center; gap: 6px;
font-size: 0.8rem; color: var(--text-dim);
}
.conn-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--primary); box-shadow: 0 0 8px var(--primary);
}
.conn-dot.off { background: #ff4444; box-shadow: 0 0 8px #ff4444; }
</style>
<base target="_blank">
</head>
<body>
<header>
<div class="brand">
<div style="font-size:1.5rem;">📋</div>
<h1>سجلات النظام — AI Video Swarm</h1>
</div>
<div style="display:flex;align-items:center;gap:16px;">
<div class="conn-status">
<div class="conn-dot" id="connDot"></div>
<span id="connText">متصل</span>
</div>
<a href="/app" class="btn">🏠 الرئيسية</a>
<button class="btn" onclick="logout()">خروج</button>
</div>
</header>
<div class="stats-bar" id="statsBar">
<span>📊 الإجمالي: <strong id="totalCount">0</strong></span>
<span>🟢 INFO: <strong id="infoCount">0</strong></span>
<span>🟡 WARNING: <strong id="warnCount">0</strong></span>
<span>🔴 ERROR: <strong id="errCount">0</strong></span>
<span>🔵 TASK: <strong id="taskCount">0</strong></span>
</div>
<div class="filters">
<input type="text" id="searchInput" placeholder="🔍 بحث في السجلات..." oninput="applyFilters()">
<div class="level-filter">
<button class="btn active" onclick="toggleLevel(this, 'all')">الكل</button>
<button class="btn" onclick="toggleLevel(this, 'INFO')">INFO</button>
<button class="btn" onclick="toggleLevel(this, 'WARNING')">WARNING</button>
<button class="btn" onclick="toggleLevel(this, 'ERROR')">ERROR</button>
<button class="btn" onclick="toggleLevel(this, 'TASK')">TASK</button>
<button class="btn" onclick="toggleLevel(this, 'AGENT')">AGENT</button>
<button class="btn" onclick="toggleLevel(this, 'DEBUG')">DEBUG</button>
</div>
<div style="flex:1;"></div>
<button class="btn" onclick="copyAll()">📋 نسخ الكل</button>
<button class="btn" onclick="clearLogs()">🗑️ مسح</button>
<button class="btn" id="pauseBtn" onclick="togglePause()">⏸️ إيقاف</button>
</div>
<div class="log-container" id="logContainer">
<div style="padding:40px;text-align:center;color:var(--text-dim);">
<div style="font-size:2rem;margin-bottom:8px;">📡</div>
<div>جاري الاتصال بخادم السجلات...</div>
</div>
</div>
<script>
let ws = null;
let logs = [];
let paused = false;
let activeLevel = 'all';
let autoScroll = true;
const LEVEL_EMOJI = {
'INFO': '🟢', 'WARNING': '🟡', 'ERROR': '🔴',
'TASK': '🔵', 'AGENT': '🟣', 'DEBUG': '⚪'
};
document.addEventListener('DOMContentLoaded', () => {
connectWS();
loadInitialLogs();
document.getElementById('logContainer').addEventListener('scroll', e => {
const el = e.target;
autoScroll = el.scrollTop + el.clientHeight >= el.scrollHeight - 50;
});
});
function connectWS() {
const token = getToken();
if (!token) return window.location.href = '/';
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
ws = new WebSocket(`${protocol}//${window.location.host}/ws/logs`);
ws.onopen = () => {
document.getElementById('connDot').classList.remove('off');
document.getElementById('connText').textContent = 'متصل';
};
ws.onmessage = e => {
if (paused) return;
try {
const entry = JSON.parse(e.data);
addLog(entry);
} catch(err) {}
};
ws.onclose = () => {
document.getElementById('connDot').classList.add('off');
document.getElementById('connText').textContent = 'غير متصل';
setTimeout(connectWS, 3000);
};
}
async function loadInitialLogs() {
try {
const res = await fetch('/api/logs?limit=200', { headers: { 'Authorization': 'Bearer ' + getToken() } });
const data = await res.json();
logs = data.logs || [];
renderLogs();
} catch(e) {}
}
function addLog(entry) {
logs.push(entry);
if (logs.length > 10000) logs = logs.slice(-10000);
if (matchesFilter(entry)) {
appendLogRow(entry);
updateStats();
if (autoScroll) scrollToBottom();
}
}
function matchesFilter(entry) {
if (activeLevel !== 'all' && entry.level !== activeLevel) return false;
const q = document.getElementById('searchInput').value.toLowerCase();
if (!q) return true;
return (entry.message && entry.message.toLowerCase().includes(q)) ||
(entry.agent && entry.agent.toLowerCase().includes(q));
}
function renderLogs() {
const container = document.getElementById('logContainer');
container.innerHTML = '';
const filtered = logs.filter(matchesFilter);
filtered.forEach(entry => appendLogRow(entry, false));
updateStats();
if (autoScroll) scrollToBottom();
}
function appendLogRow(entry, animate=true) {
const container = document.getElementById('logContainer');
const row = document.createElement('div');
row.className = 'log-row';
if (animate) row.style.animation = 'fadeIn 0.2s ease';
const ts = entry.timestamp ? new Date(entry.timestamp).toLocaleString('ar-SA') : '--';
const emoji = LEVEL_EMOJI[entry.level] || '⚪';
row.innerHTML = `
<div class="log-time">${ts}</div>
<div class="log-level level-${entry.level}">${emoji} ${entry.level}</div>
<div class="log-agent">[${entry.agent || 'UNKNOWN'}]</div>
<div class="log-msg">${escapeHtml(entry.message || '')}</div>
`;
container.appendChild(row);
}
function updateStats() {
const counts = {};
logs.forEach(l => { counts[l.level] = (counts[l.level]||0)+1; });
document.getElementById('totalCount').textContent = logs.length;
document.getElementById('infoCount').textContent = counts['INFO'] || 0;
document.getElementById('warnCount').textContent = counts['WARNING'] || 0;
document.getElementById('errCount').textContent = counts['ERROR'] || 0;
document.getElementById('taskCount').textContent = counts['TASK'] || 0;
}
function scrollToBottom() {
const container = document.getElementById('logContainer');
container.scrollTop = container.scrollHeight;
}
function applyFilters() {
renderLogs();
}
function toggleLevel(btn, level) {
document.querySelectorAll('.level-filter .btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
activeLevel = level;
renderLogs();
}
function togglePause() {
paused = !paused;
document.getElementById('pauseBtn').textContent = paused ? '▶️ استئناف' : '⏸️ إيقاف';
}
function clearLogs() {
logs = [];
document.getElementById('logContainer').innerHTML = '';
updateStats();
fetch('/api/logs/clear', { method: 'POST', headers: { 'Authorization': 'Bearer ' + getToken() } }).catch(()=>{});
}
function copyAll() {
const text = logs.map(l => {
const ts = l.timestamp ? new Date(l.timestamp).toISOString() : '--';
return `[${ts}] [${l.level}] [${l.agent}] — ${l.message}`;
}).join('\n');
navigator.clipboard.writeText(text).then(() => alert('تم النسخ!'));
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function getToken() {
const match = document.cookie.match(/access_token=([^;]+)/);
return match ? match[1] : '';
}
function logout() {
document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
window.location.href = '/';
}
// Check auth
(async function() {
const token = getToken();
if (!token) return window.location.href = '/';
try {
const res = await fetch('/api/auth/verify', { headers: { 'Authorization': 'Bearer ' + token } });
if (!res.ok) window.location.href = '/';
} catch(e) { window.location.href = '/'; }
})();
</script>
<style>
@keyframes fadeIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
</style>
</body>
</html>