Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>vBot Admin Interface</title> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary-color: #2c3e50; | |
| --secondary-color: #3498db; | |
| --accent-color: #e74c3c; | |
| --background-color: #f8f9fa; | |
| --card-background: #ffffff; | |
| } | |
| body { | |
| background-color: var(--background-color); | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin-top: 2rem; | |
| padding: 0 1rem; | |
| } | |
| .page-header { | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| color: white; | |
| padding: 2rem 0; | |
| margin-bottom: 2rem; | |
| border-radius: 10px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .page-header h1 { | |
| margin: 0; | |
| font-size: 2.5rem; | |
| font-weight: 600; | |
| } | |
| .card { | |
| border: none; | |
| border-radius: 15px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| margin-bottom: 2rem; | |
| background: var(--card-background); | |
| transition: transform 0.3s ease; | |
| } | |
| .card:hover { | |
| transform: translateY(-5px); | |
| } | |
| .card-header { | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| color: white; | |
| border-radius: 15px 15px 0 0 ; | |
| padding: 1.5rem; | |
| } | |
| .card-header h5 { | |
| margin: 0; | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| } | |
| .card-body { | |
| padding: 2rem; | |
| } | |
| .form-control { | |
| border-radius: 8px; | |
| border: 1px solid #dee2e6; | |
| padding: 0.75rem; | |
| transition: all 0.3s ease; | |
| } | |
| .form-control:focus { | |
| border-color: var(--secondary-color); | |
| box-shadow: 0 0 0 0.2rem rgba(52, 152, 219, 0.25); | |
| } | |
| .btn { | |
| border-radius: 8px; | |
| padding: 0.75rem 1.5rem; | |
| font-weight: 600; | |
| transition: all 0.3s ease; | |
| } | |
| .btn-primary { | |
| background: var(--secondary-color); | |
| border: none; | |
| } | |
| .btn-primary:hover { | |
| background: #2980b9; | |
| transform: translateY(-2px); | |
| } | |
| .btn-success { | |
| background: #2ecc71; | |
| border: none; | |
| } | |
| .btn-success:hover { | |
| background: #27ae60; | |
| transform: translateY(-2px); | |
| } | |
| .table { | |
| border-radius: 10px; | |
| overflow: hidden; | |
| } | |
| .table thead th { | |
| background: var(--primary-color); | |
| color: white; | |
| border: none; | |
| padding: 1rem; | |
| } | |
| .table tbody td { | |
| padding: 1rem; | |
| vertical-align: middle; | |
| } | |
| .badge { | |
| padding: 0.5rem 1rem; | |
| border-radius: 20px; | |
| font-weight: 500; | |
| } | |
| .alert { | |
| border-radius: 10px; | |
| border: none; | |
| padding: 1rem; | |
| } | |
| .token-section { | |
| max-width: 500px; | |
| margin: 0 auto; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| .form-label { | |
| font-weight: 500; | |
| color: var(--primary-color); | |
| } | |
| .customer-list { | |
| margin-top: 2rem; | |
| } | |
| .api-key { | |
| background: #f8f9fa; | |
| padding: 0.5rem; | |
| border-radius: 5px; | |
| font-family: monospace; | |
| font-size: 0.9rem; | |
| } | |
| .status-badge { | |
| padding: 0.5rem 1rem; | |
| border-radius: 20px; | |
| font-weight: 500; | |
| } | |
| .status-active { | |
| background: #2ecc71; | |
| color: white; | |
| } | |
| .status-inactive { | |
| background: #e74c3c; | |
| color: white; | |
| } | |
| .loading { | |
| display: none; | |
| text-align: center; | |
| padding: 2rem; | |
| } | |
| .loading i { | |
| font-size: 2rem; | |
| color: var(--secondary-color); | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="page-header text-center"> | |
| <h1><i class="fas fa-robot me-2"></i>vBot Admin Interface</h1> | |
| </div> | |
| <!-- Login Section --> | |
| <div class="card token-section" id="loginSection"> | |
| <div class="card-header"> | |
| <h5><i class="fas fa-lock me-2"></i>Admin Login</h5> | |
| </div> | |
| <div class="card-body"> | |
| <form id="loginForm"> | |
| <div class="mb-3"> | |
| <label for="username" class="form-label">Username</label> | |
| <div class="input-group"> | |
| <span class="input-group-text"><i class="fas fa-user"></i></span> | |
| <input type="text" class="form-control" id="username" required> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="password" class="form-label">Password</label> | |
| <div class="input-group"> | |
| <span class="input-group-text"><i class="fas fa-key"></i></span> | |
| <input type="password" class="form-control" id="password" required> | |
| </div> | |
| </div> | |
| <button type="submit" class="btn btn-primary w-100"> | |
| <i class="fas fa-sign-in-alt me-2"></i>Login | |
| </button> | |
| </form> | |
| <div id="tokenStatus" class="mt-3"></div> | |
| </div> | |
| </div> | |
| <!-- Main Content (Hidden until login) --> | |
| <div id="mainContent" class="hidden"> | |
| <!-- Add Customer Section --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h5><i class="fas fa-user-plus me-2"></i>Add New Customer</h5> | |
| </div> | |
| <div class="card-body"> | |
| <form id="customerForm"> | |
| <div class="row"> | |
| <div class="col-md-6 mb-3"> | |
| <label for="name" class="form-label">Name</label> | |
| <div class="input-group"> | |
| <span class="input-group-text"><i class="fas fa-user"></i></span> | |
| <input type="text" class="form-control" id="name" required> | |
| </div> | |
| </div> | |
| <div class="col-md-6 mb-3"> | |
| <label for="company_name" class="form-label">Company Name</label> | |
| <div class="input-group"> | |
| <span class="input-group-text"><i class="fas fa-building"></i></span> | |
| <input type="text" class="form-control" id="company_name" required> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="email" class="form-label">Email</label> | |
| <div class="input-group"> | |
| <span class="input-group-text"><i class="fas fa-envelope"></i></span> | |
| <input type="email" class="form-control" id="email" required> | |
| </div> | |
| </div> | |
| <button type="submit" class="btn btn-success w-100"> | |
| <i class="fas fa-plus-circle me-2"></i>Add Customer | |
| </button> | |
| </form> | |
| <div id="customerStatus" class="mt-3"></div> | |
| </div> | |
| </div> | |
| <!-- Customer List Section --> | |
| <div class="card customer-list"> | |
| <div class="card-header"> | |
| <h5><i class="fas fa-users me-2"></i>Customer List</h5> | |
| </div> | |
| <div class="card-body"> | |
| <div class="loading"> | |
| <i class="fas fa-spinner"></i> | |
| </div> | |
| <div id="customerList" class="table-responsive"> | |
| <table class="table"> | |
| <thead> | |
| <tr> | |
| <th>Name</th> | |
| <th>Company</th> | |
| <th>Email</th> | |
| <th>API Key</th> | |
| <th>Status</th> | |
| </tr> | |
| </thead> | |
| <tbody id="customerTableBody"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let accessToken = null; | |
| // Login Form Handler | |
| document.getElementById('loginForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const username = document.getElementById('username').value; | |
| const password = document.getElementById('password').value; | |
| try { | |
| const response = await fetch('/api/v1/token', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| }, | |
| body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}` | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| accessToken = data.access_token; | |
| document.getElementById('tokenStatus').innerHTML = | |
| '<div class="alert alert-success"><i class="fas fa-check-circle me-2"></i>Login successful!</div>'; | |
| // Show main content and hide login section | |
| document.getElementById('loginSection').classList.add('hidden'); | |
| document.getElementById('mainContent').classList.remove('hidden'); | |
| loadCustomers(); | |
| } else { | |
| document.getElementById('tokenStatus').innerHTML = | |
| '<div class="alert alert-danger"><i class="fas fa-exclamation-circle me-2"></i>Login failed. Please check your credentials.</div>'; | |
| } | |
| } catch (error) { | |
| document.getElementById('tokenStatus').innerHTML = | |
| '<div class="alert alert-danger"><i class="fas fa-exclamation-circle me-2"></i>Error connecting to server.</div>'; | |
| } | |
| }); | |
| // Customer Form Handler | |
| document.getElementById('customerForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| if (!accessToken) { | |
| document.getElementById('customerStatus').innerHTML = | |
| '<div class="alert alert-danger"><i class="fas fa-exclamation-circle me-2"></i>Please login first.</div>'; | |
| return; | |
| } | |
| const customerData = { | |
| name: document.getElementById('name').value, | |
| company_name: document.getElementById('company_name').value, | |
| email: document.getElementById('email').value | |
| }; | |
| try { | |
| console.log('Sending customer data:', { ...customerData, db_password: '***' }); | |
| const response = await fetch('/api/v1/customers/', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${accessToken}`, | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify(customerData) | |
| }); | |
| const data = await response.json(); | |
| console.log('Server response:', data); | |
| if (response.ok) { | |
| document.getElementById('customerStatus').innerHTML = | |
| '<div class="alert alert-success"><i class="fas fa-check-circle me-2"></i>Customer added successfully!</div>'; | |
| document.getElementById('customerForm').reset(); | |
| loadCustomers(); | |
| } else { | |
| document.getElementById('customerStatus').innerHTML = | |
| `<div class="alert alert-danger"><i class="fas fa-exclamation-circle me-2"></i>Error: ${data.detail || 'Unknown error occurred'}</div>`; | |
| } | |
| } catch (error) { | |
| console.error('Error creating customer:', error); | |
| document.getElementById('customerStatus').innerHTML = | |
| `<div class="alert alert-danger"><i class="fas fa-exclamation-circle me-2"></i>Error connecting to server: ${error.message}</div>`; | |
| } | |
| }); | |
| // Load Customers | |
| async function loadCustomers() { | |
| if (!accessToken) return; | |
| const loading = document.querySelector('.loading'); | |
| loading.style.display = 'block'; | |
| try { | |
| const response = await fetch('/api/v1/customers/', { | |
| headers: { | |
| 'Authorization': `Bearer ${accessToken}` | |
| } | |
| }); | |
| if (response.ok) { | |
| const customers = await response.json(); | |
| const tbody = document.getElementById('customerTableBody'); | |
| tbody.innerHTML = ''; | |
| if (customers.length === 0) { | |
| tbody.innerHTML = ` | |
| <tr> | |
| <td colspan="5" class="text-center">No customers found</td> | |
| </tr> | |
| `; | |
| return; | |
| } | |
| customers.forEach(customer => { | |
| const tr = document.createElement('tr'); | |
| tr.innerHTML = ` | |
| <td>${customer.name}</td> | |
| <td>${customer.company_name}</td> | |
| <td>${customer.email}</td> | |
| <td><code class="api-key">${customer.api_key}</code></td> | |
| <td> | |
| <span class="status-badge ${customer.is_active ? 'status-active' : 'status-inactive'}"> | |
| ${customer.is_active ? '<i class="fas fa-check-circle me-1"></i>Active' : '<i class="fas fa-times-circle me-1"></i>Inactive'} | |
| </span> | |
| </td> | |
| `; | |
| tbody.appendChild(tr); | |
| }); | |
| } else { | |
| const error = await response.json(); | |
| console.error('Error loading customers:', error); | |
| document.getElementById('customerList').innerHTML = ` | |
| <div class="alert alert-danger"> | |
| <i class="fas fa-exclamation-circle me-2"></i> | |
| Error loading customers: ${error.detail || 'Unknown error'} | |
| </div> | |
| `; | |
| } | |
| } catch (error) { | |
| console.error('Error loading customers:', error); | |
| document.getElementById('customerList').innerHTML = ` | |
| <div class="alert alert-danger"> | |
| <i class="fas fa-exclamation-circle me-2"></i> | |
| Error connecting to server | |
| </div> | |
| `; | |
| } finally { | |
| loading.style.display = 'none'; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |