redezign: list agents & services; feat: button remove agents
ci-front / build (push) Successful in 2m5s
ci-front / build (push) Successful in 2m5s
This commit is contained in:
@@ -1,5 +1,16 @@
|
|||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { FaBars, FaMicrochip, FaTimes, FaSpinner, FaCopy, FaCheck } from "react-icons/fa";
|
import {
|
||||||
|
FaBars,
|
||||||
|
FaMicrochip,
|
||||||
|
FaTimes,
|
||||||
|
FaSpinner,
|
||||||
|
FaCopy,
|
||||||
|
FaCheck,
|
||||||
|
FaChevronRight,
|
||||||
|
FaChevronDown,
|
||||||
|
FaProjectDiagram,
|
||||||
|
FaTrash,
|
||||||
|
} from "react-icons/fa";
|
||||||
import { useAgentStore } from "@/app/providers/layout/store/agent.store";
|
import { useAgentStore } from "@/app/providers/layout/store/agent.store";
|
||||||
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
|
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
|
||||||
|
|
||||||
@@ -8,12 +19,28 @@ interface SidebarProps {
|
|||||||
onToggle?: () => void;
|
onToggle?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) => {
|
export const Sidebar: React.FC<SidebarProps> = ({
|
||||||
const { agents, isLoading, error, fetchAgents } = useAgentStore();
|
isOpen = true,
|
||||||
|
onToggle,
|
||||||
|
}) => {
|
||||||
|
const { agents, isLoading, error, fetchAgents, removeAgent } =
|
||||||
|
useAgentStore();
|
||||||
const { token } = useAuthStore();
|
const { token } = useAuthStore();
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [showTokenModal, setShowTokenModal] = useState(false);
|
const [showTokenModal, setShowTokenModal] = useState(false);
|
||||||
|
const [expandedAgents, setExpandedAgents] = useState<Set<string>>(
|
||||||
|
new Set(agents.map((a) => a.name)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleAgent = (name: string) => {
|
||||||
|
setExpandedAgents((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(name)) next.delete(name);
|
||||||
|
else next.add(name);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const filteredAgents = useMemo(() => {
|
const filteredAgents = useMemo(() => {
|
||||||
if (!searchQuery) return agents;
|
if (!searchQuery) return agents;
|
||||||
@@ -21,7 +48,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
return agents.filter(
|
return agents.filter(
|
||||||
(agent) =>
|
(agent) =>
|
||||||
agent.name.toLowerCase().includes(query) ||
|
agent.name.toLowerCase().includes(query) ||
|
||||||
agent.services.some((s) => s.name.toLowerCase().includes(query))
|
agent.services.some((s) => s.name.toLowerCase().includes(query)),
|
||||||
);
|
);
|
||||||
}, [agents, searchQuery]);
|
}, [agents, searchQuery]);
|
||||||
|
|
||||||
@@ -38,7 +65,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
<button
|
<button
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
className="fixed top-4 left-4 z-50 p-2.5 rounded-lg shadow-lg transition-colors md:hidden"
|
className="fixed top-4 left-4 z-50 p-2.5 rounded-lg shadow-lg transition-colors md:hidden"
|
||||||
style={{ backgroundColor: "var(--accent)", color: "var(--accent-text)" }}
|
style={{
|
||||||
|
backgroundColor: "var(--accent)",
|
||||||
|
color: "var(--accent-text)",
|
||||||
|
}}
|
||||||
aria-label="Открыть sidebar"
|
aria-label="Открыть sidebar"
|
||||||
>
|
>
|
||||||
<FaBars size={18} />
|
<FaBars size={18} />
|
||||||
@@ -49,7 +79,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Overlay для мобильных */}
|
{/* Overlay для мобильных */}
|
||||||
<div className="fixed inset-0 bg-black/50 z-40 md:hidden" onClick={onToggle} />
|
<div
|
||||||
|
className="fixed inset-0 bg-black/50 z-40 md:hidden"
|
||||||
|
onClick={onToggle}
|
||||||
|
/>
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
className={`fixed md:relative w-72 h-screen z-50 transition-transform duration-300 ease-in-out flex flex-col ${
|
className={`fixed md:relative w-72 h-screen z-50 transition-transform duration-300 ease-in-out flex flex-col ${
|
||||||
@@ -67,12 +100,18 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaMicrochip style={{ color: "var(--accent)", fontSize: "18px" }} />
|
<FaMicrochip style={{ color: "var(--accent)", fontSize: "18px" }} />
|
||||||
<h2 className="text-sm font-semibold" style={{ color: "var(--text-primary)" }}>
|
<h2
|
||||||
|
className="text-sm font-semibold"
|
||||||
|
style={{ color: "var(--text-primary)" }}
|
||||||
|
>
|
||||||
Агенты
|
Агенты
|
||||||
</h2>
|
</h2>
|
||||||
<span
|
<span
|
||||||
className="text-xs px-1.5 py-0.5 rounded"
|
className="text-xs px-1.5 py-0.5 rounded"
|
||||||
style={{ backgroundColor: "var(--bg-secondary)", color: "var(--text-secondary)" }}
|
style={{
|
||||||
|
backgroundColor: "var(--bg-secondary)",
|
||||||
|
color: "var(--text-secondary)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{agents.length}
|
{agents.length}
|
||||||
</span>
|
</span>
|
||||||
@@ -114,14 +153,20 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
<div className="flex-1 overflow-y-auto px-2 py-2">
|
<div className="flex-1 overflow-y-auto px-2 py-2">
|
||||||
{isLoading && agents.length === 0 ? (
|
{isLoading && agents.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center py-12">
|
<div className="flex flex-col items-center justify-center py-12">
|
||||||
<FaSpinner className="animate-spin mb-3" style={{ color: "var(--accent)", fontSize: "20px" }} />
|
<FaSpinner
|
||||||
|
className="animate-spin mb-3"
|
||||||
|
style={{ color: "var(--accent)", fontSize: "20px" }}
|
||||||
|
/>
|
||||||
<p className="text-xs" style={{ color: "var(--text-secondary)" }}>
|
<p className="text-xs" style={{ color: "var(--text-secondary)" }}>
|
||||||
Загрузка агентов...
|
Загрузка агентов...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<div className="text-xs mb-2" style={{ color: "var(--error-text)" }}>
|
<div
|
||||||
|
className="text-xs mb-2"
|
||||||
|
style={{ color: "var(--error-text)" }}
|
||||||
|
>
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -133,7 +178,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : filteredAgents.length === 0 ? (
|
) : filteredAgents.length === 0 ? (
|
||||||
<div className="text-center py-8" style={{ color: "var(--text-muted)" }}>
|
<div
|
||||||
|
className="text-center py-8"
|
||||||
|
style={{ color: "var(--text-muted)" }}
|
||||||
|
>
|
||||||
<FaMicrochip className="mx-auto mb-2 opacity-50" size={16} />
|
<FaMicrochip className="mx-auto mb-2 opacity-50" size={16} />
|
||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
{searchQuery ? "Ничего не найдено" : "Нет агентов"}
|
{searchQuery ? "Ничего не найдено" : "Нет агентов"}
|
||||||
@@ -141,58 +189,153 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{filteredAgents.map((agent) => (
|
{filteredAgents.map((agent) => {
|
||||||
<div
|
const isExpanded = expandedAgents.has(agent.name);
|
||||||
key={agent.name}
|
return (
|
||||||
className="rounded-lg border p-3 transition-all hover:shadow-md"
|
<div
|
||||||
style={{
|
key={agent.name}
|
||||||
backgroundColor: "var(--bg-secondary)",
|
className="rounded-lg border overflow-hidden transition-all group"
|
||||||
borderColor: "var(--border)",
|
style={{
|
||||||
}}
|
backgroundColor: "var(--bg-secondary)",
|
||||||
>
|
borderColor: "var(--border)",
|
||||||
<div className="flex items-center justify-between mb-2">
|
}}
|
||||||
<span className="text-sm font-medium" style={{ color: "var(--text-primary)" }}>
|
>
|
||||||
{agent.name}
|
{/* Agent header — кликабельный для сворачивания */}
|
||||||
</span>
|
<div
|
||||||
</div>
|
className="flex items-center gap-2 px-3 py-2 cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
<div className="space-y-1">
|
onClick={() => toggleAgent(agent.name)}
|
||||||
{agent.services.map((service) => (
|
>
|
||||||
<div
|
<span style={{ color: "var(--text-muted)" }}>
|
||||||
key={service.name}
|
{isExpanded ? (
|
||||||
className="flex items-center justify-between text-xs"
|
<FaChevronDown size={10} />
|
||||||
|
) : (
|
||||||
|
<FaChevronRight size={10} />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<FaMicrochip
|
||||||
|
size={12}
|
||||||
|
style={{ color: "var(--accent)" }}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="text-sm font-medium flex-1 truncate"
|
||||||
|
style={{ color: "var(--text-primary)" }}
|
||||||
>
|
>
|
||||||
<span style={{ color: "var(--text-secondary)" }}>{service.name}</span>
|
{agent.name}
|
||||||
|
</span>
|
||||||
|
{/* Статус-индикатор агента (сколько сервисов запущено) */}
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{agent.services.filter((s) => s.status === "running")
|
||||||
|
.length > 0 && (
|
||||||
|
<span
|
||||||
|
className="w-2 h-2 rounded-full"
|
||||||
|
style={{ backgroundColor: "#4ade80" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
className="px-1.5 py-0.5 rounded text-[10px] font-medium"
|
className="text-[10px]"
|
||||||
style={{
|
style={{ color: "var(--text-muted)" }}
|
||||||
backgroundColor:
|
|
||||||
service.status === "running"
|
|
||||||
? "var(--success-bg)"
|
|
||||||
: service.status === "error"
|
|
||||||
? "var(--error-bg)"
|
|
||||||
: "var(--bg-secondary)",
|
|
||||||
color:
|
|
||||||
service.status === "running"
|
|
||||||
? "var(--success-text)"
|
|
||||||
: service.status === "error"
|
|
||||||
? "var(--error-text)"
|
|
||||||
: "var(--text-muted)",
|
|
||||||
border: `1px solid ${
|
|
||||||
service.status === "running"
|
|
||||||
? "var(--success-border)"
|
|
||||||
: service.status === "error"
|
|
||||||
? "var(--error-border)"
|
|
||||||
: "var(--border)"
|
|
||||||
}`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{service.status}
|
{
|
||||||
|
agent.services.filter((s) => s.status === "running")
|
||||||
|
.length
|
||||||
|
}
|
||||||
|
/{agent.services.length}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
{/* Кнопка удаления — появляется при наведении */}
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (
|
||||||
|
window.confirm(`Удалить агента "${agent.name}"?`)
|
||||||
|
) {
|
||||||
|
removeAgent(agent.name);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="opacity-0 group-hover:opacity-100 p-1 rounded transition-all flex-shrink-0"
|
||||||
|
style={{
|
||||||
|
color: "var(--text-muted)",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = "#f87171";
|
||||||
|
e.currentTarget.style.backgroundColor =
|
||||||
|
"rgba(248, 113, 113, 0.15)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = "var(--text-muted)";
|
||||||
|
e.currentTarget.style.backgroundColor = "transparent";
|
||||||
|
}}
|
||||||
|
title="Удалить агента"
|
||||||
|
>
|
||||||
|
<FaTrash size={10} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Services list — сворачивается */}
|
||||||
|
{isExpanded && (
|
||||||
|
<div
|
||||||
|
className="px-3 pb-2"
|
||||||
|
style={{ paddingLeft: "24px" }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="border-l-2 pl-3 space-y-1"
|
||||||
|
style={{ borderColor: "var(--border)" }}
|
||||||
|
>
|
||||||
|
{agent.services.map((service) => {
|
||||||
|
const isRunning = service.status === "running";
|
||||||
|
const isError = service.status === "error";
|
||||||
|
const isStopped =
|
||||||
|
service.status === "stopped" || !isRunning;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={service.name}
|
||||||
|
className="flex items-center justify-between py-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="text-xs"
|
||||||
|
style={{ color: "var(--text-secondary)" }}
|
||||||
|
>
|
||||||
|
{service.name}
|
||||||
|
</span>
|
||||||
|
{/* Status indicator */}
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<span
|
||||||
|
className="w-1.5 h-1.5 rounded-full flex-shrink-0"
|
||||||
|
style={{
|
||||||
|
backgroundColor: isRunning
|
||||||
|
? "#4ade80"
|
||||||
|
: isError
|
||||||
|
? "#f87171"
|
||||||
|
: "#555",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="text-[10px] font-medium"
|
||||||
|
style={{
|
||||||
|
color: isRunning
|
||||||
|
? "#4ade80"
|
||||||
|
: isError
|
||||||
|
? "#f87171"
|
||||||
|
: "#777",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isRunning
|
||||||
|
? "run"
|
||||||
|
: isError
|
||||||
|
? "err"
|
||||||
|
: "stop"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -200,8 +343,29 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
{/* Footer с кнопками */}
|
{/* Footer с кнопками */}
|
||||||
<div
|
<div
|
||||||
className="p-2 border-t flex gap-2"
|
className="p-2 border-t flex gap-2"
|
||||||
style={{ borderColor: "var(--border)", backgroundColor: "var(--card-bg)" }}
|
style={{
|
||||||
|
borderColor: "var(--border)",
|
||||||
|
backgroundColor: "var(--card-bg)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Кнопка Графы */}
|
||||||
|
<button
|
||||||
|
className="flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs rounded transition-colors"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "var(--bg-secondary)",
|
||||||
|
color: "var(--text-secondary)",
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = "var(--border)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = "var(--bg-secondary)";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaProjectDiagram size={10} />
|
||||||
|
Графы
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowTokenModal(true)}
|
onClick={() => setShowTokenModal(true)}
|
||||||
className="flex-1 flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs rounded transition-colors"
|
className="flex-1 flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs rounded transition-colors"
|
||||||
@@ -225,7 +389,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="w-full max-w-md rounded-xl shadow-2xl border"
|
className="w-full max-w-md rounded-xl shadow-2xl border"
|
||||||
style={{ backgroundColor: "var(--card-bg)", borderColor: "var(--border)" }}
|
style={{
|
||||||
|
backgroundColor: "var(--card-bg)",
|
||||||
|
borderColor: "var(--border)",
|
||||||
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -234,7 +401,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaCopy style={{ color: "var(--accent)" }} size={14} />
|
<FaCopy style={{ color: "var(--accent)" }} size={14} />
|
||||||
<h2 className="text-sm font-semibold" style={{ color: "var(--text-primary)" }}>
|
<h2
|
||||||
|
className="text-sm font-semibold"
|
||||||
|
style={{ color: "var(--text-primary)" }}
|
||||||
|
>
|
||||||
Ваш токен доступа
|
Ваш токен доступа
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -249,12 +419,18 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
|
|
||||||
<div className="p-4 space-y-3">
|
<div className="p-4 space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium mb-2" style={{ color: "var(--text-secondary)" }}>
|
<label
|
||||||
|
className="block text-xs font-medium mb-2"
|
||||||
|
style={{ color: "var(--text-secondary)" }}
|
||||||
|
>
|
||||||
Токен
|
Токен
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-2 rounded-lg p-3 border"
|
className="flex items-center gap-2 rounded-lg p-3 border"
|
||||||
style={{ backgroundColor: "var(--bg-secondary)", borderColor: "var(--border)" }}
|
style={{
|
||||||
|
backgroundColor: "var(--bg-secondary)",
|
||||||
|
borderColor: "var(--border)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<code
|
<code
|
||||||
className="flex-1 text-xs font-mono break-all"
|
className="flex-1 text-xs font-mono break-all"
|
||||||
@@ -269,7 +445,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
style={{ color: "var(--text-secondary)" }}
|
style={{ color: "var(--text-secondary)" }}
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
<FaCheck size={12} style={{ color: "var(--success-text)" }} />
|
<FaCheck
|
||||||
|
size={12}
|
||||||
|
style={{ color: "var(--success-text)" }}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FaCopy size={12} />
|
<FaCopy size={12} />
|
||||||
)}
|
)}
|
||||||
@@ -281,7 +460,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen = true, onToggle }) =>
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowTokenModal(false)}
|
onClick={() => setShowTokenModal(false)}
|
||||||
className="w-full py-2 rounded-lg text-xs font-medium transition-colors"
|
className="w-full py-2 rounded-lg text-xs font-medium transition-colors"
|
||||||
style={{ backgroundColor: "var(--accent)", color: "var(--accent-text)" }}
|
style={{
|
||||||
|
backgroundColor: "var(--accent)",
|
||||||
|
color: "var(--accent-text)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Закрыть
|
Закрыть
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -10,8 +10,37 @@ interface AgentState {
|
|||||||
removeAgent: (name: string) => void;
|
removeAgent: (name: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mockAgents: AgentInfo[] = [
|
||||||
|
{
|
||||||
|
name: "agent-core-01",
|
||||||
|
token: "tok_a1b2c3d4e5f6g7h8",
|
||||||
|
services: [
|
||||||
|
{ name: "postgres", status: "running" },
|
||||||
|
{ name: "redis", status: "running" },
|
||||||
|
{ name: "log-collector", status: "running" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "agent-worker-02",
|
||||||
|
token: "tok_x9y8z7w6v5u4t3s2",
|
||||||
|
services: [
|
||||||
|
{ name: "celery-worker", status: "running" },
|
||||||
|
{ name: "flower", status: "stopped" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "agent-monitor-03",
|
||||||
|
token: "tok_m1n2o3p4q5r6s7t8",
|
||||||
|
services: [
|
||||||
|
{ name: "prometheus", status: "running" },
|
||||||
|
{ name: "grafana", status: "running" },
|
||||||
|
{ name: "alertmanager", status: "stopped" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const useAgentStore = create<AgentState>()((set, get) => ({
|
export const useAgentStore = create<AgentState>()((set, get) => ({
|
||||||
agents: [],
|
agents: mockAgents,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
||||||
@@ -22,7 +51,8 @@ export const useAgentStore = create<AgentState>()((set, get) => ({
|
|||||||
set({ agents, isLoading: false });
|
set({ agents, isLoading: false });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
set({
|
set({
|
||||||
error: error instanceof Error ? error.message : "Failed to fetch agents",
|
error:
|
||||||
|
error instanceof Error ? error.message : "Failed to fetch agents",
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// shared/api/websocket.service.ts
|
// shared/api/websocket.service.ts
|
||||||
import { useAgentStore } from "@/components/layout/sidebar/store/agent.store";
|
import { useAgentStore } from "@/app/providers/layout/store/agent.store";
|
||||||
import { useWebSocket, type LogMessage } from "@/shared/hooks/useWebSocket";
|
import { useWebSocket, type LogMessage } from "@/shared/hooks/useWebSocket";
|
||||||
import { useEffect, useRef, useCallback, useMemo } from "react";
|
import { useEffect, useRef, useCallback, useMemo } from "react";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user