Srini P
Fresh cleaner push without any mp4
e7586f8
Raw
History Blame Contribute Delete
15.2 kB
'use client';
import React, { useState, useEffect } from 'react';
import { User } from '@/lib/types';
import { api } from '@/lib/api';
import { X, Plus, Loader2, Check, AlertCircle } from 'lucide-react';
import {ROLE_COLORS } from '@/lib/constants';
interface AdminPanelProps {
onClose: () => void;
}
export default function AdminPanel({ onClose }: AdminPanelProps) {
const [activeTab, setActiveTab] = useState<'users' | 'management'>('users');
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
const [formData, setFormData] = useState({
username: '',
name: '',
role: 'employee' as const,
department: '',
});
useEffect(() => {
loadUsers();
}, []);
const loadUsers = async () => {
try {
setLoading(true);
const userList = await api.getUsers();
setUsers(userList);
} catch (err) {
setMessage({ type: 'error', text: 'Failed to load users' });
} finally {
setLoading(false);
}
};
const handleCreateUser = async (e: React.FormEvent) => {
e.preventDefault();
if (!formData.username || !formData.name || !formData.department) {
setMessage({ type: 'error', text: 'All fields are required' });
return;
}
try {
setLoading(true);
await api.adminCreateUser({
username: formData.username,
name: formData.name,
role: formData.role,
department: formData.department,
});
setMessage({ type: 'success', text: 'User created successfully' });
setFormData({ username: '', name: '', role: 'employee', department: '' });
await loadUsers();
} catch (err) {
setMessage({ type: 'error', text: 'Failed to create user' });
} finally {
setLoading(false);
}
};
const handleIngest = async () => {
try {
setLoading(true);
await api.adminIngest();
setMessage({ type: 'success', text: 'Document ingestion completed successfully' });
} catch (err) {
setMessage({ type: 'error', text: 'Failed to trigger ingestion' });
} finally {
setLoading(false);
}
};
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] overflow-hidden flex flex-col">
{/* Header */}
<div className="border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Admin Panel</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<X size={24} />
</button>
</div>
{/* Tabs */}
<div className="border-b border-gray-200 flex">
<button
onClick={() => setActiveTab('users')}
className={`flex-1 px-6 py-4 font-semibold transition-colors ${
activeTab === 'users'
? 'border-b-2 border-primary-600 text-primary-600'
: 'text-gray-600 hover:text-gray-900'
}`}
>
User Management
</button>
<button
onClick={() => setActiveTab('management')}
className={`flex-1 px-6 py-4 font-semibold transition-colors ${
activeTab === 'management'
? 'border-b-2 border-primary-600 text-primary-600'
: 'text-gray-600 hover:text-gray-900'
}`}
>
System Management
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-6">
{/* Message */}
{message && (
<div
className={`mb-4 p-4 rounded-lg flex items-center gap-3 ${
message.type === 'success'
? 'bg-green-50 border border-green-200 text-green-700'
: 'bg-red-50 border border-red-200 text-red-700'
}`}
>
{message.type === 'success' ? <Check size={20} /> : <AlertCircle size={20} />}
{message.text}
</div>
)}
{/* Users Tab */}
{activeTab === 'users' && (
<div className="space-y-6">
{/* Create User Form */}
<div className="bg-gray-50 border border-gray-200 rounded-lg p-6">
<h3 className="text-lg font-bold text-gray-900 mb-4">Create New User</h3>
<form onSubmit={handleCreateUser} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Username
</label>
<input
type="text"
value={formData.username}
onChange={(e) =>
setFormData({ ...formData, username: e.target.value })
}
placeholder="e.g., user_john"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Full Name
</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="e.g., John Doe"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Role
</label>
<select
value={formData.role}
onChange={(e) =>
setFormData({
...formData,
role: e.target.value as any,
})
}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<option value="employee">Employee</option>
<option value="finance">Finance</option>
<option value="engineering">Engineering</option>
<option value="marketing">Marketing</option>
<option value="c_level">C-Level</option>
</select>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Department
</label>
<input
type="text"
value={formData.department}
onChange={(e) =>
setFormData({ ...formData, department: e.target.value })
}
placeholder="e.g., Finance, Engineering"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
</div>
</div>
<button
type="submit"
disabled={loading}
className="w-full px-4 py-3 bg-primary-600 hover:bg-primary-700 disabled:opacity-50 text-white rounded-lg font-semibold flex items-center justify-center gap-2 transition-colors"
>
{loading ? (
<>
<Loader2 className="animate-spin" size={18} />
Creating...
</>
) : (
<>
<Plus size={18} />
Create User
</>
)}
</button>
</form>
</div>
{/* Users List */}
<div>
<h3 className="text-lg font-bold text-gray-900 mb-4">Current Users</h3>
<div className="grid gap-3 max-h-96 overflow-y-auto">
{users.map((user) => (
<div key={user.username} className="border border-gray-200 rounded-lg p-4">
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="font-bold text-gray-900">{user.name}</p>
<p className="text-sm text-gray-600">@{user.username}</p>
<div className="flex items-center gap-3 mt-2">
<span
className={`px-3 py-1 rounded-full text-xs font-semibold ${ROLE_COLORS[user.role]}`}
>
{user.role}
</span>
<span className="text-xs text-gray-600">{user.department}</span>
</div>
</div>
<div className="text-right">
<p className="text-xs font-semibold text-gray-700 mb-1">Access:</p>
<div className="flex flex-wrap gap-1 justify-end">
{(user.accessible_collections ?? []).map((col) => (
<span
key={col}
className="inline-block bg-blue-100 text-blue-700 px-2 py-1 rounded text-xs"
>
{col}
</span>
))}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)}
{/* Management Tab */}
{activeTab === 'management' && (
<div className="space-y-6">
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6">
<h3 className="text-lg font-bold text-blue-900 mb-4">Document Ingestion</h3>
<p className="text-blue-700 mb-4">
Re-ingest all documents from the data folder. This will parse, chunk, embed, and store all documents in the vector database.
</p>
<button
onClick={handleIngest}
disabled={loading}
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:opacity-50 text-white rounded-lg font-semibold transition-colors flex items-center gap-2"
>
{loading ? (
<>
<Loader2 className="animate-spin" size={18} />
Ingesting...
</>
) : (
'📥 Re-Ingest Documents'
)}
</button>
</div>
<div className="bg-gray-50 border border-gray-200 rounded-lg p-6">
<h3 className="text-lg font-bold text-gray-900 mb-4">System Configuration</h3>
<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-white border border-gray-200 rounded">
<div>
<p className="font-semibold text-gray-900">RBAC Enforcement</p>
<p className="text-sm text-gray-600">Enabled at vector database level</p>
</div>
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
</div>
<div className="flex items-center justify-between p-3 bg-white border border-gray-200 rounded">
<div>
<p className="font-semibold text-gray-900">Input Guardrails</p>
<p className="text-sm text-gray-600">Injection, off-topic, PII detection</p>
</div>
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
</div>
<div className="flex items-center justify-between p-3 bg-white border border-gray-200 rounded">
<div>
<p className="font-semibold text-gray-900">Output Guardrails</p>
<p className="text-sm text-gray-600">Grounding, citations, leakage detection</p>
</div>
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
</div>
<div className="flex items-center justify-between p-3 bg-white border border-gray-200 rounded">
<div>
<p className="font-semibold text-gray-900">Semantic Routing</p>
<p className="text-sm text-gray-600">5 intent-based routes configured</p>
</div>
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
</div>
<div className="flex items-center justify-between p-3 bg-white border border-gray-200 rounded">
<div>
<p className="font-semibold text-gray-900">LLM (Groq)</p>
<p className="text-sm text-gray-600">
Inference via backend <code className="text-xs bg-gray-100 px-1 rounded">GROQ_API_KEY</code> — not stored in the browser
</p>
</div>
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
</div>
</div>
</div>
<div className="bg-purple-50 border border-purple-200 rounded-lg p-6">
<h3 className="text-lg font-bold text-purple-900 mb-4">Collections</h3>
<div className="space-y-3">
{['general', 'finance', 'engineering', 'marketing', 'hr'].map((collection) => (
<div key={collection} className="flex items-center justify-between p-3 bg-white border border-purple-100 rounded">
<span className="font-semibold text-gray-900 capitalize">{collection}</span>
<span className="text-sm text-gray-600">✓ Configured</span>
</div>
))}
</div>
</div>
</div>
)}
</div>
</div>
</div>
);
}