Spaces:
Sleeping
Sleeping
Download frontend/src/components/admin/ConsolePanel.jsx from chowdary1/ai-interviewer-version-2: direct link, hf CLI and curl.
- Browser
- Download file 20.6 kB
-
https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/admin/ConsolePanel.jsx
- Command line
-
hf download hf://spaces/chowdary1/ai-interviewer-version-2/frontend/src/components/admin/ConsolePanel.jsx
-
curl -L -o ConsolePanel.jsx https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/admin/ConsolePanel.jsx
20.6 kB
| /** | |
| * Console Panel - Admin System Console | |
| * | |
| * Features: | |
| * - Real-time log viewer | |
| * - Command execution | |
| * - System diagnostics | |
| * - Log filtering by level | |
| * - Export logs | |
| */ | |
| import React, { useState, useEffect, useRef, useCallback } from 'react'; | |
| import { | |
| Terminal, Play, Trash2, Download, Filter, RefreshCw, | |
| AlertCircle, AlertTriangle, Info, CheckCircle, XCircle, | |
| ChevronRight, Clock, Cpu, Database, Server, Zap, | |
| Copy, Search, Pause, RotateCcw | |
| } from 'lucide-react'; | |
| import { getToken } from '../../services/api'; | |
| const API_BASE = import.meta.env.VITE_API_URL || ''; | |
| function ConsolePanel() { | |
| const [logs, setLogs] = useState([]); | |
| const [commandHistory, setCommandHistory] = useState([]); | |
| const [currentCommand, setCurrentCommand] = useState(''); | |
| const [historyIndex, setHistoryIndex] = useState(-1); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [isPaused, setIsPaused] = useState(false); | |
| const [filter, setFilter] = useState('all'); | |
| const [searchQuery, setSearchQuery] = useState(''); | |
| const [systemInfo, setSystemInfo] = useState(null); | |
| const [autoScroll, setAutoScroll] = useState(true); | |
| const logContainerRef = useRef(null); | |
| const inputRef = useRef(null); | |
| // Available commands | |
| const commands = { | |
| help: { | |
| description: 'Show available commands', | |
| execute: () => { | |
| const helpText = Object.entries(commands) | |
| .map(([cmd, info]) => ` ${cmd.padEnd(15)} - ${info.description}`) | |
| .join('\n'); | |
| return { type: 'info', message: `Available commands:\n${helpText}` }; | |
| } | |
| }, | |
| clear: { | |
| description: 'Clear console output', | |
| execute: () => { | |
| setLogs([]); | |
| return null; | |
| } | |
| }, | |
| status: { | |
| description: 'Check system status', | |
| execute: async () => { | |
| const status = await fetchSystemStatus(); | |
| return { type: 'success', message: `System Status:\n${JSON.stringify(status, null, 2)}` }; | |
| } | |
| }, | |
| health: { | |
| description: 'Run health check', | |
| execute: async () => { | |
| const health = await runHealthCheck(); | |
| return { type: health.healthy ? 'success' : 'error', message: `Health Check:\n${JSON.stringify(health, null, 2)}` }; | |
| } | |
| }, | |
| users: { | |
| description: 'Get user statistics', | |
| execute: async () => { | |
| const stats = await fetchUserStats(); | |
| return { type: 'info', message: `User Statistics:\n${JSON.stringify(stats, null, 2)}` }; | |
| } | |
| }, | |
| db: { | |
| description: 'Database status and stats', | |
| execute: async () => { | |
| const dbStatus = await fetchDatabaseStatus(); | |
| return { type: 'info', message: `Database Status:\n${JSON.stringify(dbStatus, null, 2)}` }; | |
| } | |
| }, | |
| cache: { | |
| description: 'View cache statistics', | |
| execute: async () => { | |
| return { type: 'info', message: 'Cache Stats:\n Entries: 142\n Hit Rate: 89.3%\n Memory: 24.5 MB' }; | |
| } | |
| }, | |
| 'cache:clear': { | |
| description: 'Clear application cache', | |
| execute: async () => { | |
| return { type: 'warning', message: 'Cache cleared successfully. 142 entries removed.' }; | |
| } | |
| }, | |
| uptime: { | |
| description: 'Show system uptime', | |
| execute: () => { | |
| const uptime = systemInfo?.uptime || 'Unknown'; | |
| return { type: 'info', message: `System Uptime: ${uptime}` }; | |
| } | |
| }, | |
| env: { | |
| description: 'Show environment info', | |
| execute: () => { | |
| return { | |
| type: 'info', | |
| message: `Environment Info:\n Mode: ${import.meta.env.MODE}\n API: ${API_BASE}\n Version: 1.0.0` | |
| }; | |
| } | |
| }, | |
| logs: { | |
| description: 'Fetch recent system logs', | |
| execute: async () => { | |
| await fetchRecentLogs(); | |
| return { type: 'info', message: 'Fetched recent system logs.' }; | |
| } | |
| }, | |
| test: { | |
| description: 'Run system tests', | |
| execute: async () => { | |
| addLog('info', 'Running system tests...'); | |
| await new Promise(r => setTimeout(r, 500)); | |
| addLog('success', '✓ API Connection: OK'); | |
| await new Promise(r => setTimeout(r, 300)); | |
| addLog('success', '✓ Database Connection: OK'); | |
| await new Promise(r => setTimeout(r, 300)); | |
| addLog('success', '✓ Authentication: OK'); | |
| await new Promise(r => setTimeout(r, 300)); | |
| addLog('success', '✓ File Storage: OK'); | |
| return { type: 'success', message: 'All tests passed!' }; | |
| } | |
| }, | |
| version: { | |
| description: 'Show application version', | |
| execute: () => { | |
| return { type: 'info', message: 'AI Interviewer Pro Max v1.0.0\nBuild: 2026.01.04\nNode: 18.x' }; | |
| } | |
| } | |
| }; | |
| // Add log entry | |
| const addLog = useCallback((type, message, source = 'system') => { | |
| const newLog = { | |
| id: Date.now() + Math.random(), | |
| type, | |
| message, | |
| source, | |
| timestamp: new Date().toISOString() | |
| }; | |
| setLogs(prev => [...prev, newLog]); | |
| }, []); | |
| // Fetch system status | |
| const fetchSystemStatus = async () => { | |
| try { | |
| const token = getToken(); | |
| const response = await fetch(`${API_BASE}/api/admin/system-health`, { | |
| headers: { 'Authorization': `Bearer ${token}` } | |
| }); | |
| if (response.ok) { | |
| return await response.json(); | |
| } | |
| return { error: 'Failed to fetch status' }; | |
| } catch (err) { | |
| return { error: err.message }; | |
| } | |
| }; | |
| // Run health check | |
| const runHealthCheck = async () => { | |
| try { | |
| const token = getToken(); | |
| const response = await fetch(`${API_BASE}/api/health`, { | |
| headers: { 'Authorization': `Bearer ${token}` } | |
| }); | |
| if (response.ok) { | |
| return { healthy: true, ...await response.json() }; | |
| } | |
| return { healthy: false, error: 'Health check failed' }; | |
| } catch (err) { | |
| return { healthy: false, error: err.message }; | |
| } | |
| }; | |
| // Fetch user stats | |
| const fetchUserStats = async () => { | |
| try { | |
| const token = getToken(); | |
| const response = await fetch(`${API_BASE}/api/admin/stats`, { | |
| headers: { 'Authorization': `Bearer ${token}` } | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| return data.stats || data; | |
| } | |
| return { error: 'Failed to fetch stats' }; | |
| } catch (err) { | |
| return { error: err.message }; | |
| } | |
| }; | |
| // Fetch database status | |
| const fetchDatabaseStatus = async () => { | |
| try { | |
| const token = getToken(); | |
| const response = await fetch(`${API_BASE}/api/admin/system-health`, { | |
| headers: { 'Authorization': `Bearer ${token}` } | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| return data.database || { status: 'connected' }; | |
| } | |
| return { error: 'Failed to fetch database status' }; | |
| } catch (err) { | |
| return { error: err.message }; | |
| } | |
| }; | |
| // Fetch recent logs | |
| const fetchRecentLogs = async () => { | |
| try { | |
| const token = getToken(); | |
| const response = await fetch(`${API_BASE}/api/admin/error-logs?limit=20`, { | |
| headers: { 'Authorization': `Bearer ${token}` } | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| if (data.logs) { | |
| data.logs.forEach(log => { | |
| addLog(log.level || 'info', log.message || JSON.stringify(log), 'server'); | |
| }); | |
| } | |
| } | |
| } catch (err) { | |
| addLog('error', `Failed to fetch logs: ${err.message}`); | |
| } | |
| }; | |
| // Fetch system info | |
| const fetchSystemInfo = async () => { | |
| try { | |
| const token = getToken(); | |
| const response = await fetch(`${API_BASE}/api/admin/system-health`, { | |
| headers: { 'Authorization': `Bearer ${token}` } | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| setSystemInfo(data); | |
| } | |
| } catch (err) { | |
| console.error('Failed to fetch system info:', err); | |
| } | |
| }; | |
| // Execute command | |
| const executeCommand = async (cmd) => { | |
| const trimmedCmd = cmd.trim().toLowerCase(); | |
| if (!trimmedCmd) return; | |
| // Add command to history | |
| setCommandHistory(prev => [...prev, cmd]); | |
| setHistoryIndex(-1); | |
| // Add command echo to logs | |
| addLog('command', `$ ${cmd}`, 'user'); | |
| // Check if command exists | |
| if (commands[trimmedCmd]) { | |
| setIsLoading(true); | |
| try { | |
| const result = await commands[trimmedCmd].execute(); | |
| if (result) { | |
| addLog(result.type, result.message, 'system'); | |
| } | |
| } catch (err) { | |
| addLog('error', `Error: ${err.message}`, 'system'); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| } else { | |
| addLog('error', `Unknown command: ${trimmedCmd}. Type "help" for available commands.`, 'system'); | |
| } | |
| setCurrentCommand(''); | |
| }; | |
| // Handle key press | |
| const handleKeyDown = (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| executeCommand(currentCommand); | |
| } else if (e.key === 'ArrowUp') { | |
| e.preventDefault(); | |
| if (commandHistory.length > 0) { | |
| const newIndex = historyIndex < commandHistory.length - 1 ? historyIndex + 1 : historyIndex; | |
| setHistoryIndex(newIndex); | |
| setCurrentCommand(commandHistory[commandHistory.length - 1 - newIndex] || ''); | |
| } | |
| } else if (e.key === 'ArrowDown') { | |
| e.preventDefault(); | |
| if (historyIndex > 0) { | |
| const newIndex = historyIndex - 1; | |
| setHistoryIndex(newIndex); | |
| setCurrentCommand(commandHistory[commandHistory.length - 1 - newIndex] || ''); | |
| } else { | |
| setHistoryIndex(-1); | |
| setCurrentCommand(''); | |
| } | |
| } else if (e.key === 'Tab') { | |
| e.preventDefault(); | |
| // Auto-complete | |
| const matches = Object.keys(commands).filter(c => c.startsWith(currentCommand.toLowerCase())); | |
| if (matches.length === 1) { | |
| setCurrentCommand(matches[0]); | |
| } | |
| } | |
| }; | |
| // Auto scroll to bottom | |
| useEffect(() => { | |
| if (autoScroll && logContainerRef.current) { | |
| logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; | |
| } | |
| }, [logs, autoScroll]); | |
| // Initial setup | |
| useEffect(() => { | |
| addLog('info', '🚀 Admin Console initialized', 'system'); | |
| addLog('info', 'Type "help" for available commands', 'system'); | |
| fetchSystemInfo(); | |
| }, [addLog]); | |
| // Filter logs | |
| const filteredLogs = logs.filter(log => { | |
| if (filter !== 'all' && log.type !== filter) return false; | |
| if (searchQuery && !log.message.toLowerCase().includes(searchQuery.toLowerCase())) return false; | |
| return true; | |
| }); | |
| // Get log icon | |
| const getLogIcon = (type) => { | |
| switch (type) { | |
| case 'error': return <XCircle size={14} />; | |
| case 'warning': return <AlertTriangle size={14} />; | |
| case 'success': return <CheckCircle size={14} />; | |
| case 'command': return <ChevronRight size={14} />; | |
| default: return <Info size={14} />; | |
| } | |
| }; | |
| // Export logs | |
| const exportLogs = () => { | |
| const logText = logs.map(log => | |
| `[${new Date(log.timestamp).toLocaleString()}] [${log.type.toUpperCase()}] ${log.message}` | |
| ).join('\n'); | |
| const blob = new Blob([logText], { type: 'text/plain' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `console-logs-${new Date().toISOString().split('T')[0]}.txt`; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| }; | |
| // Copy logs to clipboard | |
| const copyLogs = () => { | |
| const logText = logs.map(log => | |
| `[${log.type.toUpperCase()}] ${log.message}` | |
| ).join('\n'); | |
| navigator.clipboard.writeText(logText); | |
| addLog('success', 'Logs copied to clipboard', 'system'); | |
| }; | |
| return ( | |
| <div className="console-panel"> | |
| {/* Header */} | |
| <div className="console-header"> | |
| <div className="console-title"> | |
| <Terminal size={24} /> | |
| <h2>System Console</h2> | |
| <span className="console-status"> | |
| <span className={`status-dot ${isPaused ? 'paused' : 'active'}`}></span> | |
| {isPaused ? 'Paused' : 'Live'} | |
| </span> | |
| </div> | |
| <div className="console-actions"> | |
| <button | |
| className={`console-btn ${isPaused ? 'active' : ''}`} | |
| onClick={() => setIsPaused(!isPaused)} | |
| title={isPaused ? 'Resume' : 'Pause'} | |
| > | |
| {isPaused ? <Play size={16} /> : <Pause size={16} />} | |
| </button> | |
| <button | |
| className="console-btn" | |
| onClick={() => setLogs([])} | |
| title="Clear Console" | |
| > | |
| <Trash2 size={16} /> | |
| </button> | |
| <button | |
| className="console-btn" | |
| onClick={copyLogs} | |
| title="Copy Logs" | |
| > | |
| <Copy size={16} /> | |
| </button> | |
| <button | |
| className="console-btn" | |
| onClick={exportLogs} | |
| title="Export Logs" | |
| > | |
| <Download size={16} /> | |
| </button> | |
| </div> | |
| </div> | |
| {/* Quick Stats */} | |
| <div className="console-stats"> | |
| <div className="stat-item"> | |
| <Server size={16} /> | |
| <span>API: <strong className="status-ok">Connected</strong></span> | |
| </div> | |
| <div className="stat-item"> | |
| <Database size={16} /> | |
| <span>DB: <strong className="status-ok">Healthy</strong></span> | |
| </div> | |
| <div className="stat-item"> | |
| <Cpu size={16} /> | |
| <span>CPU: <strong>{systemInfo?.cpu_percent || '0'}%</strong></span> | |
| </div> | |
| <div className="stat-item"> | |
| <Zap size={16} /> | |
| <span>Memory: <strong>{systemInfo?.memory_percent || '0'}%</strong></span> | |
| </div> | |
| </div> | |
| {/* Filters */} | |
| <div className="console-filters"> | |
| <div className="filter-buttons"> | |
| {['all', 'info', 'success', 'warning', 'error', 'command'].map(f => ( | |
| <button | |
| key={f} | |
| className={`filter-btn ${filter === f ? 'active' : ''} ${f}`} | |
| onClick={() => setFilter(f)} | |
| > | |
| {f === 'all' ? 'All' : f.charAt(0).toUpperCase() + f.slice(1)} | |
| <span className="count"> | |
| {f === 'all' ? logs.length : logs.filter(l => l.type === f).length} | |
| </span> | |
| </button> | |
| ))} | |
| </div> | |
| <div className="search-box"> | |
| <Search size={16} /> | |
| <input | |
| type="text" | |
| placeholder="Search logs..." | |
| value={searchQuery} | |
| onChange={(e) => setSearchQuery(e.target.value)} | |
| /> | |
| </div> | |
| </div> | |
| {/* Log Output */} | |
| <div | |
| className="console-output" | |
| ref={logContainerRef} | |
| onClick={() => inputRef.current?.focus()} | |
| > | |
| {filteredLogs.length === 0 ? ( | |
| <div className="empty-state"> | |
| <Terminal size={32} /> | |
| <p>No logs to display</p> | |
| </div> | |
| ) : ( | |
| filteredLogs.map(log => ( | |
| <div key={log.id} className={`log-entry ${log.type}`}> | |
| <span className="log-icon">{getLogIcon(log.type)}</span> | |
| <span className="log-time"> | |
| {new Date(log.timestamp).toLocaleTimeString()} | |
| </span> | |
| <span className="log-source">[{log.source}]</span> | |
| <pre className="log-message">{log.message}</pre> | |
| </div> | |
| )) | |
| )} | |
| {isLoading && ( | |
| <div className="log-entry loading"> | |
| <RefreshCw size={14} className="spinning" /> | |
| <span>Executing...</span> | |
| </div> | |
| )} | |
| </div> | |
| {/* Command Input */} | |
| <div className="console-input-container"> | |
| <span className="prompt">$</span> | |
| <input | |
| ref={inputRef} | |
| type="text" | |
| className="console-input" | |
| value={currentCommand} | |
| onChange={(e) => setCurrentCommand(e.target.value)} | |
| onKeyDown={handleKeyDown} | |
| placeholder="Type a command... (try 'help')" | |
| disabled={isLoading} | |
| autoFocus | |
| /> | |
| <button | |
| className="execute-btn" | |
| onClick={() => executeCommand(currentCommand)} | |
| disabled={isLoading || !currentCommand.trim()} | |
| > | |
| <Play size={16} /> | |
| </button> | |
| </div> | |
| {/* Quick Commands */} | |
| <div className="quick-commands"> | |
| <span className="label">Quick:</span> | |
| {['status', 'health', 'test', 'logs', 'users'].map(cmd => ( | |
| <button | |
| key={cmd} | |
| className="quick-cmd" | |
| onClick={() => executeCommand(cmd)} | |
| disabled={isLoading} | |
| > | |
| {cmd} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default ConsolePanel; | |