feat: complete logs
This commit is contained in:
@@ -6,7 +6,7 @@ interface LogFilterState {
|
|||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
startDate: Date | null;
|
startDate: Date | null;
|
||||||
endDate: Date | null;
|
endDate: Date | null;
|
||||||
selectedLogLevels: LogLevel[];
|
selectedLogLevel: LogLevel | null;
|
||||||
selectedService: string;
|
selectedService: string;
|
||||||
selectedAgent: string;
|
selectedAgent: string;
|
||||||
limit: number;
|
limit: number;
|
||||||
@@ -15,14 +15,14 @@ interface LogFilterState {
|
|||||||
setSearchQuery: (query: string) => void;
|
setSearchQuery: (query: string) => void;
|
||||||
setStartDate: (date: Date | null) => void;
|
setStartDate: (date: Date | null) => void;
|
||||||
setEndDate: (date: Date | null) => void;
|
setEndDate: (date: Date | null) => void;
|
||||||
toggleLogLevel: (level: LogLevel) => void;
|
setSelectedLogLevel: (level: LogLevel | null) => void;
|
||||||
setSelectedService: (service: string) => void;
|
setSelectedService: (service: string) => void;
|
||||||
setSelectedAgent: (agent: string) => void;
|
setSelectedAgent: (agent: string) => void;
|
||||||
setLimit: (limit: number) => void;
|
setLimit: (limit: number) => void;
|
||||||
setOffset: (offset: number) => void;
|
setOffset: (offset: number) => void;
|
||||||
resetFilters: () => void;
|
resetFilters: () => void;
|
||||||
getFilters: () => {
|
getFilters: () => {
|
||||||
level?: string | string[];
|
level?: string;
|
||||||
service?: string;
|
service?: string;
|
||||||
agent?: string;
|
agent?: string;
|
||||||
date_from?: string;
|
date_from?: string;
|
||||||
@@ -36,7 +36,7 @@ export const useLogFilterStore = create<LogFilterState>((set, get) => ({
|
|||||||
searchQuery: "",
|
searchQuery: "",
|
||||||
startDate: null,
|
startDate: null,
|
||||||
endDate: null,
|
endDate: null,
|
||||||
selectedLogLevels: ["info", "warning", "error", "fatal"],
|
selectedLogLevel: null,
|
||||||
selectedService: "",
|
selectedService: "",
|
||||||
selectedAgent: "",
|
selectedAgent: "",
|
||||||
limit: 100,
|
limit: 100,
|
||||||
@@ -45,14 +45,7 @@ export const useLogFilterStore = create<LogFilterState>((set, get) => ({
|
|||||||
setSearchQuery: (query) => set({ searchQuery: query }),
|
setSearchQuery: (query) => set({ searchQuery: query }),
|
||||||
setStartDate: (date) => set({ startDate: date }),
|
setStartDate: (date) => set({ startDate: date }),
|
||||||
setEndDate: (date) => set({ endDate: date }),
|
setEndDate: (date) => set({ endDate: date }),
|
||||||
toggleLogLevel: (level) => {
|
setSelectedLogLevel: (level) => set({ selectedLogLevel: level }),
|
||||||
const { selectedLogLevels } = get();
|
|
||||||
if (selectedLogLevels.includes(level)) {
|
|
||||||
set({ selectedLogLevels: selectedLogLevels.filter((l) => l !== level) });
|
|
||||||
} else {
|
|
||||||
set({ selectedLogLevels: [...selectedLogLevels, level] });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setSelectedService: (service) => set({ selectedService: service }),
|
setSelectedService: (service) => set({ selectedService: service }),
|
||||||
setSelectedAgent: (agent) => set({ selectedAgent: agent }),
|
setSelectedAgent: (agent) => set({ selectedAgent: agent }),
|
||||||
setLimit: (limit) => set({ limit }),
|
setLimit: (limit) => set({ limit }),
|
||||||
@@ -63,7 +56,7 @@ export const useLogFilterStore = create<LogFilterState>((set, get) => ({
|
|||||||
searchQuery: "",
|
searchQuery: "",
|
||||||
startDate: null,
|
startDate: null,
|
||||||
endDate: null,
|
endDate: null,
|
||||||
selectedLogLevels: ["info", "warning", "error", "fatal"],
|
selectedLogLevel: null,
|
||||||
selectedService: "",
|
selectedService: "",
|
||||||
selectedAgent: "",
|
selectedAgent: "",
|
||||||
limit: 100,
|
limit: 100,
|
||||||
@@ -73,7 +66,7 @@ export const useLogFilterStore = create<LogFilterState>((set, get) => ({
|
|||||||
|
|
||||||
getFilters: () => {
|
getFilters: () => {
|
||||||
const {
|
const {
|
||||||
selectedLogLevels,
|
selectedLogLevel,
|
||||||
selectedService,
|
selectedService,
|
||||||
selectedAgent,
|
selectedAgent,
|
||||||
startDate,
|
startDate,
|
||||||
@@ -82,7 +75,7 @@ export const useLogFilterStore = create<LogFilterState>((set, get) => ({
|
|||||||
offset,
|
offset,
|
||||||
} = get();
|
} = get();
|
||||||
return {
|
return {
|
||||||
level: selectedLogLevels.length > 0 ? selectedLogLevels : undefined,
|
level: selectedLogLevel || undefined,
|
||||||
service: selectedService || undefined,
|
service: selectedService || undefined,
|
||||||
agent: selectedAgent || undefined,
|
agent: selectedAgent || undefined,
|
||||||
date_from: startDate ? startDate.toISOString() : undefined,
|
date_from: startDate ? startDate.toISOString() : undefined,
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ export interface TokenUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LogEntry {
|
export interface LogEntry {
|
||||||
agent: string;
|
Agent: string;
|
||||||
level: string;
|
Level: string;
|
||||||
message: string;
|
Message: string;
|
||||||
service: string;
|
Service: string;
|
||||||
timestamp: string;
|
Timestamp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InsertLogRequest {
|
export interface InsertLogRequest {
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ const logLevelColors: Record<
|
|||||||
{ bg: string; text: string; border: string }
|
{ bg: string; text: string; border: string }
|
||||||
> = {
|
> = {
|
||||||
info: {
|
info: {
|
||||||
bg: "var(--info-bg)",
|
bg: "rgba(59, 130, 246, 0.1)",
|
||||||
text: "var(--info-text)",
|
text: "#3b82f6",
|
||||||
border: "var(--info-border)",
|
border: "rgba(59, 130, 246, 0.3)",
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
bg: "var(--warning-bg)",
|
bg: "rgba(245, 158, 11, 0.1)",
|
||||||
text: "var(--warning-text)",
|
text: "#f59e0b",
|
||||||
border: "var(--warning-border)",
|
border: "rgba(245, 158, 11, 0.3)",
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
bg: "var(--error-bg)",
|
bg: "var(--error-bg)",
|
||||||
@@ -29,9 +29,9 @@ const logLevelColors: Record<
|
|||||||
border: "var(--error-border)",
|
border: "var(--error-border)",
|
||||||
},
|
},
|
||||||
fatal: {
|
fatal: {
|
||||||
bg: "var(--fatal-bg)",
|
bg: "rgba(168, 85, 247, 0.1)",
|
||||||
text: "var(--fatal-text)",
|
text: "#a855f7",
|
||||||
border: "var(--fatal-border)",
|
border: "rgba(168, 85, 247, 0.3)",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,13 +50,13 @@ export const LogFilters: React.FC<LogFiltersProps> = ({
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
selectedLogLevels,
|
selectedLogLevel,
|
||||||
selectedService,
|
selectedService,
|
||||||
selectedAgent,
|
selectedAgent,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
setStartDate,
|
setStartDate,
|
||||||
setEndDate,
|
setEndDate,
|
||||||
toggleLogLevel,
|
setSelectedLogLevel,
|
||||||
setSelectedService,
|
setSelectedService,
|
||||||
setSelectedAgent,
|
setSelectedAgent,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
@@ -67,6 +67,9 @@ export const LogFilters: React.FC<LogFiltersProps> = ({
|
|||||||
const [localEndDate, setLocalEndDate] = useState<Date | null>(endDate);
|
const [localEndDate, setLocalEndDate] = useState<Date | null>(endDate);
|
||||||
const [localService, setLocalService] = useState(selectedService);
|
const [localService, setLocalService] = useState(selectedService);
|
||||||
const [localAgent, setLocalAgent] = useState(selectedAgent);
|
const [localAgent, setLocalAgent] = useState(selectedAgent);
|
||||||
|
const [localLevel, setLocalLevel] = useState<LogLevel | null>(
|
||||||
|
selectedLogLevel,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalSearchQuery(searchQuery);
|
setLocalSearchQuery(searchQuery);
|
||||||
@@ -88,10 +91,15 @@ export const LogFilters: React.FC<LogFiltersProps> = ({
|
|||||||
setLocalAgent(selectedAgent);
|
setLocalAgent(selectedAgent);
|
||||||
}, [selectedAgent]);
|
}, [selectedAgent]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalLevel(selectedLogLevel);
|
||||||
|
}, [selectedLogLevel]);
|
||||||
|
|
||||||
const handleApply = useCallback(() => {
|
const handleApply = useCallback(() => {
|
||||||
setSearchQuery(localSearchQuery);
|
setSearchQuery(localSearchQuery);
|
||||||
setStartDate(localStartDate);
|
setStartDate(localStartDate);
|
||||||
setEndDate(localEndDate);
|
setEndDate(localEndDate);
|
||||||
|
setSelectedLogLevel(localLevel);
|
||||||
setSelectedService(localService);
|
setSelectedService(localService);
|
||||||
setSelectedAgent(localAgent);
|
setSelectedAgent(localAgent);
|
||||||
onApply();
|
onApply();
|
||||||
@@ -99,6 +107,7 @@ export const LogFilters: React.FC<LogFiltersProps> = ({
|
|||||||
localSearchQuery,
|
localSearchQuery,
|
||||||
localStartDate,
|
localStartDate,
|
||||||
localEndDate,
|
localEndDate,
|
||||||
|
localLevel,
|
||||||
localService,
|
localService,
|
||||||
localAgent,
|
localAgent,
|
||||||
onApply,
|
onApply,
|
||||||
@@ -108,6 +117,7 @@ export const LogFilters: React.FC<LogFiltersProps> = ({
|
|||||||
setLocalSearchQuery("");
|
setLocalSearchQuery("");
|
||||||
setLocalStartDate(null);
|
setLocalStartDate(null);
|
||||||
setLocalEndDate(null);
|
setLocalEndDate(null);
|
||||||
|
setLocalLevel(null);
|
||||||
setLocalService("");
|
setLocalService("");
|
||||||
setLocalAgent("");
|
setLocalAgent("");
|
||||||
resetFilters();
|
resetFilters();
|
||||||
@@ -121,7 +131,7 @@ export const LogFilters: React.FC<LogFiltersProps> = ({
|
|||||||
if (endDate) count++;
|
if (endDate) count++;
|
||||||
if (selectedService) count++;
|
if (selectedService) count++;
|
||||||
if (selectedAgent) count++;
|
if (selectedAgent) count++;
|
||||||
if (selectedLogLevels.length < 4) count++;
|
if (selectedLogLevel) count++;
|
||||||
return count;
|
return count;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -265,21 +275,18 @@ export const LogFilters: React.FC<LogFiltersProps> = ({
|
|||||||
className="text-xs font-medium"
|
className="text-xs font-medium"
|
||||||
style={{ color: "var(--text-secondary)" }}
|
style={{ color: "var(--text-secondary)" }}
|
||||||
>
|
>
|
||||||
Уровни логов
|
Уровень логов
|
||||||
</span>
|
|
||||||
<span className="text-xs" style={{ color: "var(--text-muted)" }}>
|
|
||||||
({selectedLogLevels.length}/4)
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{(["info", "warning", "error", "fatal"] as LogLevel[]).map(
|
{(["info", "warning", "error", "fatal"] as LogLevel[]).map(
|
||||||
(level) => {
|
(level) => {
|
||||||
const isSelected = selectedLogLevels.includes(level);
|
const isSelected = localLevel === level;
|
||||||
const colors = logLevelColors[level];
|
const colors = logLevelColors[level];
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={level}
|
key={level}
|
||||||
onClick={() => toggleLogLevel(level)}
|
onClick={() => setLocalLevel(isSelected ? null : level)}
|
||||||
className="px-3 py-2 rounded-lg text-xs font-medium transition-all border flex-shrink-0"
|
className="px-3 py-2 rounded-lg text-xs font-medium transition-all border flex-shrink-0"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: isSelected ? colors.bg : "transparent",
|
backgroundColor: isSelected ? colors.bg : "transparent",
|
||||||
@@ -287,6 +294,24 @@ export const LogFilters: React.FC<LogFiltersProps> = ({
|
|||||||
borderColor: isSelected ? colors.border : "var(--border)",
|
borderColor: isSelected ? colors.border : "var(--border)",
|
||||||
minHeight: "36px",
|
minHeight: "36px",
|
||||||
}}
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (isSelected) {
|
||||||
|
e.currentTarget.style.backgroundColor = colors.text;
|
||||||
|
e.currentTarget.style.color = "#fff";
|
||||||
|
} else {
|
||||||
|
e.currentTarget.style.backgroundColor =
|
||||||
|
"rgba(128, 128, 128, 0.08)";
|
||||||
|
e.currentTarget.style.color = "var(--text-primary)";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = isSelected
|
||||||
|
? colors.bg
|
||||||
|
: "transparent";
|
||||||
|
e.currentTarget.style.color = isSelected
|
||||||
|
? colors.text
|
||||||
|
: "var(--text-secondary)";
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<FiCheck size={10} className="inline mr-1" />
|
<FiCheck size={10} className="inline mr-1" />
|
||||||
@@ -402,6 +427,39 @@ export const LogFilters: React.FC<LogFiltersProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{selectedLogLevel &&
|
||||||
|
(() => {
|
||||||
|
const colors = logLevelColors[selectedLogLevel];
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-1 px-2 py-1 rounded-lg border text-xs"
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.bg,
|
||||||
|
borderColor: colors.border,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FiTag size={10} style={{ color: colors.text }} />
|
||||||
|
<span style={{ color: colors.text }}>
|
||||||
|
Уровень: {selectedLogLevel.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setLocalLevel(null);
|
||||||
|
setSelectedLogLevel(null);
|
||||||
|
onApply();
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
background: "none",
|
||||||
|
border: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
color: colors.text,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FiX size={10} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
{selectedAgent && (
|
{selectedAgent && (
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-1 px-2 py-1 rounded-lg border text-xs"
|
className="flex items-center gap-1 px-2 py-1 rounded-lg border text-xs"
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ const logLevelColors: Record<
|
|||||||
{ bg: string; text: string; border: string }
|
{ bg: string; text: string; border: string }
|
||||||
> = {
|
> = {
|
||||||
info: {
|
info: {
|
||||||
bg: "var(--info-bg)",
|
bg: "rgba(59, 130, 246, 0.1)",
|
||||||
text: "var(--info-text)",
|
text: "#3b82f6",
|
||||||
border: "var(--info-border)",
|
border: "rgba(59, 130, 246, 0.3)",
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
bg: "var(--warning-bg)",
|
bg: "rgba(245, 158, 11, 0.1)",
|
||||||
text: "var(--warning-text)",
|
text: "#f59e0b",
|
||||||
border: "var(--warning-border)",
|
border: "rgba(245, 158, 11, 0.3)",
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
bg: "var(--error-bg)",
|
bg: "var(--error-bg)",
|
||||||
@@ -41,9 +41,9 @@ const logLevelColors: Record<
|
|||||||
border: "var(--error-border)",
|
border: "var(--error-border)",
|
||||||
},
|
},
|
||||||
fatal: {
|
fatal: {
|
||||||
bg: "var(--fatal-bg)",
|
bg: "rgba(168, 85, 247, 0.1)",
|
||||||
text: "var(--fatal-text)",
|
text: "#a855f7",
|
||||||
border: "var(--fatal-border)",
|
border: "rgba(168, 85, 247, 0.3)",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -306,13 +306,13 @@ export const LogsPage: React.FC = () => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{logs.map((log, index) => {
|
{logs.map((log, index) => {
|
||||||
const level = log.level?.toLowerCase() || "info";
|
const level = log.Level?.toLowerCase() || "info";
|
||||||
const colors =
|
const colors =
|
||||||
logLevelColors[level] || logLevelColors.info;
|
logLevelColors[level] || logLevelColors.info;
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={index}
|
key={index}
|
||||||
className="border-t"
|
className="border-t transition-colors"
|
||||||
style={{
|
style={{
|
||||||
borderColor: "var(--border)",
|
borderColor: "var(--border)",
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
@@ -320,12 +320,22 @@ export const LogsPage: React.FC = () => {
|
|||||||
? "var(--card-bg)"
|
? "var(--card-bg)"
|
||||||
: "var(--bg-secondary)",
|
: "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)";
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
className="px-4 py-3 text-sm font-mono whitespace-nowrap"
|
className="px-4 py-3 text-sm font-mono whitespace-nowrap"
|
||||||
style={{ color: "var(--text-secondary)" }}
|
style={{ color: "var(--text-secondary)" }}
|
||||||
>
|
>
|
||||||
{formatTimestamp(log.timestamp)}
|
{formatTimestamp(log.Timestamp)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span
|
<span
|
||||||
@@ -344,19 +354,19 @@ export const LogsPage: React.FC = () => {
|
|||||||
className="px-4 py-3 text-sm"
|
className="px-4 py-3 text-sm"
|
||||||
style={{ color: "var(--text-primary)" }}
|
style={{ color: "var(--text-primary)" }}
|
||||||
>
|
>
|
||||||
{log.service || "—"}
|
{log.Service || "—"}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className="px-4 py-3 text-sm font-mono"
|
className="px-4 py-3 text-sm font-mono"
|
||||||
style={{ color: "var(--text-primary)" }}
|
style={{ color: "var(--text-primary)" }}
|
||||||
>
|
>
|
||||||
{log.agent || "—"}
|
{log.Agent || "—"}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className="px-4 py-3 text-sm"
|
className="px-4 py-3 text-sm"
|
||||||
style={{ color: "var(--text-primary)" }}
|
style={{ color: "var(--text-primary)" }}
|
||||||
>
|
>
|
||||||
{log.message || "—"}
|
{log.Message || "—"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user