183 lines
6.9 KiB
JavaScript
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"> →</span>
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Dashboard;
|