Files
HellreigN/frontend/src/pages/logs.page.tsx
T
nikitaa_ts c6a9907822
ci-front / build (push) Successful in 2m26s
feat
2026-04-04 19:49:37 +03:00

372 lines
12 KiB
TypeScript
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.
import React, { useState, useEffect, useCallback } from "react";
import { agentApiService } from "@/modules/agent";
import type { LogEntry } from "@/modules/agent";
import { LogFilters } from "@/modules/agent/ui/LogFilters";
import { useLogFilterStore } from "@/modules/agent/store/logFilter.store";
import {
FiFileText,
FiRefreshCw,
FiChevronLeft,
FiChevronRight,
FiInfo,
FiAlertTriangle,
FiAlertCircle,
FiXOctagon,
} from "react-icons/fi";
const logLevelIcons: Record<string, React.ReactNode> = {
INFO: <FiInfo size={14} />,
WARNING: <FiAlertTriangle size={14} />,
ERROR: <FiAlertCircle size={14} />,
FATAL: <FiXOctagon size={14} />,
};
const logLevelColors: Record<
string,
{ bg: string; text: string; border: string }
> = {
INFO: {
bg: "var(--info-bg)",
text: "var(--info-text)",
border: "var(--info-border)",
},
WARNING: {
bg: "var(--warning-bg)",
text: "var(--warning-text)",
border: "var(--warning-border)",
},
ERROR: {
bg: "var(--error-bg)",
text: "var(--error-text)",
border: "var(--error-border)",
},
FATAL: {
bg: "var(--fatal-bg)",
text: "var(--fatal-text)",
border: "var(--fatal-border)",
},
};
export const LogsPage: React.FC = () => {
const [logs, setLogs] = useState<LogEntry[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [availableServices, setAvailableServices] = useState<string[]>([]);
const [availableAgents, setAvailableAgents] = useState<string[]>([]);
const [totalLogs, setTotalLogs] = useState(0);
const { getFilters, limit, offset, setOffset } = useLogFilterStore();
const fetchLogs = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const filters = getFilters();
const data = await agentApiService.searchLogs(filters);
setLogs(data);
setTotalLogs(data.length);
} catch (err) {
setError(
err instanceof Error ? err.message : "Ошибка при загрузке логов",
);
} finally {
setIsLoading(false);
}
}, [getFilters]);
const fetchDistinctData = useCallback(async () => {
try {
const [services, agents] = await Promise.all([
agentApiService.getDistinctServices(),
agentApiService.getDistinctAgents(),
]);
setAvailableServices(services);
setAvailableAgents(agents);
} catch (err) {
console.error("Failed to fetch distinct data:", err);
}
}, []);
useEffect(() => {
fetchDistinctData();
}, [fetchDistinctData]);
useEffect(() => {
fetchLogs();
}, [fetchLogs, offset, limit]);
const handleFilterApply = () => {
setOffset(0);
fetchLogs();
};
const handleNextPage = () => {
setOffset(offset + limit);
};
const handlePrevPage = () => {
setOffset(Math.max(0, offset - limit));
};
const formatTimestamp = (timestamp: string) => {
const date = new Date(timestamp);
return date.toLocaleString("ru-RU", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
};
return (
<div
className="min-h-screen py-8 px-4"
style={{ backgroundColor: "var(--bg-primary)" }}
>
<div style={{ maxWidth: "1400px", margin: "0 auto" }}>
{/* Header */}
<div className="mb-6">
<div className="flex items-center gap-4 mb-4">
<div
className="w-14 h-14 rounded-xl flex items-center justify-center"
style={{ backgroundColor: "var(--bg-secondary)" }}
>
<FiFileText
className="w-7 h-7"
style={{ color: "var(--accent)" }}
/>
</div>
<div>
<h1
className="text-3xl font-bold mb-1"
style={{ color: "var(--text-primary)" }}
>
Поиск логов
</h1>
<p style={{ color: "var(--text-secondary)", fontSize: "16px" }}>
Фильтрация и анализ логов системы
</p>
</div>
</div>
</div>
{/* Filters */}
<div className="mb-6">
<LogFilters
onApply={handleFilterApply}
availableServices={availableServices}
availableAgents={availableAgents}
/>
</div>
{/* Error Message */}
{error && (
<div
className="mb-6 p-4 rounded-lg border text-sm"
style={{
backgroundColor: "var(--error-bg)",
borderColor: "var(--error-border)",
color: "var(--error-text)",
}}
>
{error}
</div>
)}
{/* Logs Table */}
<div
className="rounded-xl border overflow-hidden"
style={{
backgroundColor: "var(--card-bg)",
borderColor: "var(--border)",
}}
>
{/* Table Header */}
<div
className="flex items-center justify-between px-4 py-3 border-b"
style={{ borderColor: "var(--border)" }}
>
<span
className="text-sm font-medium"
style={{ color: "var(--text-primary)" }}
>
Найдено: {totalLogs} записей
</span>
<button
onClick={fetchLogs}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg transition-all text-xs font-medium border"
style={{
backgroundColor: "transparent",
color: "var(--accent)",
borderColor: "var(--border)",
}}
>
<FiRefreshCw
size={12}
className={isLoading ? "animate-spin" : ""}
/>
Обновить
</button>
</div>
{isLoading ? (
<div
className="flex items-center justify-center py-12"
style={{ color: "var(--text-secondary)" }}
>
<FiRefreshCw size={24} className="animate-spin mr-3" />
Загрузка логов...
</div>
) : logs.length === 0 ? (
<div
className="text-center py-12"
style={{ color: "var(--text-muted)" }}
>
Логи не найдены
</div>
) : (
<>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr style={{ backgroundColor: "var(--bg-secondary)" }}>
<th
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
style={{ color: "var(--text-secondary)" }}
>
Время
</th>
<th
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
style={{ color: "var(--text-secondary)" }}
>
Уровень
</th>
<th
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
style={{ color: "var(--text-secondary)" }}
>
Сервис
</th>
<th
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
style={{ color: "var(--text-secondary)" }}
>
Агент
</th>
<th
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
style={{ color: "var(--text-secondary)" }}
>
Сообщение
</th>
</tr>
</thead>
<tbody>
{logs.map((log, index) => {
const colors =
logLevelColors[log.level] || logLevelColors.INFO;
return (
<tr
key={index}
className="border-t"
style={{
borderColor: "var(--border)",
backgroundColor:
index % 2 === 0
? "var(--card-bg)"
: "var(--bg-secondary)",
}}
>
<td
className="px-4 py-3 text-sm font-mono whitespace-nowrap"
style={{ color: "var(--text-secondary)" }}
>
{formatTimestamp(log.timestamp)}
</td>
<td className="px-4 py-3">
<span
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium border"
style={{
backgroundColor: colors.bg,
color: colors.text,
borderColor: colors.border,
}}
>
{logLevelIcons[log.level]}
{log.level}
</span>
</td>
<td
className="px-4 py-3 text-sm"
style={{ color: "var(--text-primary)" }}
>
{log.service}
</td>
<td
className="px-4 py-3 text-sm font-mono"
style={{ color: "var(--text-primary)" }}
>
{log.agent}
</td>
<td
className="px-4 py-3 text-sm"
style={{ color: "var(--text-primary)" }}
>
{log.message}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{/* Pagination */}
<div
className="flex items-center justify-between px-4 py-3 border-t"
style={{ borderColor: "var(--border)" }}
>
<button
onClick={handlePrevPage}
disabled={offset === 0}
className="flex items-center gap-2 px-4 py-2 rounded-lg transition-all text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed border"
style={{
backgroundColor: "var(--input-bg)",
color: "var(--text-primary)",
borderColor: "var(--border)",
}}
>
<FiChevronLeft size={16} />
Назад
</button>
<span
className="text-sm"
style={{ color: "var(--text-secondary)" }}
>
Показано {logs.length} записей (смещение: {offset})
</span>
<button
onClick={handleNextPage}
disabled={logs.length < limit}
className="flex items-center gap-2 px-4 py-2 rounded-lg transition-all text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed border"
style={{
backgroundColor: "var(--input-bg)",
color: "var(--text-primary)",
borderColor: "var(--border)",
}}
>
Далее
<FiChevronRight size={16} />
</button>
</div>
</>
)}
</div>
</div>
</div>
);
};