diff --git a/frontend/src/modules/agent/store/logFilter.store.ts b/frontend/src/modules/agent/store/logFilter.store.ts index c736e60..60be5e5 100644 --- a/frontend/src/modules/agent/store/logFilter.store.ts +++ b/frontend/src/modules/agent/store/logFilter.store.ts @@ -1,12 +1,12 @@ import { create } from "zustand"; -export type LogLevel = "INFO" | "WARNING" | "ERROR" | "FATAL"; +export type LogLevel = "info" | "warning" | "error" | "fatal"; interface LogFilterState { searchQuery: string; startDate: Date | null; endDate: Date | null; - selectedLogLevels: LogLevel[]; + selectedLogLevel: LogLevel | null; selectedService: string; selectedAgent: string; limit: number; @@ -15,7 +15,7 @@ interface LogFilterState { setSearchQuery: (query: string) => void; setStartDate: (date: Date | null) => void; setEndDate: (date: Date | null) => void; - toggleLogLevel: (level: LogLevel) => void; + setSelectedLogLevel: (level: LogLevel | null) => void; setSelectedService: (service: string) => void; setSelectedAgent: (agent: string) => void; setLimit: (limit: number) => void; @@ -36,7 +36,7 @@ export const useLogFilterStore = create((set, get) => ({ searchQuery: "", startDate: null, endDate: null, - selectedLogLevels: ["INFO", "WARNING", "ERROR", "FATAL"], + selectedLogLevel: null, selectedService: "", selectedAgent: "", limit: 100, @@ -45,14 +45,7 @@ export const useLogFilterStore = create((set, get) => ({ setSearchQuery: (query) => set({ searchQuery: query }), setStartDate: (date) => set({ startDate: date }), setEndDate: (date) => set({ endDate: date }), - toggleLogLevel: (level) => { - const { selectedLogLevels } = get(); - if (selectedLogLevels.includes(level)) { - set({ selectedLogLevels: selectedLogLevels.filter((l) => l !== level) }); - } else { - set({ selectedLogLevels: [...selectedLogLevels, level] }); - } - }, + setSelectedLogLevel: (level) => set({ selectedLogLevel: level }), setSelectedService: (service) => set({ selectedService: service }), setSelectedAgent: (agent) => set({ selectedAgent: agent }), setLimit: (limit) => set({ limit }), @@ -63,7 +56,7 @@ export const useLogFilterStore = create((set, get) => ({ searchQuery: "", startDate: null, endDate: null, - selectedLogLevels: ["INFO", "WARNING", "ERROR", "FATAL"], + selectedLogLevel: null, selectedService: "", selectedAgent: "", limit: 100, @@ -72,9 +65,17 @@ export const useLogFilterStore = create((set, get) => ({ }, getFilters: () => { - const { selectedLogLevels, selectedService, selectedAgent, startDate, endDate, limit, offset } = get(); + const { + selectedLogLevel, + selectedService, + selectedAgent, + startDate, + endDate, + limit, + offset, + } = get(); return { - level: selectedLogLevels.length > 0 ? selectedLogLevels.join(",") : undefined, + level: selectedLogLevel || undefined, service: selectedService || undefined, agent: selectedAgent || undefined, date_from: startDate ? startDate.toISOString() : undefined, diff --git a/frontend/src/modules/agent/types/agent.types.ts b/frontend/src/modules/agent/types/agent.types.ts index adec31b..addeaea 100644 --- a/frontend/src/modules/agent/types/agent.types.ts +++ b/frontend/src/modules/agent/types/agent.types.ts @@ -42,11 +42,11 @@ export interface TokenUser { } export interface LogEntry { - agent: string; - level: string; - message: string; - service: string; - timestamp: string; + Agent: string; + Level: string; + Message: string; + Service: string; + Timestamp: string; } export interface InsertLogRequest { @@ -62,7 +62,7 @@ export interface InsertLogsRequest { } export interface LogFilters { - level?: string; + level?: string | string[]; service?: string; agent?: string; date_from?: string; diff --git a/frontend/src/modules/agent/ui/LogFilters.tsx b/frontend/src/modules/agent/ui/LogFilters.tsx index 36ba5fb..7490251 100644 --- a/frontend/src/modules/agent/ui/LogFilters.tsx +++ b/frontend/src/modules/agent/ui/LogFilters.tsx @@ -13,25 +13,25 @@ const logLevelColors: Record< LogLevel, { bg: string; text: string; border: string } > = { - INFO: { - bg: "var(--info-bg)", - text: "var(--info-text)", - border: "var(--info-border)", + info: { + bg: "rgba(59, 130, 246, 0.1)", + text: "#3b82f6", + border: "rgba(59, 130, 246, 0.3)", }, - WARNING: { - bg: "var(--warning-bg)", - text: "var(--warning-text)", - border: "var(--warning-border)", + warning: { + bg: "rgba(245, 158, 11, 0.1)", + text: "#f59e0b", + border: "rgba(245, 158, 11, 0.3)", }, - ERROR: { + 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)", + fatal: { + bg: "rgba(168, 85, 247, 0.1)", + text: "#a855f7", + border: "rgba(168, 85, 247, 0.3)", }, }; @@ -50,13 +50,13 @@ export const LogFilters: React.FC = ({ searchQuery, startDate, endDate, - selectedLogLevels, + selectedLogLevel, selectedService, selectedAgent, setSearchQuery, setStartDate, setEndDate, - toggleLogLevel, + setSelectedLogLevel, setSelectedService, setSelectedAgent, resetFilters, @@ -67,6 +67,9 @@ export const LogFilters: React.FC = ({ const [localEndDate, setLocalEndDate] = useState(endDate); const [localService, setLocalService] = useState(selectedService); const [localAgent, setLocalAgent] = useState(selectedAgent); + const [localLevel, setLocalLevel] = useState( + selectedLogLevel, + ); useEffect(() => { setLocalSearchQuery(searchQuery); @@ -88,10 +91,15 @@ export const LogFilters: React.FC = ({ setLocalAgent(selectedAgent); }, [selectedAgent]); + useEffect(() => { + setLocalLevel(selectedLogLevel); + }, [selectedLogLevel]); + const handleApply = useCallback(() => { setSearchQuery(localSearchQuery); setStartDate(localStartDate); setEndDate(localEndDate); + setSelectedLogLevel(localLevel); setSelectedService(localService); setSelectedAgent(localAgent); onApply(); @@ -99,6 +107,7 @@ export const LogFilters: React.FC = ({ localSearchQuery, localStartDate, localEndDate, + localLevel, localService, localAgent, onApply, @@ -108,6 +117,7 @@ export const LogFilters: React.FC = ({ setLocalSearchQuery(""); setLocalStartDate(null); setLocalEndDate(null); + setLocalLevel(null); setLocalService(""); setLocalAgent(""); resetFilters(); @@ -121,7 +131,7 @@ export const LogFilters: React.FC = ({ if (endDate) count++; if (selectedService) count++; if (selectedAgent) count++; - if (selectedLogLevels.length < 4) count++; + if (selectedLogLevel) count++; return count; }; @@ -265,21 +275,18 @@ export const LogFilters: React.FC = ({ className="text-xs font-medium" style={{ color: "var(--text-secondary)" }} > - Уровни логов - - - ({selectedLogLevels.length}/4) + Уровень логов
- {(["INFO", "WARNING", "ERROR", "FATAL"] as LogLevel[]).map( + {(["info", "warning", "error", "fatal"] as LogLevel[]).map( (level) => { - const isSelected = selectedLogLevels.includes(level); + const isSelected = localLevel === level; const colors = logLevelColors[level]; return ( ); }, @@ -402,6 +427,39 @@ export const LogFilters: React.FC = ({
)} + {selectedLogLevel && + (() => { + const colors = logLevelColors[selectedLogLevel]; + return ( +
+ + + Уровень: {selectedLogLevel.toUpperCase()} + + +
+ ); + })()} {selectedAgent && (
= { - INFO: , - WARNING: , - ERROR: , - FATAL: , + info: , + warning: , + error: , + fatal: , }; const logLevelColors: Record< string, { bg: string; text: string; border: string } > = { - INFO: { - bg: "var(--info-bg)", - text: "var(--info-text)", - border: "var(--info-border)", + info: { + bg: "rgba(59, 130, 246, 0.1)", + text: "#3b82f6", + border: "rgba(59, 130, 246, 0.3)", }, - WARNING: { - bg: "var(--warning-bg)", - text: "var(--warning-text)", - border: "var(--warning-border)", + warning: { + bg: "rgba(245, 158, 11, 0.1)", + text: "#f59e0b", + border: "rgba(245, 158, 11, 0.3)", }, - ERROR: { + 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)", + fatal: { + bg: "rgba(168, 85, 247, 0.1)", + text: "#a855f7", + border: "rgba(168, 85, 247, 0.3)", }, }; @@ -306,13 +306,13 @@ export const LogsPage: React.FC = () => { {logs.map((log, index) => { - const level = log.level || "INFO"; + const level = log.Level?.toLowerCase() || "info"; const colors = - logLevelColors[level] || logLevelColors.INFO; + logLevelColors[level] || logLevelColors.info; return ( { ? "var(--card-bg)" : "var(--bg-secondary)", }} + onMouseEnter={(e) => { + e.currentTarget.style.backgroundColor = + "var(--border)"; + }} + onMouseLeave={(e) => { + e.currentTarget.style.backgroundColor = + index % 2 === 0 + ? "var(--card-bg)" + : "var(--bg-secondary)"; + }} > - {formatTimestamp(log.timestamp)} + {formatTimestamp(log.Timestamp)} { className="px-4 py-3 text-sm" style={{ color: "var(--text-primary)" }} > - {log.service || "—"} + {log.Service || "—"} - {log.agent || "—"} + {log.Agent || "—"} - {log.message || "—"} + {log.Message || "—"} ); diff --git a/frontend/src/shared/api/axios.instance.ts b/frontend/src/shared/api/axios.instance.ts index 4c7ba74..e5381c3 100644 --- a/frontend/src/shared/api/axios.instance.ts +++ b/frontend/src/shared/api/axios.instance.ts @@ -24,6 +24,33 @@ class ApiClient { validateStatus: (status) => { return status >= 200 && status < 400; }, + // Добавляем кастомный сериализатор параметров + paramsSerializer: { + serialize: (params) => { + const parts: string[] = []; + + Object.entries(params).forEach(([key, value]) => { + if (value === undefined || value === null) return; + + if (Array.isArray(value)) { + // Преобразуем массив в множественные параметры: level=info&level=warning + value.forEach((item) => { + if (item !== undefined && item !== null) { + parts.push( + `${encodeURIComponent(key)}=${encodeURIComponent(item)}`, + ); + } + }); + } else { + parts.push( + `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`, + ); + } + }); + + return parts.join("&"); + }, + }, }); this.setupInterceptors();