@@ -33,14 +33,14 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
const [showTokenModal, setShowTokenModal] = useState(false);
|
const [showTokenModal, setShowTokenModal] = useState(false);
|
||||||
const [showGraphs, setShowGraphs] = useState(false);
|
const [showGraphs, setShowGraphs] = useState(false);
|
||||||
const [expandedAgents, setExpandedAgents] = useState<Set<string>>(
|
const [expandedAgents, setExpandedAgents] = useState<Set<string>>(
|
||||||
new Set(agents.map((a) => a.name)),
|
new Set(agents.map((a) => a.label)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleAgent = (name: string) => {
|
const toggleAgent = (label: string) => {
|
||||||
setExpandedAgents((prev) => {
|
setExpandedAgents((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
if (next.has(name)) next.delete(name);
|
if (next.has(label)) next.delete(label);
|
||||||
else next.add(name);
|
else next.add(label);
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -50,8 +50,8 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
const query = searchQuery.toLowerCase();
|
const query = searchQuery.toLowerCase();
|
||||||
return agents.filter(
|
return agents.filter(
|
||||||
(agent) =>
|
(agent) =>
|
||||||
agent.name.toLowerCase().includes(query) ||
|
agent.label.toLowerCase().includes(query) ||
|
||||||
agent.services.some((s) => s.name.toLowerCase().includes(query)),
|
agent.services.some((s) => s.toLowerCase().includes(query)),
|
||||||
);
|
);
|
||||||
}, [agents, searchQuery]);
|
}, [agents, searchQuery]);
|
||||||
|
|
||||||
@@ -61,25 +61,25 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
|
|
||||||
agents.forEach((agent) => {
|
agents.forEach((agent) => {
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: agent.name,
|
id: agent.label,
|
||||||
name: agent.name,
|
name: agent.label,
|
||||||
type: "agent" as const,
|
type: "agent" as const,
|
||||||
val: 8,
|
val: 8,
|
||||||
description: `Агент: ${agent.name}`,
|
description: `Агент: ${agent.label}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
agent.services.forEach((service) => {
|
agent.services.forEach((service) => {
|
||||||
const serviceId = `${agent.name}-${service.name}`;
|
const serviceId = `${agent.label}-${service}`;
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: serviceId,
|
id: serviceId,
|
||||||
name: service.name,
|
name: service,
|
||||||
type: "service" as const,
|
type: "service" as const,
|
||||||
val: 12,
|
val: 12,
|
||||||
description: `Сервис: ${service.name} (${service.status})`,
|
description: `Сервис: ${service}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
links.push({
|
links.push({
|
||||||
source: agent.name,
|
source: agent.label,
|
||||||
target: serviceId,
|
target: serviceId,
|
||||||
type: "hosts",
|
type: "hosts",
|
||||||
});
|
});
|
||||||
@@ -239,10 +239,10 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{filteredAgents.map((agent) => {
|
{filteredAgents.map((agent) => {
|
||||||
const isExpanded = expandedAgents.has(agent.name);
|
const isExpanded = expandedAgents.has(agent.label);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={agent.name}
|
key={agent.label}
|
||||||
className="rounded-lg border overflow-hidden transition-all group"
|
className="rounded-lg border overflow-hidden transition-all group"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "var(--bg-secondary)",
|
backgroundColor: "var(--bg-secondary)",
|
||||||
@@ -252,7 +252,7 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
{/* Agent header — кликабельный для сворачивания */}
|
{/* Agent header — кликабельный для сворачивания */}
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-2 px-3 py-2 cursor-pointer hover:opacity-80 transition-opacity"
|
className="flex items-center gap-2 px-3 py-2 cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
onClick={() => toggleAgent(agent.name)}
|
onClick={() => toggleAgent(agent.label)}
|
||||||
>
|
>
|
||||||
<span style={{ color: "var(--text-muted)" }}>
|
<span style={{ color: "var(--text-muted)" }}>
|
||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
@@ -269,13 +269,11 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
className="text-sm font-medium flex-1 truncate"
|
className="text-sm font-medium flex-1 truncate"
|
||||||
style={{ color: "var(--text-primary)" }}
|
style={{ color: "var(--text-primary)" }}
|
||||||
>
|
>
|
||||||
{agent.name}
|
{agent.label}
|
||||||
</span>
|
</span>
|
||||||
{/* Статус-индикатор агента (сколько сервисов запущено) */}
|
{/* Статус-индикатор агента (количество сервисов) */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{agent.services.filter(
|
{agent.services.length > 0 && (
|
||||||
(s) => s.status === "running",
|
|
||||||
).length > 0 && (
|
|
||||||
<span
|
<span
|
||||||
className="w-2 h-2 rounded-full"
|
className="w-2 h-2 rounded-full"
|
||||||
style={{ backgroundColor: "#4ade80" }}
|
style={{ backgroundColor: "#4ade80" }}
|
||||||
@@ -285,12 +283,7 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
className="text-[10px]"
|
className="text-[10px]"
|
||||||
style={{ color: "var(--text-muted)" }}
|
style={{ color: "var(--text-muted)" }}
|
||||||
>
|
>
|
||||||
{
|
{agent.services.length}
|
||||||
agent.services.filter(
|
|
||||||
(s) => s.status === "running",
|
|
||||||
).length
|
|
||||||
}
|
|
||||||
/{agent.services.length}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* Кнопка удаления — появляется при наведении */}
|
{/* Кнопка удаления — появляется при наведении */}
|
||||||
@@ -299,10 +292,10 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (
|
if (
|
||||||
window.confirm(
|
window.confirm(
|
||||||
`Удалить агента "${agent.name}"?`,
|
`Удалить агента "${agent.label}"?`,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
removeAgent(agent.name);
|
removeAgent(agent.label);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="opacity-0 group-hover:opacity-100 p-1 rounded transition-all flex-shrink-0"
|
className="opacity-0 group-hover:opacity-100 p-1 rounded transition-all flex-shrink-0"
|
||||||
@@ -336,49 +329,32 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
style={{ borderColor: "var(--border)" }}
|
style={{ borderColor: "var(--border)" }}
|
||||||
>
|
>
|
||||||
{agent.services.map((service) => {
|
{agent.services.map((service) => {
|
||||||
const isRunning = service.status === "running";
|
|
||||||
const isError = service.status === "error";
|
|
||||||
const isStopped =
|
|
||||||
service.status === "stopped" || !isRunning;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={service.name}
|
key={service}
|
||||||
className="flex items-center justify-between py-1"
|
className="flex items-center justify-between py-1"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="text-xs"
|
className="text-xs"
|
||||||
style={{ color: "var(--text-secondary)" }}
|
style={{ color: "var(--text-secondary)" }}
|
||||||
>
|
>
|
||||||
{service.name}
|
{service}
|
||||||
</span>
|
</span>
|
||||||
{/* Status indicator */}
|
{/* Status indicator */}
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span
|
<span
|
||||||
className="w-1.5 h-1.5 rounded-full flex-shrink-0"
|
className="w-1.5 h-1.5 rounded-full flex-shrink-0"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: isRunning
|
backgroundColor: "#4ade80",
|
||||||
? "#4ade80"
|
|
||||||
: isError
|
|
||||||
? "#f87171"
|
|
||||||
: "#555",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="text-[10px] font-medium"
|
className="text-[10px] font-medium"
|
||||||
style={{
|
style={{
|
||||||
color: isRunning
|
color: "#4ade80",
|
||||||
? "#4ade80"
|
|
||||||
: isError
|
|
||||||
? "#f87171"
|
|
||||||
: "#777",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isRunning
|
run
|
||||||
? "run"
|
|
||||||
: isError
|
|
||||||
? "err"
|
|
||||||
: "stop"}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,30 +12,22 @@ interface AgentState {
|
|||||||
|
|
||||||
const mockAgents: AgentInfo[] = [
|
const mockAgents: AgentInfo[] = [
|
||||||
{
|
{
|
||||||
name: "agent-core-01",
|
label: "agent-core-01",
|
||||||
token: "tok_a1b2c3d4e5f6g7h8",
|
token: "tok_a1b2c3d4e5f6g7h8",
|
||||||
services: [
|
services: ["postgres", "redis", "log-collector"],
|
||||||
{ name: "postgres", status: "running" },
|
connected_at: "2026-04-04 15:25:09",
|
||||||
{ name: "redis", status: "running" },
|
|
||||||
{ name: "log-collector", status: "running" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "agent-worker-02",
|
label: "agent-worker-02",
|
||||||
token: "tok_x9y8z7w6v5u4t3s2",
|
token: "tok_x9y8z7w6v5u4t3s2",
|
||||||
services: [
|
services: ["celery-worker", "flower"],
|
||||||
{ name: "celery-worker", status: "running" },
|
connected_at: "2026-04-04 15:25:09",
|
||||||
{ name: "flower", status: "stopped" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "agent-monitor-03",
|
label: "agent-monitor-03",
|
||||||
token: "tok_m1n2o3p4q5r6s7t8",
|
token: "tok_m1n2o3p4q5r6s7t8",
|
||||||
services: [
|
services: ["prometheus", "grafana", "alertmanager"],
|
||||||
{ name: "prometheus", status: "running" },
|
connected_at: "2026-04-04 15:25:09",
|
||||||
{ name: "grafana", status: "running" },
|
|
||||||
{ name: "alertmanager", status: "stopped" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -59,6 +51,6 @@ export const useAgentStore = create<AgentState>()((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
removeAgent: (name: string) => {
|
removeAgent: (name: string) => {
|
||||||
set({ agents: get().agents.filter((a) => a.name !== name) });
|
set({ agents: get().agents.filter((a) => a.label !== name) });
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -45,17 +45,20 @@ class AgentApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async searchLogs(filters?: LogFilters): Promise<LogEntry[]> {
|
async searchLogs(filters?: LogFilters): Promise<LogEntry[]> {
|
||||||
const response = await apiClient.get<LogEntry[]>(this.logsBasePath, {
|
const response = await apiClient.get<LogEntry[]>(
|
||||||
params: {
|
`${this.logsBasePath}/mock`,
|
||||||
level: filters?.level,
|
{
|
||||||
service: filters?.service,
|
params: {
|
||||||
agent: filters?.agent,
|
level: filters?.level,
|
||||||
date_from: filters?.date_from,
|
service: filters?.service,
|
||||||
date_to: filters?.date_to,
|
agent: filters?.agent,
|
||||||
limit: filters?.limit ?? 100,
|
date_from: filters?.date_from,
|
||||||
offset: filters?.offset ?? 0,
|
date_to: filters?.date_to,
|
||||||
|
limit: filters?.limit ?? 100,
|
||||||
|
offset: filters?.offset ?? 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
export interface AgentService {
|
|
||||||
name: string;
|
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AgentInfo {
|
export interface AgentInfo {
|
||||||
name: string;
|
|
||||||
services: AgentService[];
|
|
||||||
token: string;
|
token: string;
|
||||||
|
label: string;
|
||||||
|
services: string[];
|
||||||
|
connected_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginRequest {
|
export interface LoginRequest {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Graph, type GraphData, type GraphNode, type GraphLink } from "@/modules/graph";
|
import {
|
||||||
|
Graph,
|
||||||
|
type GraphData,
|
||||||
|
type GraphNode,
|
||||||
|
type GraphLink,
|
||||||
|
} from "@/modules/graph";
|
||||||
import { useAgentStore } from "@/app/providers/layout/store/agent.store";
|
import { useAgentStore } from "@/app/providers/layout/store/agent.store";
|
||||||
|
|
||||||
const buildGraphFromAgents = (): GraphData => {
|
const buildGraphFromAgents = (): GraphData => {
|
||||||
@@ -10,26 +15,26 @@ const buildGraphFromAgents = (): GraphData => {
|
|||||||
agents.forEach((agent) => {
|
agents.forEach((agent) => {
|
||||||
// Агент как узел
|
// Агент как узел
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: agent.name,
|
id: agent.label,
|
||||||
name: agent.name,
|
name: agent.label,
|
||||||
type: "agent",
|
type: "agent",
|
||||||
val: 8,
|
val: 8,
|
||||||
description: `Агент: ${agent.name}`,
|
description: `Агент: ${agent.label}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Сервисы агента как узлы + связи
|
// Сервисы агента как узлы + связи
|
||||||
agent.services.forEach((service) => {
|
agent.services.forEach((service) => {
|
||||||
const serviceId = `${agent.name}-${service.name}`;
|
const serviceId = `${agent.label}-${service}`;
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: serviceId,
|
id: serviceId,
|
||||||
name: service.name,
|
name: service,
|
||||||
type: "service",
|
type: "service",
|
||||||
val: 12,
|
val: 12,
|
||||||
description: `Сервис: ${service.name} (${service.status})`,
|
description: `Сервис: ${service}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
links.push({
|
links.push({
|
||||||
source: agent.name,
|
source: agent.label,
|
||||||
target: serviceId,
|
target: serviceId,
|
||||||
type: "hosts",
|
type: "hosts",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,11 +21,30 @@ const logLevelIcons: Record<string, React.ReactNode> = {
|
|||||||
FATAL: <FiXOctagon size={14} />,
|
FATAL: <FiXOctagon size={14} />,
|
||||||
};
|
};
|
||||||
|
|
||||||
const logLevelColors: Record<string, { bg: string; text: string; border: string }> = {
|
const logLevelColors: Record<
|
||||||
INFO: { bg: "var(--info-bg)", text: "var(--info-text)", border: "var(--info-border)" },
|
string,
|
||||||
WARNING: { bg: "var(--warning-bg)", text: "var(--warning-text)", border: "var(--warning-border)" },
|
{ bg: string; text: string; border: string }
|
||||||
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)" },
|
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 = () => {
|
export const LogsPage: React.FC = () => {
|
||||||
@@ -47,7 +66,9 @@ export const LogsPage: React.FC = () => {
|
|||||||
setLogs(data);
|
setLogs(data);
|
||||||
setTotalLogs(data.length);
|
setTotalLogs(data.length);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Ошибка при загрузке логов");
|
setError(
|
||||||
|
err instanceof Error ? err.message : "Ошибка при загрузке логов",
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -112,7 +133,10 @@ export const LogsPage: React.FC = () => {
|
|||||||
className="w-14 h-14 rounded-xl flex items-center justify-center"
|
className="w-14 h-14 rounded-xl flex items-center justify-center"
|
||||||
style={{ backgroundColor: "var(--bg-secondary)" }}
|
style={{ backgroundColor: "var(--bg-secondary)" }}
|
||||||
>
|
>
|
||||||
<FiFileText className="w-7 h-7" style={{ color: "var(--accent)" }} />
|
<FiFileText
|
||||||
|
className="w-7 h-7"
|
||||||
|
style={{ color: "var(--accent)" }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1
|
<h1
|
||||||
@@ -160,8 +184,14 @@ export const LogsPage: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Table Header */}
|
{/* Table Header */}
|
||||||
<div className="flex items-center justify-between px-4 py-3 border-b" style={{ borderColor: "var(--border)" }}>
|
<div
|
||||||
<span className="text-sm font-medium" style={{ color: "var(--text-primary)" }}>
|
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} записей
|
Найдено: {totalLogs} записей
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@@ -173,18 +203,27 @@ export const LogsPage: React.FC = () => {
|
|||||||
borderColor: "var(--border)",
|
borderColor: "var(--border)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FiRefreshCw size={12} className={isLoading ? "animate-spin" : ""} />
|
<FiRefreshCw
|
||||||
|
size={12}
|
||||||
|
className={isLoading ? "animate-spin" : ""}
|
||||||
|
/>
|
||||||
Обновить
|
Обновить
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center py-12" style={{ color: "var(--text-secondary)" }}>
|
<div
|
||||||
|
className="flex items-center justify-center py-12"
|
||||||
|
style={{ color: "var(--text-secondary)" }}
|
||||||
|
>
|
||||||
<FiRefreshCw size={24} className="animate-spin mr-3" />
|
<FiRefreshCw size={24} className="animate-spin mr-3" />
|
||||||
Загрузка логов...
|
Загрузка логов...
|
||||||
</div>
|
</div>
|
||||||
) : logs.length === 0 ? (
|
) : logs.length === 0 ? (
|
||||||
<div className="text-center py-12" style={{ color: "var(--text-muted)" }}>
|
<div
|
||||||
|
className="text-center py-12"
|
||||||
|
style={{ color: "var(--text-muted)" }}
|
||||||
|
>
|
||||||
Логи не найдены
|
Логи не найдены
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -193,36 +232,58 @@ export const LogsPage: React.FC = () => {
|
|||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ backgroundColor: "var(--bg-secondary)" }}>
|
<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
|
||||||
|
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
|
||||||
|
style={{ color: "var(--text-secondary)" }}
|
||||||
|
>
|
||||||
Время
|
Время
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider" style={{ color: "var(--text-secondary)" }}>
|
<th
|
||||||
|
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
|
||||||
|
style={{ color: "var(--text-secondary)" }}
|
||||||
|
>
|
||||||
Уровень
|
Уровень
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider" style={{ color: "var(--text-secondary)" }}>
|
<th
|
||||||
|
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
|
||||||
|
style={{ color: "var(--text-secondary)" }}
|
||||||
|
>
|
||||||
Сервис
|
Сервис
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider" style={{ color: "var(--text-secondary)" }}>
|
<th
|
||||||
|
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
|
||||||
|
style={{ color: "var(--text-secondary)" }}
|
||||||
|
>
|
||||||
Агент
|
Агент
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider" style={{ color: "var(--text-secondary)" }}>
|
<th
|
||||||
|
className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider"
|
||||||
|
style={{ color: "var(--text-secondary)" }}
|
||||||
|
>
|
||||||
Сообщение
|
Сообщение
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{logs.map((log, index) => {
|
{logs.map((log, index) => {
|
||||||
const colors = logLevelColors[log.level] || logLevelColors.INFO;
|
const colors =
|
||||||
|
logLevelColors[log.level] || logLevelColors.INFO;
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={index}
|
key={index}
|
||||||
className="border-t"
|
className="border-t"
|
||||||
style={{
|
style={{
|
||||||
borderColor: "var(--border)",
|
borderColor: "var(--border)",
|
||||||
backgroundColor: index % 2 === 0 ? "var(--card-bg)" : "var(--bg-secondary)",
|
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)" }}>
|
<td
|
||||||
|
className="px-4 py-3 text-sm font-mono whitespace-nowrap"
|
||||||
|
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">
|
||||||
@@ -238,13 +299,22 @@ export const LogsPage: React.FC = () => {
|
|||||||
{log.level}
|
{log.level}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm" style={{ color: "var(--text-primary)" }}>
|
<td
|
||||||
|
className="px-4 py-3 text-sm"
|
||||||
|
style={{ color: "var(--text-primary)" }}
|
||||||
|
>
|
||||||
{log.service}
|
{log.service}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm font-mono" style={{ color: "var(--text-primary)" }}>
|
<td
|
||||||
|
className="px-4 py-3 text-sm font-mono"
|
||||||
|
style={{ color: "var(--text-primary)" }}
|
||||||
|
>
|
||||||
{log.agent}
|
{log.agent}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm" style={{ color: "var(--text-primary)" }}>
|
<td
|
||||||
|
className="px-4 py-3 text-sm"
|
||||||
|
style={{ color: "var(--text-primary)" }}
|
||||||
|
>
|
||||||
{log.message}
|
{log.message}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -255,7 +325,10 @@ export const LogsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
<div className="flex items-center justify-between px-4 py-3 border-t" style={{ borderColor: "var(--border)" }}>
|
<div
|
||||||
|
className="flex items-center justify-between px-4 py-3 border-t"
|
||||||
|
style={{ borderColor: "var(--border)" }}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
onClick={handlePrevPage}
|
onClick={handlePrevPage}
|
||||||
disabled={offset === 0}
|
disabled={offset === 0}
|
||||||
@@ -269,7 +342,10 @@ export const LogsPage: React.FC = () => {
|
|||||||
<FiChevronLeft size={16} />
|
<FiChevronLeft size={16} />
|
||||||
Назад
|
Назад
|
||||||
</button>
|
</button>
|
||||||
<span className="text-sm" style={{ color: "var(--text-secondary)" }}>
|
<span
|
||||||
|
className="text-sm"
|
||||||
|
style={{ color: "var(--text-secondary)" }}
|
||||||
|
>
|
||||||
Показано {logs.length} записей (смещение: {offset})
|
Показано {logs.length} записей (смещение: {offset})
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ export const useWebSocketService = ({
|
|||||||
const selectedServices: string[] = [];
|
const selectedServices: string[] = [];
|
||||||
const selectedHosts: string[] = [];
|
const selectedHosts: string[] = [];
|
||||||
|
|
||||||
|
// TODO: реализовать механизм выбора сервисов
|
||||||
|
// Пока выбираем все
|
||||||
agents.forEach((agent) => {
|
agents.forEach((agent) => {
|
||||||
agent.services.forEach((service) => {
|
agent.services.forEach((service) => {
|
||||||
if (service.isSelected) {
|
selectedServices.push(service);
|
||||||
selectedServices.push(service.name);
|
selectedHosts.push(agent.token);
|
||||||
selectedHosts.push(agent.token);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user