| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Dashboard - Ollama</title> |
| <link rel="stylesheet" href="/static/style.css"> |
| </head> |
| <body> |
| <div class="container"> |
| <header> |
| <h1>Dashboard</h1> |
| <div> |
| <span id="ollama-badge" class="status status-pending">Checking...</span> |
| <a href="/chat" class="btn btn-primary">Chat</a> |
| <button class="btn btn-secondary" onclick="logout()">Logout</button> |
| </div> |
| </header> |
|
|
| <div class="dashboard-grid"> |
| <div class="card"> |
| <h2>Your Profile</h2> |
| <div id="profile"></div> |
| </div> |
|
|
| <div class="card"> |
| <h2>API Keys</h2> |
| <form onsubmit="createApiKey(event)"> |
| <input type="text" id="key-name" placeholder="Key name" required> |
| <button type="submit" class="btn btn-primary">Generate</button> |
| </form> |
| <ul id="api-keys-list" class="key-list"></ul> |
| </div> |
|
|
| <div class="card"> |
| <h2>Model Status</h2> |
| <p><strong>Model:</strong> llama3.1:8b</p> |
| <p><strong>Status:</strong> <span id="model-status-text">Checking...</span></p> |
| <div id="installed-section"> |
| <p><strong>Installed Models:</strong></p> |
| <ul id="installed-models" class="model-list"><li>Loading...</li></ul> |
| </div> |
| <a href="/chat" class="btn btn-primary" style="width:100%;margin-top:12px;text-align:center">Open Chat</a> |
| </div> |
|
|
| <div class="card full-width"> |
| <h2>Quick Start</h2> |
| <p>Use the chat interface to interact with <strong>llama3.1:8b</strong>.</p> |
| <p>Generate API keys above to access the model programmatically via <code>POST /api/chat</code>.</p> |
| <p style="margin-top:8px;font-size:0.9rem;color:#666;"> |
| Model status: <span id="status-sub">checking...</span> |
| </p> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| const token = localStorage.getItem('token'); |
| if (!token) window.location.href = '/login'; |
| |
| async function api(url, opts = {}) { |
| opts.headers = {...opts.headers, 'Authorization': `Bearer ${token}`}; |
| if (opts.body && typeof opts.body === 'object') { |
| opts.headers['Content-Type'] = 'application/json'; |
| opts.body = JSON.stringify(opts.body); |
| } |
| const res = await fetch(url, opts); |
| if (res.status === 401) { localStorage.removeItem('token'); window.location.href = '/login'; } |
| return res; |
| } |
| |
| function logout() { localStorage.removeItem('token'); window.location.href = '/login'; } |
| |
| |
| api('/api/me').then(r => r.json()).then(data => { |
| document.getElementById('profile').innerHTML = ` |
| <p><strong>Username:</strong> ${data.username}</p> |
| <p><strong>Email:</strong> ${data.email}</p> |
| <p><strong>Default API Key:</strong> <code>${data.api_key}</code></p> |
| `; |
| }); |
| |
| |
| async function loadStatus() { |
| const res = await fetch('/api/status', {headers: {'Authorization': `Bearer ${token}`}}); |
| const data = await res.json(); |
| const badge = document.getElementById('ollama-badge'); |
| const statusText = document.getElementById('model-status-text'); |
| const subText = document.getElementById('status-sub'); |
| |
| if (data.model) { |
| badge.className = 'status status-completed'; |
| badge.textContent = 'Ready'; |
| statusText.textContent = 'Ready'; |
| subText.textContent = 'llama3.1:8b is ready'; |
| } else if (data.ollama) { |
| badge.className = 'status status-downloading'; |
| badge.textContent = 'Downloading...'; |
| statusText.textContent = 'Downloading model...'; |
| subText.textContent = data.message || 'Model is being downloaded'; |
| setTimeout(loadStatus, 10000); |
| } else { |
| badge.className = 'status status-error'; |
| badge.textContent = 'Unavailable'; |
| statusText.textContent = 'Unavailable'; |
| subText.textContent = data.message || 'Ollama not reachable'; |
| } |
| |
| |
| const ul = document.getElementById('installed-models'); |
| if (data.installed && data.installed.length > 0) { |
| ul.innerHTML = data.installed.map(m => `<li>${m}</li>`).join(''); |
| } else { |
| ul.innerHTML = '<li>None yet</li>'; |
| } |
| } |
| loadStatus(); |
| |
| |
| async function loadApiKeys() { |
| const res = await api('/api/api-keys'); |
| const data = await res.json(); |
| const ul = document.getElementById('api-keys-list'); |
| ul.innerHTML = data.api_keys.map(k => ` |
| <li> |
| <strong>${k.key_name}</strong>: <code>${k.api_key}</code> |
| <button onclick="revokeKey(${k.id})" class="btn-small btn-danger">Revoke</button> |
| </li> |
| `).join(''); |
| } |
| loadApiKeys(); |
| |
| async function createApiKey(e) { |
| e.preventDefault(); |
| const name = document.getElementById('key-name').value; |
| await api('/api/api-keys', {method: 'POST', body: {key_name: name}}); |
| document.getElementById('key-name').value = ''; |
| loadApiKeys(); |
| } |
| |
| async function revokeKey(id) { |
| if (confirm('Revoke this API key?')) { |
| await api(`/api/api-keys/${id}`, {method: 'DELETE'}); |
| loadApiKeys(); |
| } |
| } |
| </script> |
| </body> |
| </html> |
|
|