Files
SIAX-MONITOR/web/logs.html
pablinux 7a66f25150 fix: Evitar desbordamiento horizontal en logs
- Agregado overflow-x-auto a contenedores de logs
- Agregado break-words y overflow-wrap-anywhere a líneas de logs
- Aplicado tanto en logs de aplicaciones como en errores del sistema
- Las líneas largas ahora se ajustan correctamente sin scroll horizontal
2026-01-20 06:43:46 -05:00

623 lines
26 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 class="dark" lang="es" dir="ltr">
<head>
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Visor de Registros - SIAX Monitor</title>
<link rel="icon" type="image/svg+xml" href="/static/icon/favicon.svg" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
rel="stylesheet"
/>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
primary: "#137fec",
"background-light": "#f6f7f8",
"background-dark": "#101922",
},
fontFamily: {
display: ["Inter", "sans-serif"],
mono: ["JetBrains Mono", "monospace"],
},
borderRadius: {
DEFAULT: "0.25rem",
lg: "0.5rem",
xl: "0.75rem",
full: "9999px",
},
},
},
};
</script>
<style>
body {
font-family: "Inter", sans-serif;
}
.material-symbols-outlined {
font-variation-settings:
"FILL" 0,
"wght" 400,
"GRAD" 0,
"opsz" 24;
}
</style>
</head>
<body
class="bg-background-light dark:bg-background-dark font-display text-white min-h-screen flex flex-col"
>
<!-- Sticky Top Navigation -->
<header
class="sticky top-0 z-50 w-full border-b border-solid border-[#283039] bg-background-dark/80 backdrop-blur-md px-4 md:px-10 py-3"
>
<div
class="max-w-[1400px] mx-auto flex items-center justify-between whitespace-nowrap"
>
<div class="flex items-center gap-8">
<div class="flex items-center gap-4 text-white">
<div
class="size-8 bg-primary rounded-lg flex items-center justify-center"
>
<img
src="/static/icon/logo.png"
alt="Logo"
class="w-full h-full object-cover"
/>
</div>
<h2
class="text-white text-lg font-bold leading-tight tracking-[-0.015em]"
>
SIAX Monitor
</h2>
</div>
</div>
<div class="flex flex-1 justify-end gap-6 items-center">
<nav class="hidden md:flex items-center gap-6">
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/"
>Panel</a
>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/scan"
>Escanear</a
>
<a
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
href="/select"
>Selecionar Detectada</a
>
<a
class="text-primary text-sm font-medium border-b-2 border-primary pb-1"
href="/logs"
>Registros</a
>
</nav>
<button
class="hidden lg:flex cursor-pointer items-center justify-center rounded-lg h-9 px-4 bg-primary text-white text-sm font-bold transition-opacity hover:opacity-90"
onclick="window.location.href = '/register'"
>
<span>Nueva App</span>
</button>
</div>
</div>
</header>
<div class="flex-1 flex max-w-[1400px] mx-auto w-full">
<!-- Sidebar - App List -->
<aside
class="w-64 border-r border-[#283039] bg-[#161f2a] p-4 space-y-4 overflow-y-auto"
>
<div class="flex items-center justify-between mb-4">
<h3 class="text-white font-bold text-sm">Aplicaciones</h3>
<button
onclick="loadApps()"
class="text-[#9dabb9] hover:text-white transition-colors"
>
<span class="material-symbols-outlined text-[18px]"
>refresh</span
>
</button>
</div>
<div id="app-list" class="space-y-2">
<!-- Apps will be loaded here -->
</div>
<!-- Loading State -->
<div id="sidebar-loading" class="hidden text-center py-8">
<span
class="material-symbols-outlined text-primary text-3xl animate-spin"
>progress_activity</span
>
</div>
<!-- Empty State -->
<div id="sidebar-empty" class="hidden text-center py-8">
<span
class="material-symbols-outlined text-[#9dabb9] text-3xl"
>inbox</span
>
<p class="text-[#9dabb9] text-xs mt-2">
No hay apps registradas
</p>
</div>
</aside>
<!-- Main Content - Log Viewer -->
<main class="flex-1 flex flex-col">
<!-- Log Header -->
<div class="border-b border-[#283039] bg-[#161f2a] p-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="material-symbols-outlined text-primary"
>terminal</span
>
<div>
<h1
class="text-white text-lg font-bold"
id="current-app-name"
>
Selecciona una aplicación
</h1>
<p
class="text-[#9dabb9] text-xs"
id="connection-status"
>
Not connected
</p>
</div>
</div>
<div class="flex items-center gap-2">
<button
id="auto-scroll-btn"
onclick="toggleAutoScroll()"
class="flex items-center gap-2 px-3 py-2 bg-primary/20 border border-primary/30 rounded-lg text-primary text-sm font-medium hover:bg-primary/30 transition-colors"
>
<span
class="material-symbols-outlined text-[18px]"
>swap_vert</span
>
Auto-scroll: ON
</button>
<button
onclick="clearLogViewer()"
class="flex items-center gap-2 px-3 py-2 bg-red-500/20 border border-red-500/30 rounded-lg text-red-400 text-sm font-medium hover:bg-red-500/30 transition-colors"
>
<span
class="material-symbols-outlined text-[18px]"
>delete</span
>
Clear
</button>
</div>
</div>
</div>
<!-- Tabs -->
<div class="border-b border-[#283039] bg-[#161f2a] px-4">
<div class="flex gap-1">
<button
id="tab-app-logs"
onclick="switchTab('app-logs')"
class="tab-button px-4 py-2 text-sm font-medium transition-colors rounded-t-lg border-b-2 border-primary text-primary"
>
<span
class="material-symbols-outlined text-[16px] align-middle"
>terminal</span
>
Logs de App
</button>
<button
id="tab-system-errors"
onclick="switchTab('system-errors')"
class="tab-button px-4 py-2 text-sm font-medium transition-colors rounded-t-lg border-b-2 border-transparent text-[#9dabb9] hover:text-white"
>
<span
class="material-symbols-outlined text-[16px] align-middle"
>error</span
>
Errores del Sistema
</button>
</div>
</div>
<!-- Tab Content: App Logs -->
<div
id="content-app-logs"
class="flex-1 bg-[#0a0f16] overflow-y-auto overflow-x-auto p-4 font-mono text-sm tab-content"
>
<div
id="log-container"
class="space-y-1 break-words overflow-wrap-anywhere"
>
<!-- Welcome Message -->
<div class="text-[#9dabb9] opacity-50">
<span class="text-green-400"></span> SIAX Monitor
Log Viewer
</div>
<div class="text-[#9dabb9] opacity-50">
<span class="text-blue-400"></span> Select an
application from the sidebar to view logs
</div>
</div>
</div>
<!-- Tab Content: System Errors -->
<div
id="content-system-errors"
class="hidden flex-1 bg-[#0a0f16] overflow-y-auto overflow-x-auto p-4 font-mono text-sm tab-content"
>
<div
id="system-errors-container"
class="space-y-1 break-words overflow-wrap-anywhere"
>
<div class="text-[#9dabb9] opacity-50">
<span class="text-yellow-400"></span> Cargando logs
de errores del sistema...
</div>
</div>
</div>
</main>
</div>
<script>
let ws = null;
let autoScroll = true;
let currentApp = null;
async function loadApps() {
const appList = document.getElementById("app-list");
const loading = document.getElementById("sidebar-loading");
const empty = document.getElementById("sidebar-empty");
appList.classList.add("hidden");
loading.classList.remove("hidden");
empty.classList.add("hidden");
try {
const response = await fetch("/api/apps");
const result = await response.json();
loading.classList.add("hidden");
if (
!result.success ||
!result.data ||
!result.data.apps ||
result.data.apps.length === 0
) {
empty.classList.remove("hidden");
return;
}
appList.classList.remove("hidden");
appList.innerHTML = result.data.apps
.map((app) => {
const statusColor =
app.status === "Running"
? "text-green-400"
: app.status === "Stopped"
? "text-gray-400"
: "text-red-400";
const statusIcon =
app.status === "Running"
? "play_circle"
: app.status === "Stopped"
? "stop_circle"
: "error";
return `
<button
onclick="connectToApp('${app.name}')"
class="app-item w-full text-left p-3 rounded-lg border border-[#283039] hover:border-primary hover:bg-[#1c2730] transition-all ${currentApp === app.name ? "bg-primary/20 border-primary" : ""}"
data-app="${app.name}"
>
<div class="flex items-center justify-between mb-1">
<span class="text-white text-sm font-medium truncate">${app.name}</span>
<span class="material-symbols-outlined ${statusColor} text-[16px]">${statusIcon}</span>
</div>
<div class="text-[#9dabb9] text-xs">${app.status}</div>
</button>
`;
})
.join("");
} catch (error) {
console.error("Error loading apps:", error);
loading.classList.add("hidden");
empty.classList.remove("hidden");
}
}
function connectToApp(appName) {
// Close existing connection
if (ws) {
ws.close();
}
currentApp = appName;
document.getElementById("current-app-name").textContent =
appName;
document.getElementById("connection-status").textContent =
"Connecting...";
// Clear log container
const logContainer = document.getElementById("log-container");
logContainer.innerHTML = `
<div class="text-[#9dabb9]">
<span class="text-blue-400"></span> Connecting to ${appName} logs...
</div>
`;
// Update active state
document.querySelectorAll(".app-item").forEach((item) => {
if (item.dataset.app === appName) {
item.classList.add("bg-primary/20", "border-primary");
} else {
item.classList.remove(
"bg-primary/20",
"border-primary",
);
}
});
// Connect WebSocket
const protocol =
window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/api/apps/${appName}/logs`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
document.getElementById("connection-status").textContent =
"Connected - Live streaming";
appendLog("success", `Connected to ${appName} logs`);
};
ws.onmessage = (event) => {
try {
const log = JSON.parse(event.data);
appendLog("log", log.MESSAGE || event.data, log);
} catch (e) {
appendLog("log", event.data);
}
};
ws.onerror = (error) => {
appendLog("error", "WebSocket error occurred");
document.getElementById("connection-status").textContent =
"Connection error";
};
ws.onclose = () => {
appendLog("warning", `Disconnected from ${appName}`);
document.getElementById("connection-status").textContent =
"Disconnected";
};
}
function appendLog(type, message, logData = null) {
const logContainer = document.getElementById("log-container");
const logEntry = document.createElement("div");
logEntry.className =
"log-line break-words overflow-wrap-anywhere";
const timestamp = new Date()
.toISOString()
.split("T")[1]
.slice(0, 12);
let icon = "●";
let color = "text-white";
if (type === "error") {
icon = "✖";
color = "text-red-400";
} else if (type === "warning") {
icon = "⚠";
color = "text-yellow-400";
} else if (type === "success") {
icon = "✓";
color = "text-green-400";
} else if (logData) {
// Parse log priority
const priority = logData.PRIORITY || "6";
if (priority <= "3") {
icon = "✖";
color = "text-red-400";
} else if (priority === "4") {
icon = "⚠";
color = "text-yellow-400";
} else {
icon = "●";
color = "text-blue-400";
}
}
logEntry.innerHTML = `
<span class="text-[#9dabb9] opacity-50">[${timestamp}]</span>
<span class="${color}">${icon}</span>
<span class="${color}">${escapeHtml(message)}</span>
`;
logContainer.appendChild(logEntry);
// Auto-scroll
if (autoScroll && currentTab === "app-logs") {
const terminal =
document.getElementById("content-app-logs");
terminal.scrollTop = terminal.scrollHeight;
}
// Limit to last 1000 lines
while (logContainer.children.length > 1000) {
logContainer.removeChild(logContainer.firstChild);
}
}
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
function toggleAutoScroll() {
autoScroll = !autoScroll;
const btn = document.getElementById("auto-scroll-btn");
btn.innerHTML = `
<span class="material-symbols-outlined text-[18px]">swap_vert</span>
Auto-scroll: ${autoScroll ? "ON" : "OFF"}
`;
if (autoScroll) {
btn.classList.add(
"bg-primary/20",
"border-primary/30",
"text-primary",
);
btn.classList.remove(
"bg-[#1c2730]",
"border-[#283039]",
"text-[#9dabb9]",
);
} else {
btn.classList.remove(
"bg-primary/20",
"border-primary/30",
"text-primary",
);
btn.classList.add(
"bg-[#1c2730]",
"border-[#283039]",
"text-[#9dabb9]",
);
}
}
function clearLogViewer() {
const logContainer = document.getElementById("log-container");
logContainer.innerHTML = `
<div class="text-[#9dabb9]">
<span class="text-blue-400"></span> Log viewer cleared
</div>
`;
}
// Tab switching
let currentTab = "app-logs";
function switchTab(tabName) {
currentTab = tabName;
// Update tab buttons
document.querySelectorAll(".tab-button").forEach((btn) => {
btn.classList.remove("border-primary", "text-primary");
btn.classList.add("border-transparent", "text-[#9dabb9]");
});
const activeTab = document.getElementById(`tab-${tabName}`);
activeTab.classList.remove(
"border-transparent",
"text-[#9dabb9]",
);
activeTab.classList.add("border-primary", "text-primary");
// Update tab content
document.querySelectorAll(".tab-content").forEach((content) => {
content.classList.add("hidden");
});
document
.getElementById(`content-${tabName}`)
.classList.remove("hidden");
// Load system errors if switching to that tab
if (tabName === "system-errors") {
loadSystemErrors();
}
}
async function loadSystemErrors() {
const container = document.getElementById(
"system-errors-container",
);
try {
const response = await fetch("/api/logs/errors");
const result = await response.json();
if (
result.success &&
result.logs &&
result.logs.length > 0
) {
container.innerHTML = result.logs
.map((line) => {
// Parse log line
let icon = "●";
let color = "text-white";
if (line.includes("[ERROR]")) {
icon = "✖";
color = "text-red-400";
} else if (line.includes("[WARN]")) {
icon = "⚠";
color = "text-yellow-400";
} else if (line.includes("[INFO]")) {
icon = "";
color = "text-blue-400";
}
return `<div class="log-line break-words overflow-wrap-anywhere ${color}">${icon} ${escapeHtml(line)}</div>`;
})
.join("");
// Auto scroll to bottom
container.scrollTop = container.scrollHeight;
} else if (result.message) {
container.innerHTML = `
<div class="text-[#9dabb9]">
<span class="text-yellow-400">⚠</span> ${result.message}
</div>
`;
} else {
container.innerHTML = `
<div class="text-[#9dabb9]">
<span class="text-blue-400"></span> No hay logs de errores disponibles
</div>
`;
}
} catch (error) {
console.error("Error loading system errors:", error);
container.innerHTML = `
<div class="text-red-400">
<span class="text-red-400">✖</span> Error cargando logs del sistema
</div>
`;
}
}
// Load apps on page load
document.addEventListener("DOMContentLoaded", loadApps);
// Cleanup on page unload
window.addEventListener("beforeunload", () => {
if (ws) {
ws.close();
}
});
</script>
</body>
</html>