forge/frontend/app/admin/page.tsx
DJP 7a804e896d Initial commit - FORGE AI unified platform
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>
2025-12-09 20:39:00 -05:00

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>
);
}