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:
Apple
2025-11-29 05:17:08 -08:00
parent 0bab4bba08
commit a6e531a098
69 changed files with 4693 additions and 1310 deletions

View File

@@ -4,10 +4,12 @@
*/
import { useState, useEffect } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { getMicrodao, getMembers, getTreasury, type MicrodaoRead, type MicrodaoMember, type TreasuryItem } from '@/api/microdao';
import { getMicrodao, getMembers, getTreasury, uploadAsset, updateMicrodaoBranding, type MicrodaoRead, type MicrodaoMember, type TreasuryItem } from '@/api/microdao';
import { getAgents, type AgentListItem } from '@/api/agents';
import { MicrodaoHero } from './components/MicrodaoHero';
import { MicrodaoBrandBadge } from './components/MicrodaoBrandBadge';
type TabType = 'overview' | 'members' | 'agents' | 'treasury';
type TabType = 'overview' | 'members' | 'agents' | 'treasury' | 'settings';
export function MicrodaoConsolePage() {
const { slug } = useParams<{ slug: string }>();
@@ -62,6 +64,26 @@ export function MicrodaoConsolePage() {
}
};
const handleUpload = async (file: File | undefined, type: string) => {
if (!file || !microdao) return;
try {
// Optimistic UI update could be done here, but better wait for server
const { processed_url } = await uploadAsset(file, type);
// Update branding
const updated = await updateMicrodaoBranding(
microdao.slug,
type === 'microdao_logo' ? processed_url : undefined,
type === 'microdao_banner' ? processed_url : undefined
);
setMicrodao(prev => prev ? { ...prev, ...updated } : null);
} catch (err) {
console.error('Upload failed:', err);
alert('Upload failed');
}
};
if (loading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
@@ -95,81 +117,82 @@ export function MicrodaoConsolePage() {
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
{/* Hero Header */}
<MicrodaoHero
name={microdao.name}
tagline={microdao.description}
logoUrl={microdao.logo_url}
bannerUrl={microdao.banner_url}
>
<button
onClick={() => navigate('/microdao')}
className="px-4 py-2 bg-white/10 backdrop-blur-md border border-white/20 text-white rounded-lg hover:bg-white/20 transition-colors text-sm"
>
Список
</button>
<Link
to={`/dao/${microdao.slug}-governance`}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2 shadow-md text-sm font-medium"
>
<span>🗳</span>
<span>Governance</span>
</Link>
</MicrodaoHero>
{/* Tabs */}
<div className="bg-white border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<button
onClick={() => navigate('/microdao')}
className="text-blue-600 hover:text-blue-700 mb-4 flex items-center gap-2"
>
Назад до списку
</button>
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">
{microdao.name}
</h1>
{microdao.description && (
<p className="text-gray-600 mt-1">{microdao.description}</p>
)}
<div className="mt-2 text-sm text-gray-500">
<span className="font-mono">{microdao.slug}</span> · {microdao.external_id}
</div>
</div>
<div>
<Link
to={`/dao/${microdao.slug}-governance`}
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-lg hover:from-blue-700 hover:to-purple-700 transition flex items-center gap-2 shadow-md"
>
<span className="text-xl">🗳</span>
<span className="font-semibold">DAO Governance</span>
</Link>
</div>
</div>
{/* Tabs */}
<div className="flex gap-1 border-b border-gray-200 mt-6">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex gap-1 -mb-px pt-2">
<button
onClick={() => setActiveTab('overview')}
className={`px-6 py-3 font-medium transition-colors ${
className={`px-6 py-3 font-medium transition-colors border-b-2 ${
activeTab === 'overview'
? 'text-blue-600 border-b-2 border-blue-600'
: 'text-gray-600 hover:text-gray-900'
? 'text-blue-600 border-blue-600'
: 'text-gray-600 hover:text-gray-900 border-transparent'
}`}
>
📊 Огляд
</button>
<button
onClick={() => setActiveTab('members')}
className={`px-6 py-3 font-medium transition-colors ${
className={`px-6 py-3 font-medium transition-colors border-b-2 ${
activeTab === 'members'
? 'text-blue-600 border-b-2 border-blue-600'
: 'text-gray-600 hover:text-gray-900'
? 'text-blue-600 border-blue-600'
: 'text-gray-600 hover:text-gray-900 border-transparent'
}`}
>
👥 Учасники ({members.length})
</button>
<button
onClick={() => setActiveTab('agents')}
className={`px-6 py-3 font-medium transition-colors ${
className={`px-6 py-3 font-medium transition-colors border-b-2 ${
activeTab === 'agents'
? 'text-blue-600 border-b-2 border-blue-600'
: 'text-gray-600 hover:text-gray-900'
? 'text-blue-600 border-blue-600'
: 'text-gray-600 hover:text-gray-900 border-transparent'
}`}
>
🤖 Агенти ({agents.length})
</button>
<button
onClick={() => setActiveTab('treasury')}
className={`px-6 py-3 font-medium transition-colors ${
className={`px-6 py-3 font-medium transition-colors border-b-2 ${
activeTab === 'treasury'
? 'text-blue-600 border-b-2 border-blue-600'
: 'text-gray-600 hover:text-gray-900'
? 'text-blue-600 border-blue-600'
: 'text-gray-600 hover:text-gray-900 border-transparent'
}`}
>
💰 Казна
</button>
<button
onClick={() => setActiveTab('settings')}
className={`px-6 py-3 font-medium transition-colors border-b-2 ${
activeTab === 'settings'
? 'text-blue-600 border-blue-600'
: 'text-gray-600 hover:text-gray-900 border-transparent'
}`}
>
Налаштування
</button>
</div>
</div>
</div>
@@ -298,6 +321,52 @@ export function MicrodaoConsolePage() {
</div>
</div>
)}
{/* Settings Tab */}
{activeTab === 'settings' && microdao && (
<div className="bg-white border border-gray-200 rounded-lg p-6 space-y-8">
<div>
<h3 className="text-lg font-semibold mb-6">Брендинг</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Logo Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Логотип (256x256)</label>
<div className="flex items-center gap-6">
<MicrodaoBrandBadge name={microdao.name} logoUrl={microdao.logo_url} size="xl" />
<div className="flex-1">
<input
type="file"
accept="image/*"
onChange={(e) => handleUpload(e.target.files?.[0], 'microdao_logo')}
className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 cursor-pointer"
/>
<p className="text-xs text-gray-500 mt-2">PNG, JPG, SVG. Макс 5MB.</p>
</div>
</div>
</div>
{/* Banner Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Банер</label>
<div className="relative w-full h-32 bg-gray-100 rounded-lg overflow-hidden mb-3 border border-gray-200 group">
{microdao.banner_url ? (
<img src={microdao.banner_url} className="w-full h-full object-cover" alt="Banner" />
) : (
<div className="flex items-center justify-center h-full text-gray-400">Немає банера</div>
)}
</div>
<input
type="file"
accept="image/*"
onChange={(e) => handleUpload(e.target.files?.[0], 'microdao_banner')}
className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 cursor-pointer"
/>
</div>
</div>
</div>
</div>
)}
</div>
</div>
);

View File

@@ -6,6 +6,7 @@ import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMicrodaos } from './hooks/useMicrodaos';
import { createMicrodao, type MicrodaoCreate } from '@/api/microdao';
import { MicrodaoBrandBadge } from './components/MicrodaoBrandBadge';
export function MicrodaoListPage() {
const navigate = useNavigate();
@@ -120,24 +121,42 @@ export function MicrodaoListPage() {
<div
key={dao.id}
onClick={() => navigate(`/microdao/${dao.slug}`)}
className="bg-white border border-gray-200 rounded-lg p-6 hover:shadow-lg hover:border-blue-500 transition-all cursor-pointer"
className="bg-white border border-gray-200 rounded-lg overflow-hidden hover:shadow-lg hover:border-blue-500 transition-all cursor-pointer group relative"
>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
{dao.name}
</h3>
{dao.description && (
<p className="text-sm text-gray-600 mb-4 line-clamp-2">
{dao.description}
</p>
{/* Banner Background */}
{dao.banner_url && (
<div
className="absolute inset-0 h-32 bg-cover bg-center opacity-10 group-hover:opacity-20 transition-opacity"
style={{ backgroundImage: `url(${dao.banner_url})` }}
/>
)}
<div className="flex items-center gap-4 text-sm text-gray-500">
<div>
👥 {dao.member_count || 0} учасників
<div className="p-6 relative z-10">
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
<MicrodaoBrandBadge name={dao.name} logoUrl={dao.logo_url} size="md" />
<div>
<h3 className="text-xl font-semibold text-gray-900">
{dao.name}
</h3>
<p className="text-xs text-gray-500">@{dao.slug}</p>
</div>
</div>
</div>
<div>
🤖 {dao.agent_count || 0} агентів
{dao.description && (
<p className="text-sm text-gray-600 mb-4 line-clamp-2">
{dao.description}
</p>
)}
<div className="flex items-center gap-4 text-sm text-gray-500 mt-auto">
<div className="flex items-center gap-1">
<span>👥</span> {dao.member_count || 0}
</div>
<div className="flex items-center gap-1">
<span>🤖</span> {dao.agent_count || 0}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,47 @@
import React from 'react';
import { Building2 } from 'lucide-react';
interface MicrodaoBrandBadgeProps {
logoUrl?: string | null;
name: string;
size?: 'sm' | 'md' | 'lg' | 'xl';
className?: string;
}
export const MicrodaoBrandBadge: React.FC<MicrodaoBrandBadgeProps> = ({
logoUrl,
name,
size = 'md',
className = ''
}) => {
const sizeClasses = {
sm: 'w-8 h-8',
md: 'w-12 h-12',
lg: 'w-16 h-16',
xl: 'w-24 h-24'
};
const iconSizes = {
sm: 16,
md: 24,
lg: 32,
xl: 48
};
if (logoUrl) {
return (
<img
src={logoUrl}
alt={`${name} logo`}
className={`rounded-full object-cover bg-gray-100 shadow-sm border border-gray-200/50 ${sizeClasses[size]} ${className}`}
/>
);
}
return (
<div className={`rounded-full bg-gradient-to-br from-blue-500/10 to-purple-500/10 flex items-center justify-center border border-gray-200 shadow-sm ${sizeClasses[size]} ${className}`}>
<Building2 size={iconSizes[size]} className="text-gray-400" />
</div>
);
};

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { MicrodaoBrandBadge } from './MicrodaoBrandBadge';
interface MicrodaoHeroProps {
bannerUrl?: string | null;
logoUrl?: string | null;
name: string;
tagline?: string | null;
children?: React.ReactNode; // For action buttons etc.
}
export const MicrodaoHero: React.FC<MicrodaoHeroProps> = ({
bannerUrl,
logoUrl,
name,
tagline,
children
}) => {
return (
<div className="relative w-full h-48 md:h-64 lg:h-80 bg-gray-900 overflow-hidden group">
{/* Background / Banner */}
{bannerUrl ? (
<div
className="absolute inset-0 w-full h-full bg-cover bg-center transition-transform duration-700 group-hover:scale-105"
style={{ backgroundImage: `url(${bannerUrl})` }}
/>
) : (
<div className="absolute inset-0 w-full h-full bg-gradient-to-r from-slate-900 via-purple-900 to-slate-900" />
)}
{/* Overlay */}
<div className="absolute inset-0 bg-black/40 backdrop-blur-[2px]" />
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent" />
{/* Content */}
<div className="absolute bottom-0 left-0 w-full p-6 md:p-8">
<div className="container mx-auto flex flex-col md:flex-row items-end md:items-end gap-6">
{/* Logo */}
<div className="relative -mb-2 md:mb-0 shrink-0">
<MicrodaoBrandBadge
logoUrl={logoUrl}
name={name}
size="xl"
className="ring-4 ring-black/20 shadow-2xl"
/>
</div>
{/* Text */}
<div className="flex-1 mb-1">
<h1 className="text-3xl md:text-4xl font-bold text-white drop-shadow-lg tracking-tight">
{name}
</h1>
{tagline && (
<p className="text-gray-200 text-lg mt-1 max-w-2xl drop-shadow-md font-light">
{tagline}
</p>
)}
</div>
{/* Actions */}
{children && (
<div className="flex items-center gap-3 mt-4 md:mt-0">
{children}
</div>
)}
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,90 @@
import React from 'react';
import { MicrodaoBrandBadge } from './MicrodaoBrandBadge';
import { Hash, Users, Info } from 'lucide-react';
interface RoomBrandHeaderProps {
bannerUrl?: string | null;
logoUrl?: string | null; // Room logo
microdaoLogoUrl?: string | null; // Fallback to MicroDAO logo
name: string;
description?: string | null;
microdaoName?: string;
membersCount?: number;
children?: React.ReactNode;
}
export const RoomBrandHeader: React.FC<RoomBrandHeaderProps> = ({
bannerUrl,
logoUrl,
microdaoLogoUrl,
name,
description,
microdaoName,
membersCount,
children
}) => {
// Use room logo if available, else MicroDAO logo
const displayLogo = logoUrl || microdaoLogoUrl;
return (
<div className="relative w-full h-32 md:h-40 bg-gray-800 overflow-hidden rounded-t-xl shrink-0">
{/* Background / Banner */}
{bannerUrl ? (
<div
className="absolute inset-0 w-full h-full bg-cover bg-center"
style={{ backgroundImage: `url(${bannerUrl})` }}
/>
) : (
<div className="absolute inset-0 w-full h-full bg-gradient-to-r from-gray-800 via-gray-700 to-gray-800" />
)}
{/* Overlay */}
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent" />
{/* Top Actions (Children) */}
{children && (
<div className="absolute top-0 left-0 w-full p-4 z-10">
{children}
</div>
)}
{/* Content */}
<div className="absolute bottom-0 left-0 w-full p-4 md:p-6 flex items-end gap-4">
<div className="shrink-0">
<MicrodaoBrandBadge
logoUrl={displayLogo}
name={name}
size="lg"
className="ring-2 ring-black/10 shadow-lg bg-white"
/>
</div>
<div className="flex-1 min-w-0 mb-0.5">
<div className="flex items-center gap-2 text-gray-300 text-xs uppercase tracking-wider font-medium mb-0.5">
{microdaoName && <span>{microdaoName}</span>}
{membersCount !== undefined && (
<>
<span></span>
<span className="flex items-center gap-1">
<Users size={10} />
{membersCount} online
</span>
</>
)}
</div>
<h2 className="text-xl md:text-2xl font-bold text-white flex items-center gap-2 truncate">
<Hash className="text-gray-400 w-5 h-5 md:w-6 md:h-6 shrink-0" />
{name}
</h2>
{description && (
<p className="text-gray-300 text-sm truncate max-w-xl opacity-90">
{description}
</p>
)}
</div>
</div>
</div>
);
};