feat(FE): Automation Hub page — 4 n8n agents overview, setup guide, execution history
This commit is contained in:
parent
94f0eeda7e
commit
6ae3a75cb1
1 changed files with 177 additions and 0 deletions
177
src/app/dashboard/automation/page.tsx
Normal file
177
src/app/dashboard/automation/page.tsx
Normal 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: 'Lun–Vin, 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>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue