Tech Stack Advisor - Code Viewer

← Back to File Tree

admin.html

Language: html | Path: backend/static/admin.html | Lines: 542
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Admin Dashboard - Tech Stack Advisor</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1400px;
            margin: 0 auto;
        }

        .header {
            background: white;
            border-radius: 20px;
            padding: 30px;
            margin-bottom: 20px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }

        .header h1 {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            font-size: 2em;
            margin-bottom: 10px;
        }

        .header .subtitle {
            color: #666;
            font-size: 1.1em;
        }

        .user-info {
            float: right;
            text-align: right;
        }

        .user-email {
            color: #333;
            font-weight: 600;
            margin-bottom: 10px;
        }

        .admin-badge {
            display: inline-block;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 5px 15px;
            border-radius: 20px;
            font-size: 12px;
            font-weight: 600;
            margin-right: 10px;
        }

        .logout-btn {
            padding: 8px 20px;
            background: rgba(102, 126, 234, 0.1);
            color: #667eea;
            border: 1px solid #667eea;
            border-radius: 6px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }

        .logout-btn:hover {
            background: #667eea;
            color: white;
        }

        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
            margin-bottom: 20px;
        }

        .stat-card {
            background: white;
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
        }

        .stat-card h3 {
            color: #666;
            font-size: 14px;
            margin-bottom: 10px;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        .stat-value {
            font-size: 36px;
            font-weight: bold;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        .stat-label {
            color: #999;
            font-size: 12px;
            margin-top: 5px;
        }

        .tabs {
            background: white;
            border-radius: 15px;
            padding: 10px;
            margin-bottom: 20px;
            display: flex;
            gap: 10px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
        }

        .tab {
            flex: 1;
            padding: 15px;
            border: none;
            background: transparent;
            cursor: pointer;
            font-size: 16px;
            font-weight: 600;
            color: #666;
            border-radius: 10px;
            transition: all 0.3s;
        }

        .tab.active {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }

        .tab-content {
            display: none;
        }

        .tab-content.active {
            display: block;
        }

        .panel {
            background: white;
            border-radius: 15px;
            padding: 30px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
        }

        .panel h2 {
            margin-bottom: 20px;
            color: #333;
        }

        table {
            width: 100%;
            border-collapse: collapse;
        }

        table th {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 15px;
            text-align: left;
            font-weight: 600;
        }

        table td {
            padding: 15px;
            border-bottom: 1px solid #eee;
        }

        table tr:hover {
            background: #f9f9f9;
        }

        .link-to-main {
            display: inline-block;
            margin-top: 20px;
            padding: 12px 24px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            text-decoration: none;
            border-radius: 8px;
            font-weight: 600;
            transition: transform 0.2s;
        }

        .link-to-main:hover {
            transform: translateY(-2px);
        }

        .loading {
            text-align: center;
            padding: 50px;
            color: #666;
            font-size: 18px;
        }

        .error {
            background: #fee;
            color: #c33;
            padding: 20px;
            border-radius: 10px;
            margin-bottom: 20px;
        }

        .empty-state {
            text-align: center;
            padding: 50px;
            color: #999;
        }

        .timestamp {
            color: #999;
            font-size: 12px;
        }

        .cost-high {
            color: #e53e3e;
            font-weight: bold;
        }

        .cost-medium {
            color: #dd6b20;
            font-weight: bold;
        }

        .cost-low {
            color: #38a169;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <div class="user-info" id="userInfo">
                <div class="user-email" id="userEmail"></div>
                <span class="admin-badge">ADMIN</span>
                <button class="logout-btn" onclick="handleLogout()">Logout</button>
            </div>
            <h1>🛡️ Admin Dashboard</h1>
            <p class="subtitle">Manage users, monitor usage, and analyze platform metrics</p>
            <a href="/" class="link-to-main">← Back to Tech Stack Advisor</a>
        </div>

        <div id="loading" class="loading">Loading admin dashboard...</div>
        <div id="error" class="error" style="display: none;"></div>

        <div id="dashboard" style="display: none;">
            <!-- Overview Stats -->
            <div class="stats-grid">
                <div class="stat-card">
                    <h3>Total Users</h3>
                    <div class="stat-value" id="totalUsers">0</div>
                    <div class="stat-label">Registered accounts</div>
                </div>
                <div class="stat-card">
                    <h3>Total Queries</h3>
                    <div class="stat-value" id="totalQueries">0</div>
                    <div class="stat-label">Recommendations generated</div>
                </div>
                <div class="stat-card">
                    <h3>Total Cost</h3>
                    <div class="stat-value" id="totalCost">$0.00</div>
                    <div class="stat-label">Platform-wide spending</div>
                </div>
                <div class="stat-card">
                    <h3>Avg Cost/User</h3>
                    <div class="stat-value" id="avgCost">$0.00</div>
                    <div class="stat-label">Per user average</div>
                </div>
            </div>

            <!-- Tabs -->
            <div class="tabs">
                <button class="tab active" onclick="switchTab('users')">Users</button>
                <button class="tab" onclick="switchTab('queries')">Recent Queries</button>
                <button class="tab" onclick="switchTab('feedback')">Feedback</button>
            </div>

            <!-- Users Tab -->
            <div id="users-tab" class="tab-content active">
                <div class="panel">
                    <h2>User Statistics</h2>
                    <table id="usersTable">
                        <thead>
                            <tr>
                                <th>Email</th>
                                <th>Total Queries</th>
                                <th>Total Cost</th>
                                <th>Created</th>
                                <th>Last Login</th>
                            </tr>
                        </thead>
                        <tbody id="usersBody">
                        </tbody>
                    </table>
                </div>
            </div>

            <!-- Queries Tab -->
            <div id="queries-tab" class="tab-content">
                <div class="panel">
                    <h2>Recent Queries (Last 50)</h2>
                    <table id="queriesTable">
                        <thead>
                            <tr>
                                <th>Time</th>
                                <th>User</th>
                                <th>Query</th>
                                <th>Cost</th>
                            </tr>
                        </thead>
                        <tbody id="queriesBody">
                        </tbody>
                    </table>
                </div>
            </div>

            <!-- Feedback Tab -->
            <div id="feedback-tab" class="tab-content">
                <div class="panel">
                    <h2>User Feedback (Last 50)</h2>
                    <table id="feedbackTable">
                        <thead>
                            <tr>
                                <th>Time</th>
                                <th>User</th>
                                <th>Rating</th>
                                <th>Comment</th>
                            </tr>
                        </thead>
                        <tbody id="feedbackBody">
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>

    <script src="/auth.js"></script>
    <script>
        let adminData = null;

        // Check authentication and admin status
        async function checkAuth() {
            if (!isAuthenticated()) {
                window.location.href = '/login.html';
                return false;
            }

            const user = await fetchUserInfo();
            if (!user) {
                window.location.href = '/login.html';
                return false;
            }

            if (!user.is_admin) {
                alert('Access denied. Admin privileges required.');
                window.location.href = '/';
                return false;
            }

            document.getElementById('userEmail').textContent = user.email;
            return true;
        }

        // Load admin statistics
        async function loadAdminStats() {
            try {
                const response = await authenticatedFetch('/admin/stats');

                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }

                adminData = await response.json();
                displayAdminData();

                document.getElementById('loading').style.display = 'none';
                document.getElementById('dashboard').style.display = 'block';
            } catch (error) {
                console.error('Error loading admin stats:', error);
                document.getElementById('loading').style.display = 'none';
                const errorDiv = document.getElementById('error');
                errorDiv.textContent = `Failed to load admin data: ${error.message}`;
                errorDiv.style.display = 'block';
            }
        }

        // Display admin data
        function displayAdminData() {
            // Overview stats
            document.getElementById('totalUsers').textContent = adminData.total_users;
            document.getElementById('totalQueries').textContent = adminData.total_queries;
            document.getElementById('totalCost').textContent = `$${adminData.total_cost_usd.toFixed(4)}`;

            const avgCost = adminData.total_users > 0 ? adminData.total_cost_usd / adminData.total_users : 0;
            document.getElementById('avgCost').textContent = `$${avgCost.toFixed(4)}`;

            // Users table
            displayUsers();
            displayQueries();
            displayFeedback();
        }

        // Display users
        function displayUsers() {
            const tbody = document.getElementById('usersBody');
            tbody.innerHTML = '';

            if (adminData.user_stats.length === 0) {
                tbody.innerHTML = '<tr><td colspan="5" class="empty-state">No users found</td></tr>';
                return;
            }

            adminData.user_stats.forEach(user => {
                const row = document.createElement('tr');

                const costClass = user.total_cost_usd > 1 ? 'cost-high' :
                                 user.total_cost_usd > 0.1 ? 'cost-medium' : 'cost-low';

                row.innerHTML = `
                    <td>${user.email}</td>
                    <td>${user.total_queries}</td>
                    <td class="${costClass}">$${user.total_cost_usd.toFixed(4)}</td>
                    <td class="timestamp">${formatTimestamp(user.created_at)}</td>
                    <td class="timestamp">${formatTimestamp(user.last_login)}</td>
                `;
                tbody.appendChild(row);
            });
        }

        // Display queries
        function displayQueries() {
            const tbody = document.getElementById('queriesBody');
            tbody.innerHTML = '';

            if (adminData.recent_queries.length === 0) {
                tbody.innerHTML = '<tr><td colspan="4" class="empty-state">No queries found</td></tr>';
                return;
            }

            adminData.recent_queries.forEach(query => {
                const row = document.createElement('tr');

                const costClass = query.cost_usd > 0.1 ? 'cost-high' :
                                 query.cost_usd > 0.01 ? 'cost-medium' : 'cost-low';

                row.innerHTML = `
                    <td class="timestamp">${formatTimestamp(query.created_at)}</td>
                    <td>${query.user_email || 'Unknown'}</td>
                    <td>${truncate(query.query, 80)}</td>
                    <td class="${costClass}">$${(query.cost_usd || 0).toFixed(4)}</td>
                `;
                tbody.appendChild(row);
            });
        }

        // Display feedback
        function displayFeedback() {
            const tbody = document.getElementById('feedbackBody');
            tbody.innerHTML = '';

            if (adminData.recent_feedback.length === 0) {
                tbody.innerHTML = '<tr><td colspan="4" class="empty-state">No feedback found</td></tr>';
                return;
            }

            adminData.recent_feedback.forEach(feedback => {
                const row = document.createElement('tr');

                const rating = feedback.rating ? '⭐'.repeat(feedback.rating) : 'N/A';

                row.innerHTML = `
                    <td class="timestamp">${formatTimestamp(feedback.created_at)}</td>
                    <td>${feedback.user_email || 'Anonymous'}</td>
                    <td>${rating}</td>
                    <td>${feedback.comment || '—'}</td>
                `;
                tbody.appendChild(row);
            });
        }

        // Tab switching
        function switchTab(tabName) {
            // Update tab buttons
            document.querySelectorAll('.tab').forEach(tab => {
                tab.classList.remove('active');
            });
            event.target.classList.add('active');

            // Update tab content
            document.querySelectorAll('.tab-content').forEach(content => {
                content.classList.remove('active');
            });
            document.getElementById(`${tabName}-tab`).classList.add('active');
        }

        // Utility functions
        function formatTimestamp(timestamp) {
            if (!timestamp) return 'Never';
            const date = new Date(timestamp * 1000);
            return date.toLocaleString();
        }

        function truncate(str, length) {
            if (!str) return '';
            return str.length > length ? str.substring(0, length) + '...' : str;
        }

        async function handleLogout() {
            await logout();
        }

        // Initialize
        window.addEventListener('load', async () => {
            const isAdmin = await checkAuth();
            if (isAdmin) {
                await loadAdminStats();
            }
        });
    </script>
</body>
</html>