FlowForge/frontend/src/pages/Dashboard.js

183 lines
6.9 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import api from '../services/api';
import { PlusIcon, ArrowPathIcon } from '@heroicons/react/24/outline';
const Dashboard = () => {
const [recentWorkflows, setRecentWorkflows] = useState([]);
const [stats, setStats] = useState({
totalWorkflows: 0,
executionsToday: 0,
activeWebhooks: 0
});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchDashboardData = async () => {
try {
// Fetch workflows
const workflowsResponse = await api.get('/api/workflows');
const workflows = workflowsResponse.data.workflows || [];
// Sort by updated_at and take the 5 most recent
const sortedWorkflows = [...workflows].sort(
(a, b) => new Date(b.updated_at) - new Date(a.updated_at)
).slice(0, 5);
setRecentWorkflows(sortedWorkflows);
// Set stats
setStats({
totalWorkflows: workflows.length,
executionsToday: 0, // This would come from a separate API endpoint
activeWebhooks: workflows.reduce((count, workflow) => {
// Count webhook nodes in each workflow
const webhookNodes = workflow.nodes.filter(node => node.type === 'webhook');
return count + webhookNodes.length;
}, 0)
});
} catch (error) {
console.error('Error fetching dashboard data:', error);
} finally {
setLoading(false);
}
};
fetchDashboardData();
}, []);
return (
<div className="space-y-6">
{/* Stats */}
<div className="grid grid-cols-1 gap-5 sm:grid-cols-3">
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<dt className="text-sm font-medium text-gray-500 truncate">
Total Workflows
</dt>
<dd className="mt-1 text-3xl font-semibold text-gray-900">
{loading ? (
<div className="animate-pulse h-8 w-16 bg-gray-200 rounded"></div>
) : (
stats.totalWorkflows
)}
</dd>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<dt className="text-sm font-medium text-gray-500 truncate">
Executions Today
</dt>
<dd className="mt-1 text-3xl font-semibold text-gray-900">
{loading ? (
<div className="animate-pulse h-8 w-16 bg-gray-200 rounded"></div>
) : (
stats.executionsToday
)}
</dd>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<dt className="text-sm font-medium text-gray-500 truncate">
Active Webhooks
</dt>
<dd className="mt-1 text-3xl font-semibold text-gray-900">
{loading ? (
<div className="animate-pulse h-8 w-16 bg-gray-200 rounded"></div>
) : (
stats.activeWebhooks
)}
</dd>
</div>
</div>
</div>
{/* Recent Workflows */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-5 border-b border-gray-200 sm:px-6 flex justify-between items-center">
<h3 className="text-lg leading-6 font-medium text-gray-900">
Recent Workflows
</h3>
<Link
to="/workflows/new"
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
>
<PlusIcon className="-ml-0.5 mr-2 h-4 w-4" />
New Workflow
</Link>
</div>
<div className="px-4 py-5 sm:p-6">
{loading ? (
<div className="space-y-4">
{[...Array(3)].map((_, i) => (
<div key={i} className="animate-pulse flex items-center">
<div className="h-10 w-10 rounded-md bg-gray-200"></div>
<div className="ml-4 flex-1">
<div className="h-4 w-3/4 bg-gray-200 rounded"></div>
<div className="mt-2 h-3 w-1/2 bg-gray-200 rounded"></div>
</div>
</div>
))}
</div>
) : recentWorkflows.length > 0 ? (
<ul className="divide-y divide-gray-200">
{recentWorkflows.map((workflow) => (
<li key={workflow.id} className="py-4">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10 bg-primary-100 text-primary-600 rounded-md flex items-center justify-center">
<ArrowPathIcon className="h-6 w-6" />
</div>
<div className="ml-4 flex-1">
<div className="flex items-center justify-between">
<Link
to={`/workflows/${workflow.id}`}
className="text-sm font-medium text-primary-600 hover:text-primary-900"
>
{workflow.name}
</Link>
<div className="text-xs text-gray-500">
{new Date(workflow.updated_at).toLocaleDateString()}
</div>
</div>
<div className="mt-1 text-xs text-gray-500">
{workflow.nodes.length} nodes, {workflow.connections.length} connections
</div>
</div>
</div>
</li>
))}
</ul>
) : (
<div className="text-center py-6">
<p className="text-gray-500 text-sm">
You don't have any workflows yet.
</p>
<Link
to="/workflows/new"
className="mt-3 inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700"
>
Create your first workflow
</Link>
</div>
)}
</div>
{recentWorkflows.length > 0 && (
<div className="px-4 py-4 sm:px-6">
<Link
to="/workflows"
className="text-sm font-medium text-primary-600 hover:text-primary-500"
>
View all workflows
<span aria-hidden="true"> &rarr;</span>
</Link>
</div>
)}
</div>
</div>
);
};
export default Dashboard;