feat(FE): Automation Hub page — 4 n8n agents overview, setup guide, execution history

This commit is contained in:
admin-valentin 2026-08-26 15:49:21 +00:00
parent 94f0eeda7e
commit 6ae3a75cb1

View file

@ -0,0 +1,177 @@
'use client';
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { apiFetch } from '../../../lib/api';
import { useSession } from '../../../components/session-provider';
const WORKFLOWS = [
{
id: 'weekly-review',
name: 'Weekly Review Generator',
icon: '🔄',
schedule: 'Luni, 8:00',
description: 'Generează retrospectiva săptămânii: task-uri finalizate, obiective, lecții. Salvat ca observație.',
file: 'weekly-review.json',
color: 'text-indigo-600',
bg: 'bg-indigo-50',
},
{
id: 'daily-planner',
name: 'Daily Planner',
icon: '📅',
schedule: 'LunVin, 7:00',
description: 'Compilează brief-ul zilnic AI + task-urile prioritare active. Plan salvat în observații.',
file: 'daily-planner.json',
color: 'text-emerald-600',
bg: 'bg-emerald-50',
},
{
id: 'gmail-bridge',
name: 'Gmail → CEO OS',
icon: '📧',
schedule: 'La fiecare 15 min',
description: 'Citește emailuri importante din Gmail, clasifică cu AI (skip/info/action), creează task-uri automat.',
file: 'gmail-bridge.json',
color: 'text-red-600',
bg: 'bg-red-50',
},
{
id: 'financial-brief',
name: 'Financial Weekly Brief',
icon: '💰',
schedule: 'Vineri, 17:00',
description: 'Calculează sold, contracte active și cheltuieli. Generează brief financiar cu AI și îl salvează.',
file: 'financial-brief.json',
color: 'text-amber-600',
bg: 'bg-amber-50',
},
];
const ENV_VARS = [
{ key: 'CEO_OS_API_URL', value: 'https://boardmind.dev/api', desc: 'URL de bază CEO OS API' },
{ key: 'CEO_OS_TENANT_ID', value: '<tenantId-ul tău>', desc: 'Se găsește în Settings → Workspace' },
{ key: 'CEO_OS_SERVICE_KEY', value: '<valoarea din Coolify>', desc: 'Trebuie să fie egal cu N8N_SERVICE_KEY din ceo-api' },
];
const N8N_STEPS = [
'Deschide n8n → Settings → Variables → adaugă cele 3 variabile de mai jos',
'Importă fiecare workflow JSON din Forgejo → ceo-api/n8n-workflows/',
'Pentru Gmail Bridge: adaugă credential Gmail OAuth2 în n8n → Credentials',
'Activează workflow-urile pe rând. Testează cu "Execute Workflow" manual prima dată.',
'Monitorizează execuțiile din n8n → Executions.',
];
export default function AutomationHubPage() {
const { activeTenant } = useSession();
const tenantId = activeTenant?.tenantId ?? '';
const [copied, setCopied] = useState('');
// Show automation results from observations
const { data: observations = [] } = useQuery({
queryKey: ['automation-obs', tenantId],
queryFn: () => apiFetch<any[]>('/v1/observations', { tenantId }),
enabled: Boolean(tenantId), staleTime: 60_000,
});
const autoObs = observations.filter((o) =>
['weekly-review', 'daily-plan', 'financial-brief', 'email-received'].includes(o.metric),
).sort((a, b) => (b.observedAt ?? b.createdAt).localeCompare(a.observedAt ?? a.createdAt));
function copy(text: string, id: string) {
navigator.clipboard.writeText(text);
setCopied(id);
setTimeout(() => setCopied(''), 2000);
}
return (
<div className="max-w-5xl space-y-8 p-6">
<div>
<h1 className="font-display text-2xl font-semibold text-ink">Automation Hub</h1>
<p className="text-sm text-ink-faint mt-1">4 agenți n8n care lucrează automat cu CEO OS</p>
</div>
{/* Workflow cards */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{WORKFLOWS.map((wf) => (
<div key={wf.id} className="card p-5 space-y-3">
<div className="flex items-start gap-3">
<span className={`flex h-10 w-10 items-center justify-center rounded-xl text-xl ${wf.bg} shrink-0`}>
{wf.icon}
</span>
<div className="flex-1">
<p className={`text-sm font-semibold ${wf.color}`}>{wf.name}</p>
<p className="text-xs text-ink-faint"> {wf.schedule}</p>
</div>
</div>
<p className="text-xs text-ink leading-relaxed">{wf.description}</p>
<div className="flex items-center gap-2 pt-1">
<span className="rounded-full bg-muted px-2 py-0.5 text-[10px] font-mono text-ink-faint">
n8n-workflows/{wf.file}
</span>
</div>
</div>
))}
</div>
{/* Setup guide */}
<div className="card p-5 space-y-4">
<p className="text-sm font-semibold text-ink">Pași de configurare n8n</p>
<ol className="space-y-2">
{N8N_STEPS.map((step, i) => (
<li key={i} className="flex gap-3 text-xs text-ink">
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary font-semibold text-[10px]">
{i + 1}
</span>
{step}
</li>
))}
</ol>
</div>
{/* Environment variables */}
<div className="card p-5 space-y-3">
<p className="text-sm font-semibold text-ink">Variabile de setat în n8n (Settings Variables)</p>
<div className="space-y-2">
{ENV_VARS.map((v) => (
<div key={v.key} className="flex items-center gap-3 rounded-lg border bg-muted/20 px-3 py-2">
<code className="text-xs font-mono font-semibold text-primary w-44 shrink-0">{v.key}</code>
<code className="flex-1 text-xs font-mono text-ink-faint truncate">{v.value}</code>
<button onClick={() => copy(`${v.key}=${v.value}`, v.key)}
className="text-[10px] text-ink-faint hover:text-primary shrink-0">
{copied === v.key ? '✓' : 'Copy'}
</button>
</div>
))}
</div>
<p className="text-[10px] text-ink-faint">
<strong>N8N_SERVICE_KEY</strong> setat în Coolify ceo-api env + n8n Variables simultan cu aceeași valoare.
TenantId-ul Settings Workspace în CEO OS.
</p>
</div>
{/* Recent automation results */}
{autoObs.length > 0 && (
<div className="card p-5 space-y-3">
<p className="text-sm font-semibold text-ink">Execuții recente</p>
<div className="divide-y divide-border/40">
{autoObs.slice(0, 10).map((o) => (
<div key={o.id} className="flex items-start gap-3 py-3">
<span className="text-lg shrink-0">
{o.metric === 'weekly-review' ? '🔄' : o.metric === 'daily-plan' ? '📅' : o.metric === 'financial-brief' ? '💰' : '📧'}
</span>
<div className="flex-1 min-w-0">
<p className="text-xs font-medium text-ink">{o.metric}</p>
<p className="text-xs text-ink-faint line-clamp-2">{String(o.value).slice(0, 120)}</p>
</div>
<p className="text-[10px] text-ink-faint shrink-0">
{new Date(o.observedAt ?? o.createdAt).toLocaleDateString('ro-RO', {day:'numeric', month:'short'})}
</p>
</div>
))}
</div>
</div>
)}
</div>
);
}