fix: NODE1_REPAIR - healthchecks, dependencies, SSR env, telegram gateway
TASK_PHASE_NODE1_REPAIR: - Fix daarion-web SSR: use CITY_API_BASE_URL instead of 127.0.0.1 - Fix auth API routes: use AUTH_API_URL env var - Add wget to Dockerfiles for healthchecks (stt, ocr, web-search, swapper, vector-db, rag) - Update healthchecks to use wget instead of curl - Fix vector-db-service: update torch==2.4.0, sentence-transformers==2.6.1 - Fix rag-service: correct haystack imports for v2.x - Fix telegram-gateway: remove msg.ack() for non-JetStream NATS - Add /health endpoint to nginx mvp-routes.conf - Add room_role, is_public, sort_order columns to city_rooms migration - Add TASK_PHASE_NODE1_REPAIR.md and DEPLOY_NODE1_REPAIR.md docs Previous tasks included: - TASK 039-044: Orchestrator rooms, Matrix chat cleanup, CrewAI integration
This commit is contained in:
@@ -1,114 +1,34 @@
|
||||
/**
|
||||
* Connect Node Page - Спрощений UI для підключення ноди
|
||||
* Для звичайних користувачів (без термінала)
|
||||
* Connect Node Page - Інструкції з підключення ноди
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Download, Copy, CheckCircle, Monitor, Cpu, HardDrive } from 'lucide-react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Download, Copy, CheckCircle, Monitor, Cpu, HardDrive, Terminal, ExternalLink, AlertCircle } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { apiGet } from '../api/client';
|
||||
|
||||
export default function ConnectNodePage() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [selectedOS, setSelectedOS] = useState<'macos' | 'linux' | 'windows'>('macos');
|
||||
const [instructions, setInstructions] = useState<string>('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const registryUrl = 'http://localhost:9205'; // TODO: змінити на production URL
|
||||
useEffect(() => {
|
||||
const fetchInstructions = async () => {
|
||||
try {
|
||||
const response = await apiGet<{ content: string }>('/public/nodes/join/instructions');
|
||||
if (response.content) {
|
||||
setInstructions(response.content);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch instructions:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Інструкції для різних ОС
|
||||
const instructions = {
|
||||
macos: {
|
||||
title: '🍎 macOS',
|
||||
steps: [
|
||||
{
|
||||
title: '1. Завантажити Bootstrap Agent',
|
||||
description: 'Скачайте скрипт автоматичної реєстрації',
|
||||
action: 'download',
|
||||
code: 'curl -O http://localhost:9205/bootstrap/node_bootstrap.py',
|
||||
},
|
||||
{
|
||||
title: '2. Встановити залежності',
|
||||
description: 'Встановіть необхідні Python бібліотеки',
|
||||
code: 'pip3 install --user requests psutil',
|
||||
},
|
||||
{
|
||||
title: '3. Запустити Bootstrap Agent',
|
||||
description: 'Запустіть агент для автоматичної реєстрації',
|
||||
code: `export NODE_REGISTRY_URL="${registryUrl}"
|
||||
export NODE_ROLE="worker"
|
||||
python3 node_bootstrap.py`,
|
||||
},
|
||||
],
|
||||
},
|
||||
linux: {
|
||||
title: '🐧 Linux',
|
||||
steps: [
|
||||
{
|
||||
title: '1. Завантажити Bootstrap Agent',
|
||||
description: 'Скачайте скрипт автоматичної реєстрації',
|
||||
code: 'curl -O http://localhost:9205/bootstrap/node_bootstrap.py',
|
||||
},
|
||||
{
|
||||
title: '2. Встановити залежності',
|
||||
description: 'Встановіть необхідні Python бібліотеки',
|
||||
code: 'pip3 install requests psutil',
|
||||
},
|
||||
{
|
||||
title: '3. Запустити Bootstrap Agent',
|
||||
description: 'Запустіть агент для автоматичної реєстрації',
|
||||
code: `export NODE_REGISTRY_URL="${registryUrl}"
|
||||
export NODE_ROLE="worker"
|
||||
python3 node_bootstrap.py`,
|
||||
},
|
||||
{
|
||||
title: '4. (Опціонально) Додати як systemd service',
|
||||
description: 'Для автоматичного запуску при перезавантаженні',
|
||||
code: `sudo tee /etc/systemd/system/node-bootstrap.service << EOF
|
||||
[Unit]
|
||||
Description=DAGI Node Bootstrap
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment="NODE_REGISTRY_URL=${registryUrl}"
|
||||
Environment="NODE_ROLE=worker"
|
||||
ExecStart=/usr/bin/python3 /opt/dagi/node_bootstrap.py
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo systemctl enable node-bootstrap
|
||||
sudo systemctl start node-bootstrap`,
|
||||
},
|
||||
],
|
||||
},
|
||||
windows: {
|
||||
title: '🪟 Windows',
|
||||
steps: [
|
||||
{
|
||||
title: '1. Завантажити Bootstrap Agent',
|
||||
description: 'Скачайте скрипт автоматичної реєстрації',
|
||||
code: 'curl -O http://localhost:9205/bootstrap/node_bootstrap.py',
|
||||
},
|
||||
{
|
||||
title: '2. Встановити Python',
|
||||
description: 'Завантажте Python 3.9+ з python.org',
|
||||
link: 'https://www.python.org/downloads/',
|
||||
},
|
||||
{
|
||||
title: '3. Встановити залежності',
|
||||
description: 'Відкрийте PowerShell та виконайте',
|
||||
code: 'pip install requests psutil',
|
||||
},
|
||||
{
|
||||
title: '4. Запустити Bootstrap Agent',
|
||||
description: 'Запустіть агент для автоматичної реєстрації',
|
||||
code: `$env:NODE_REGISTRY_URL="${registryUrl}"
|
||||
$env:NODE_ROLE="worker"
|
||||
python node_bootstrap.py`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
fetchInstructions();
|
||||
}, []);
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
@@ -116,182 +36,161 @@ python node_bootstrap.py`,
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
const currentInstructions = instructions[selectedOS];
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-4 border-blue-600 border-t-transparent"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-950 via-purple-950/20 to-slate-950 text-white p-6">
|
||||
<div className="min-h-screen bg-gray-50 p-6">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold mb-2 bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">
|
||||
🔌 Підключити Ноду до DAGI
|
||||
<Link to="/nodes" className="text-blue-600 hover:underline mb-4 inline-block">← Назад до списку нод</Link>
|
||||
<h1 className="text-4xl font-bold mb-2 text-gray-900">
|
||||
🔌 Підключити Ноду
|
||||
</h1>
|
||||
<p className="text-slate-400">
|
||||
Простий спосіб підключити ваш комп'ютер до децентралізованої мережі AI
|
||||
<p className="text-gray-600">
|
||||
Інструкція з розгортання обчислювальної ноди DAARION
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Benefits */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
||||
<div className="bg-gradient-to-br from-purple-900/30 to-purple-950/30 border border-purple-800/30 rounded-xl p-6">
|
||||
<div className="bg-white border border-blue-100 rounded-xl p-6 shadow-sm">
|
||||
<div className="text-3xl mb-3">💰</div>
|
||||
<h3 className="text-lg font-semibold mb-2">Заробляйте μGOV</h3>
|
||||
<p className="text-slate-400 text-sm">
|
||||
Отримуйте токени за надання обчислювальних ресурсів
|
||||
<h3 className="text-lg font-semibold mb-2 text-gray-900">Заробляйте токени</h3>
|
||||
<p className="text-gray-500 text-sm">
|
||||
Отримуйте винагороду за надання обчислювальних ресурсів
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gradient-to-br from-blue-900/30 to-blue-950/30 border border-blue-800/30 rounded-xl p-6">
|
||||
<div className="bg-white border border-purple-100 rounded-xl p-6 shadow-sm">
|
||||
<div className="text-3xl mb-3">🤖</div>
|
||||
<h3 className="text-lg font-semibold mb-2">Доступ до AI</h3>
|
||||
<p className="text-slate-400 text-sm">
|
||||
Використовуйте AI моделі мережі безкоштовно
|
||||
<h3 className="text-lg font-semibold mb-2 text-gray-900">Доступ до AI</h3>
|
||||
<p className="text-gray-500 text-sm">
|
||||
Використовуйте AI моделі мережі безкоштовно для своїх агентів
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gradient-to-br from-green-900/30 to-green-950/30 border border-green-800/30 rounded-xl p-6">
|
||||
<div className="bg-white border border-green-100 rounded-xl p-6 shadow-sm">
|
||||
<div className="text-3xl mb-3">🌱</div>
|
||||
<h3 className="text-lg font-semibold mb-2">Підтримайте спільноту</h3>
|
||||
<p className="text-slate-400 text-sm">
|
||||
Станьте частиною децентралізованої AI мережі
|
||||
<h3 className="text-lg font-semibold mb-2 text-gray-900">Розвивайте мережу</h3>
|
||||
<p className="text-gray-500 text-sm">
|
||||
Станьте частиною децентралізованої інфраструктури
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* System Requirements */}
|
||||
<div className="bg-slate-900/50 backdrop-blur border border-slate-800 rounded-xl p-6 mb-8">
|
||||
<h2 className="text-xl font-bold mb-4">📋 Системні вимоги</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Cpu className="w-6 h-6 text-purple-400" />
|
||||
<div>
|
||||
<div className="text-slate-400 text-sm">CPU</div>
|
||||
<div className="font-medium">4+ ядра</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Monitor className="w-6 h-6 text-blue-400" />
|
||||
<div>
|
||||
<div className="text-slate-400 text-sm">RAM</div>
|
||||
<div className="font-medium">8+ GB</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<HardDrive className="w-6 h-6 text-green-400" />
|
||||
<div>
|
||||
<div className="text-slate-400 text-sm">Disk</div>
|
||||
<div className="font-medium">50+ GB вільно</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OS Selector */}
|
||||
<div className="mb-6">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setSelectedOS('macos')}
|
||||
className={`flex-1 py-3 px-4 rounded-lg font-medium transition-all ${
|
||||
selectedOS === 'macos'
|
||||
? 'bg-purple-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700'
|
||||
}`}
|
||||
>
|
||||
🍎 macOS
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedOS('linux')}
|
||||
className={`flex-1 py-3 px-4 rounded-lg font-medium transition-all ${
|
||||
selectedOS === 'linux'
|
||||
? 'bg-purple-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700'
|
||||
}`}
|
||||
>
|
||||
🐧 Linux
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedOS('windows')}
|
||||
className={`flex-1 py-3 px-4 rounded-lg font-medium transition-all ${
|
||||
selectedOS === 'windows'
|
||||
? 'bg-purple-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700'
|
||||
}`}
|
||||
>
|
||||
🪟 Windows
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-2xl font-bold">{currentInstructions.title}</h2>
|
||||
|
||||
{currentInstructions.steps.map((step, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-slate-900/50 backdrop-blur border border-slate-800 rounded-xl p-6"
|
||||
>
|
||||
<h3 className="text-lg font-semibold mb-2">{step.title}</h3>
|
||||
<p className="text-slate-400 text-sm mb-4">{step.description}</p>
|
||||
|
||||
{step.code && (
|
||||
<div className="relative">
|
||||
<pre className="bg-slate-950 border border-slate-700 rounded-lg p-4 overflow-x-auto text-sm">
|
||||
<code className="text-green-400">{step.code}</code>
|
||||
</pre>
|
||||
<button
|
||||
onClick={() => copyToClipboard(step.code)}
|
||||
className="absolute top-2 right-2 p-2 bg-slate-800 hover:bg-slate-700 rounded-lg transition-colors"
|
||||
title="Скопіювати"
|
||||
>
|
||||
{copied ? (
|
||||
<CheckCircle className="w-4 h-4 text-green-400" />
|
||||
) : (
|
||||
<Copy className="w-4 h-4 text-slate-400" />
|
||||
)}
|
||||
</button>
|
||||
{/* Content */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Main Instructions */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8 prose prose-blue max-w-none">
|
||||
{instructions ? (
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
code({node, inline, className, children, ...props}: any) {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
return !inline && match ? (
|
||||
<div className="relative group">
|
||||
<pre className={className} {...props}>
|
||||
<code>{children}</code>
|
||||
</pre>
|
||||
<button
|
||||
onClick={() => copyToClipboard(String(children).replace(/\n$/, ''))}
|
||||
className="absolute top-2 right-2 p-2 bg-gray-700 text-white rounded opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
title="Copy"
|
||||
>
|
||||
{copied ? <CheckCircle className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{instructions}
|
||||
</ReactMarkdown>
|
||||
) : (
|
||||
<div className="text-center py-12 text-gray-500">
|
||||
<AlertCircle className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||||
<p>Інструкції не знайдено. Зверніться до адміністратора.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step.link && (
|
||||
<a
|
||||
href={step.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Завантажити Python
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Alternative: One-Click Installer */}
|
||||
<div className="mt-8 bg-gradient-to-r from-purple-900/30 to-pink-900/30 border border-purple-800/30 rounded-xl p-6">
|
||||
<h2 className="text-xl font-bold mb-4">⚡ Швидке підключення (Coming Soon)</h2>
|
||||
<p className="text-slate-400 mb-4">
|
||||
Скоро буде доступний інсталятор в один клік для автоматичного налаштування ноди
|
||||
</p>
|
||||
<button
|
||||
disabled
|
||||
className="px-6 py-3 bg-purple-600/50 text-white rounded-lg cursor-not-allowed opacity-50"
|
||||
>
|
||||
<Download className="w-4 h-4 inline mr-2" />
|
||||
Завантажити інсталятор (незабаром)
|
||||
</button>
|
||||
</div>
|
||||
{/* Sidebar */}
|
||||
<div className="space-y-6">
|
||||
{/* Help Section */}
|
||||
<div className="bg-blue-50 border border-blue-100 rounded-xl p-6">
|
||||
<h2 className="text-xl font-bold mb-4 text-blue-900 flex items-center gap-2">
|
||||
<AlertCircle className="w-5 h-5" />
|
||||
Потрібна допомога?
|
||||
</h2>
|
||||
<div className="space-y-3 text-blue-800 text-sm">
|
||||
<p>Для отримання токенів доступу (NATS credentials) зверніться до адміністраторів:</p>
|
||||
<a
|
||||
href="https://matrix.to/#/#daarion:daarion.space"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 p-3 bg-white rounded-lg hover:shadow-md transition-shadow"
|
||||
>
|
||||
<span className="font-semibold">Matrix Chat</span>
|
||||
<ExternalLink className="w-4 h-4 ml-auto" />
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="flex items-center gap-2 p-3 bg-white rounded-lg hover:shadow-md transition-shadow"
|
||||
>
|
||||
<span className="font-semibold">Discord Server</span>
|
||||
<ExternalLink className="w-4 h-4 ml-auto" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Help Section */}
|
||||
<div className="mt-8 bg-slate-900/50 backdrop-blur border border-slate-800 rounded-xl p-6">
|
||||
<h2 className="text-xl font-bold mb-4">❓ Потрібна допомога?</h2>
|
||||
<div className="space-y-2 text-slate-400">
|
||||
<p>• 📚 Документація: <a href="#" className="text-purple-400 hover:underline">docs.dagi.ai</a></p>
|
||||
<p>• 💬 Telegram спільнота: <a href="#" className="text-purple-400 hover:underline">@dagi_community</a></p>
|
||||
<p>• 🐛 Проблеми: <a href="#" className="text-purple-400 hover:underline">GitHub Issues</a></p>
|
||||
{/* System Requirements Summary */}
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<h2 className="text-lg font-bold mb-4 text-gray-900">Вимоги</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Cpu className="w-5 h-5 text-gray-400" />
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">CPU</div>
|
||||
<div className="font-medium text-gray-900">4+ Cores</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Monitor className="w-5 h-5 text-gray-400" />
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">RAM</div>
|
||||
<div className="font-medium text-gray-900">16GB+ (32GB rec.)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<HardDrive className="w-5 h-5 text-gray-400" />
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">Storage</div>
|
||||
<div className="font-medium text-gray-900">100GB+ SSD</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Terminal className="w-5 h-5 text-gray-400" />
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">OS</div>
|
||||
<div className="font-medium text-gray-900">Ubuntu 22.04 / Debian</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { ArrowLeft, Server, Activity, Cpu, HardDrive, Network, Users, Settings, BarChart3, Plug, RefreshCw, CheckCircle2, XCircle, AlertCircle, Filter, Play, Loader2, Wrench, Download, Bot, Database, AlertTriangle, PlusCircle } from 'lucide-react';
|
||||
import { ArrowLeft, Server, Activity, Cpu, HardDrive, Network, Users, Settings, BarChart3, Plug, RefreshCw, CheckCircle2, XCircle, AlertCircle, Filter, Play, Loader2, Wrench, Download, Bot, Database, AlertTriangle, PlusCircle, Boxes, Shield } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiGet } from '../api/client';
|
||||
import { getNode2Agents, type Node2Agent } from '../api/node2Agents';
|
||||
import { getNode1Agents, type Node1Agent } from '../api/node1Agents';
|
||||
import { deployAgentToNode2, deployAllAgentsToNode2, checkNode2AgentsDeployment } from '../api/node2Deployment';
|
||||
@@ -41,6 +42,24 @@ interface NodeDetails {
|
||||
network_in: number;
|
||||
network_out: number;
|
||||
};
|
||||
microdaos?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
role?: string;
|
||||
}>;
|
||||
guardian_agent?: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug?: string;
|
||||
status?: string;
|
||||
};
|
||||
steward_agent?: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug?: string;
|
||||
status?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Grafana та Prometheus URLs (налаштувати під ваші сервери)
|
||||
@@ -103,6 +122,14 @@ export function NodeCabinetPage() {
|
||||
const agents = agentsData?.items || [];
|
||||
const isNode1 = nodeId?.includes('node-1');
|
||||
|
||||
// Отримуємо профіль з API (для MicroDAOs та агентів)
|
||||
let apiNodeProfile: any = null;
|
||||
try {
|
||||
apiNodeProfile = await apiGet(`/public/nodes/${nodeId}`);
|
||||
} catch (e) {
|
||||
console.warn('Failed to fetch node profile from API', e);
|
||||
}
|
||||
|
||||
// Отримуємо реальні дані з інвентаризації
|
||||
const inv = inventory;
|
||||
|
||||
@@ -195,6 +222,9 @@ export function NodeCabinetPage() {
|
||||
network_in: 1250,
|
||||
network_out: 890,
|
||||
},
|
||||
microdaos: apiNodeProfile?.microdaos || [],
|
||||
guardian_agent: apiNodeProfile?.guardian_agent,
|
||||
steward_agent: apiNodeProfile?.steward_agent,
|
||||
};
|
||||
},
|
||||
enabled: !!nodeId,
|
||||
@@ -551,6 +581,113 @@ export function NodeCabinetPage() {
|
||||
↓ {nodeDetails.metrics?.network_in || 0} MB/s ↑ {nodeDetails.metrics?.network_out || 0} MB/s
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Core Runtime & Participation */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Core Runtime */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h3 className="font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<Activity className="w-5 h-5 text-blue-600" />
|
||||
Core Runtime
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ name: 'Node Registry', icon: Database },
|
||||
{ name: 'NATS JetStream', icon: Network },
|
||||
{ name: 'Swapper Service', icon: RefreshCw },
|
||||
{ name: 'Ollama', icon: Bot },
|
||||
].map((service) => {
|
||||
const s = nodeDetails.services?.find(s => s.name.includes(service.name) || (service.name === 'Ollama' && s.name.includes('ollama')));
|
||||
const status = s?.status === 'running' ? 'online' : 'offline'; // Simple mapping
|
||||
const Icon = service.icon;
|
||||
|
||||
return (
|
||||
<div key={service.name} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<Icon className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium text-gray-700">{service.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`w-2 h-2 rounded-full ${status === 'online' ? 'bg-green-500' : 'bg-gray-400'}`} />
|
||||
<span className="text-sm text-gray-600">{status === 'online' ? 'Active' : 'Unknown'}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/* Guardian & Steward */}
|
||||
<div className="mt-4 pt-4 border-t border-gray-200 space-y-3">
|
||||
<div className="flex items-center justify-between p-3 bg-purple-50 rounded-lg border border-purple-100">
|
||||
<div className="flex items-center gap-3">
|
||||
<Shield className="w-4 h-4 text-purple-600" />
|
||||
<span className="font-medium text-purple-900">Guardian Agent</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-purple-700">
|
||||
{nodeDetails.guardian_agent?.name || 'Not active'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg border border-blue-100">
|
||||
<div className="flex items-center gap-3">
|
||||
<Users className="w-4 h-4 text-blue-600" />
|
||||
<span className="font-medium text-blue-900">Steward Agent</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-blue-700">
|
||||
{nodeDetails.steward_agent?.name || 'Not active'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Participation */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h3 className="font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<Boxes className="w-5 h-5 text-green-600" />
|
||||
Participation
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-500 mb-2 uppercase">Hosted MicroDAOs</h4>
|
||||
{nodeDetails.microdaos && nodeDetails.microdaos.length > 0 ? (
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{nodeDetails.microdaos.map(dao => (
|
||||
<div key={dao.id} className="flex items-center justify-between p-3 bg-green-50 border border-green-100 rounded-lg">
|
||||
<span className="font-medium text-green-900">{dao.name}</span>
|
||||
<span className="text-xs px-2 py-1 bg-white text-green-700 rounded border border-green-200">
|
||||
Hosting
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-6 bg-gray-50 rounded-lg border border-dashed border-gray-200">
|
||||
<p className="text-gray-500 text-sm">No MicroDAOs hosted yet</p>
|
||||
<button
|
||||
onClick={() => navigate('/microdao')}
|
||||
className="mt-2 text-sm text-blue-600 hover:underline"
|
||||
>
|
||||
Join a MicroDAO
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-500 mb-2 uppercase">Agent Capabilities</h4>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="p-3 bg-gray-50 rounded-lg text-center">
|
||||
<div className="text-xl font-bold text-gray-900">{departments.length}</div>
|
||||
<div className="text-xs text-gray-500">Teams</div>
|
||||
</div>
|
||||
<div className="p-3 bg-gray-50 rounded-lg text-center">
|
||||
<div className="text-xl font-bold text-gray-900">{nodeDetails.agents?.length || 0}</div>
|
||||
<div className="text-xs text-gray-500">Agents</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Stats */}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Server, Activity, Cpu, HardDrive, Network, ArrowRight, RefreshCw, Zap } from 'lucide-react';
|
||||
import { Server, Activity, Cpu, HardDrive, Network, ArrowRight, RefreshCw, Zap, Plus } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
@@ -216,6 +216,13 @@ export function NodesPage() {
|
||||
<p className="text-gray-600 mt-2">Управління нодами та моніторинг системи</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => navigate('/connect-node')}
|
||||
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors flex items-center gap-2 shadow-sm"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Підключити ноду
|
||||
</button>
|
||||
<button
|
||||
onClick={fetchAllNodesStatus}
|
||||
disabled={loading}
|
||||
|
||||
Reference in New Issue
Block a user