- Add devices management template - Add conflicts resolution template - Add queue management template - Update admin templates with navigation links
49 lines
22 KiB
Go
49 lines
22 KiB
Go
// Code generated by templ - DO NOT EDIT.
|
||
|
||
// templ: version: v0.3.977
|
||
package templates
|
||
|
||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||
|
||
import "github.com/a-h/templ"
|
||
import templruntime "github.com/a-h/templ/runtime"
|
||
|
||
func Devices(user User) templ.Component {
|
||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||
return templ_7745c5c3_CtxErr
|
||
}
|
||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||
if !templ_7745c5c3_IsBuffer {
|
||
defer func() {
|
||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||
if templ_7745c5c3_Err == nil {
|
||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||
}
|
||
}()
|
||
}
|
||
ctx = templ.InitializeContext(ctx)
|
||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||
if templ_7745c5c3_Var1 == nil {
|
||
templ_7745c5c3_Var1 = templ.NopComponent
|
||
}
|
||
ctx = templ.ClearChildren(ctx)
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Device Management - Bookmann</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/toast.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"><script src=\"/static/theme.js\"></script></head><body class=\"theme-{ user.Theme }\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = Header(user, "/devices").Render(ctx, templ_7745c5c3_Buffer)
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8\"><!-- Header Section --><div class=\"mb-8\"><div class=\"flex justify-between items-center\"><div><h1 class=\"text-3xl font-bold\" style=\"color: var(--text-primary)\">Device Management</h1><p style=\"color: var(--text-secondary)\">Manage your reading devices and sync settings</p></div><button onclick=\"showAddDeviceModal()\" class=\"btn-primary px-4 py-2 rounded-lg\">➕ Add New Device</button></div></div><!-- Devices Grid --><div id=\"devices-container\" class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\"><!-- Loading state --><div id=\"loading\" class=\"text-center py-16 col-span-full\" style=\"color: var(--text-secondary)\"><div class=\"loading-spinner mx-auto mb-4\"></div><p>Loading devices...</p></div><!-- Empty state --><div id=\"empty-state\" class=\"hidden text-center py-16 col-span-full\" style=\"color: var(--text-secondary)\"><div class=\"text-6xl mb-4\">📱</div><h3 class=\"text-xl font-semibold mb-2\" style=\"color: var(--text-primary)\">No Devices Yet</h3><p class=\"mb-4\">Add your reading devices to enable cross-device sync</p><button onclick=\"showAddDeviceModal()\" class=\"btn-primary px-4 py-2 rounded-lg\">Add Your First Device</button></div><!-- Devices will be loaded here --><div id=\"devices-grid\" class=\"hidden grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\"></div></div><!-- Pending Registrations Section --><div id=\"pending-section\" class=\"mt-12 hidden\"><h2 class=\"text-2xl font-bold mb-4\" style=\"color: var(--text-primary)\">Pending Device Registrations</h2><div id=\"pending-devices\" class=\"space-y-4\"></div></div><!-- Sync Queue Section --><div id=\"sync-queue-section\" class=\"mt-12 hidden\"><div class=\"flex justify-between items-center mb-4\"><h2 class=\"text-2xl font-bold\" style=\"color: var(--text-primary)\">Sync Queue</h2><button onclick=\"clearSyncQueue()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Clear Queue</button></div><div id=\"sync-queue\" class=\"space-y-3\"></div></div></div><!-- Add Device Modal --><div id=\"add-device-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-md mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Add New Device</h2><button onclick=\"hideAddDeviceModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><form id=\"add-device-form\" onsubmit=\"handleAddDevice(event)\"><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Device Name</label> <input type=\"text\" id=\"device-name\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"My Kindle Paperwhite\"></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Device Type</label> <select id=\"device-type\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"><option value=\"\">Select device type...</option> <option value=\"koreader\">KOReader (Kindle, Kobo, PocketBook)</option> <option value=\"kobo\">Kobo E-Reader</option> <option value=\"web\">Web Browser</option> <option value=\"mobile\">Mobile App</option></select></div><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Device Identifier</label> <input type=\"text\" id=\"device-identifier\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"Hardware ID or Serial Number\"><p class=\"text-xs mt-1\" style=\"color: var(--text-secondary)\">Enter your device's unique identifier</p></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideAddDeviceModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Cancel</button> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded-lg\">Register Device</button></div></form></div></div><!-- Device Settings Modal --><div id=\"device-settings-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-md mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Device Settings</h2><button onclick=\"hideDeviceSettingsModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><form id=\"device-settings-form\" onsubmit=\"handleSaveDeviceSettings(event)\"><input type=\"hidden\" id=\"settings-device-id\"><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Device Name</label> <input type=\"text\" id=\"settings-device-name\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div><div class=\"mb-4\"><label class=\"flex items-center space-x-3 cursor-pointer\"><input type=\"checkbox\" id=\"settings-sync-enabled\" checked class=\"w-5 h-5 rounded\"> <span style=\"color: var(--text-primary)\">Enable Sync</span></label><p class=\"text-xs mt-1 ml-8\" style=\"color: var(--text-secondary)\">Allow this device to sync reading progress</p></div><div class=\"mb-4\"><label class=\"flex items-center space-x-3 cursor-pointer\"><input type=\"checkbox\" id=\"settings-auto-sync\" checked class=\"w-5 h-5 rounded\"> <span style=\"color: var(--text-primary)\">Auto Sync</span></label><p class=\"text-xs mt-1 ml-8\" style=\"color: var(--text-secondary)\">Automatically sync changes</p></div><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Sync Frequency</label> <select id=\"settings-sync-frequency\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"><option value=\"1\">Every 1 minute</option> <option value=\"5\" selected>Every 5 minutes</option> <option value=\"15\">Every 15 minutes</option> <option value=\"30\">Every 30 minutes</option> <option value=\"60\">Every hour</option></select></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideDeviceSettingsModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Cancel</button> <button type=\"button\" onclick=\"handleRevokeDevice()\" class=\"btn-danger px-4 py-2 rounded-lg\">Revoke Device</button> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded-lg\">Save Settings</button></div></form></div></div><script>\n let devices = [];\n let pendingRegistrations = [];\n\n function loadDevices() {\n fetch('/api/devices', {\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(data => {\n devices = data.devices || [];\n renderDevices();\n loadPendingRegistrations();\n })\n .catch(error => {\n showToast('Failed to load devices', 'error');\n document.getElementById('loading').classList.add('hidden');\n });\n }\n\n function loadPendingRegistrations() {\n fetch('/api/devices/pending', {\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(data => {\n pendingRegistrations = data.pending_registrations || [];\n renderPendingRegistrations();\n })\n .catch(() => {\n // Ignore error if endpoint doesn't exist\n });\n }\n\n function renderDevices() {\n const loading = document.getElementById('loading');\n const emptyState = document.getElementById('empty-state');\n const grid = document.getElementById('devices-grid');\n\n loading.classList.add('hidden');\n\n if (devices.length === 0) {\n emptyState.classList.remove('hidden');\n grid.classList.add('hidden');\n return;\n }\n\n emptyState.classList.add('hidden');\n grid.classList.remove('hidden');\n\n const deviceIcons = {\n 'koreader': '📖',\n 'kobo': '📚',\n 'web': '🌐',\n 'mobile': '📱'\n };\n\n grid.innerHTML = devices.map(device => `\n <div class=\"card p-6 rounded-lg border\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\">\n <div class=\"flex items-start justify-between mb-4\">\n <div class=\"text-4xl\">${deviceIcons[device.device_type] || '📱'}</div>\n <div class=\"flex space-x-2\">\n <button onclick=\"showDeviceSettings('${device.id}')\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-secondary); background-color: var(--bg-primary);\">\n ⚙️\n </button>\n </div>\n </div>\n <h3 class=\"text-lg font-semibold mb-1\" style=\"color: var(--text-primary)\">${device.device_name}</h3>\n <p class=\"text-sm mb-4\" style=\"color: var(--text-secondary)\">${device.device_type.toUpperCase()}</p>\n <div class=\"space-y-2 text-sm\">\n <div class=\"flex justify-between\">\n <span style=\"color: var(--text-secondary)\">Sync Status</span>\n <span style=\"color: ${device.sync_enabled ? 'var(--accent)' : 'var(--text-secondary)'}\">\n ${device.sync_enabled ? '✓ Enabled' : '✗ Disabled'}\n </span>\n </div>\n <div class=\"flex justify-between\">\n <span style=\"color: var(--text-secondary)\">Last Sync</span>\n <span style=\"color: var(--text-primary)\">\n ${device.last_sync ? new Date(device.last_sync).toLocaleString() : 'Never'}\n </span>\n </div>\n <div class=\"flex justify-between\">\n <span style=\"color: var(--text-secondary)\">Last Seen</span>\n <span style=\"color: var(--text-primary)\">\n ${device.last_seen ? new Date(device.last_seen).toLocaleString() : 'Never'}\n </span>\n </div>\n </div>\n </div>\n `).join('');\n }\n\n function renderPendingRegistrations() {\n const section = document.getElementById('pending-section');\n const container = document.getElementById('pending-devices');\n\n if (pendingRegistrations.length === 0) {\n section.classList.add('hidden');\n return;\n }\n\n section.classList.remove('hidden');\n container.innerHTML = pendingRegistrations.map(reg => `\n <div class=\"card p-4 rounded-lg border flex items-center justify-between\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\">\n <div>\n <h3 class=\"font-semibold\" style=\"color: var(--text-primary)\">${reg.device_name || 'Unknown Device'}</h3>\n <p class=\"text-sm\" style=\"color: var(--text-secondary)\">\n ${reg.device_type.toUpperCase()} - Expires in ${Math.floor((new Date(reg.expires_at) - new Date()) / 60000)} minutes\n </p>\n </div>\n <div class=\"flex space-x-2\">\n <button onclick=\"approveDevice('${reg.registration_id}')\" class=\"btn-primary px-4 py-2 rounded-lg text-sm\">\n Approve\n </button>\n <button onclick=\"rejectDevice('${reg.registration_id}')\" class=\"btn-danger px-4 py-2 rounded-lg text-sm\">\n Reject\n </button>\n </div>\n </div>\n `).join('');\n }\n\n function showAddDeviceModal() {\n document.getElementById('add-device-modal').classList.remove('hidden');\n }\n\n function hideAddDeviceModal() {\n document.getElementById('add-device-modal').classList.add('hidden');\n document.getElementById('add-device-form').reset();\n }\n\n function handleAddDevice(event) {\n event.preventDefault();\n\n const data = {\n device_name: document.getElementById('device-name').value,\n device_type: document.getElementById('device-type').value,\n device_identifier: document.getElementById('device-identifier').value\n };\n\n fetch('/api/devices/register', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + localStorage.getItem('token')\n },\n body: JSON.stringify(data)\n })\n .then(response => response.json())\n .then(result => {\n showToast('Device registered! Check your device for sync instructions.', 'success');\n hideAddDeviceModal();\n loadDevices();\n })\n .catch(error => {\n showToast('Failed to register device', 'error');\n });\n }\n\n function showDeviceSettings(deviceId) {\n const device = devices.find(d => d.id === deviceId);\n if (!device) return;\n\n document.getElementById('settings-device-id').value = deviceId;\n document.getElementById('settings-device-name').value = device.device_name;\n document.getElementById('settings-sync-enabled').checked = device.sync_enabled;\n document.getElementById('settings-auto-sync').checked = device.auto_sync;\n document.getElementById('settings-sync-frequency').value = device.sync_frequency_minutes;\n\n document.getElementById('device-settings-modal').classList.remove('hidden');\n }\n\n function hideDeviceSettingsModal() {\n document.getElementById('device-settings-modal').classList.add('hidden');\n }\n\n function handleSaveDeviceSettings(event) {\n event.preventDefault();\n\n const deviceId = document.getElementById('settings-device-id').value;\n const data = {\n device_name: document.getElementById('settings-device-name').value,\n sync_enabled: document.getElementById('settings-sync-enabled').checked,\n auto_sync: document.getElementById('settings-auto-sync').checked,\n sync_frequency_minutes: parseInt(document.getElementById('settings-sync-frequency').value)\n };\n\n fetch(`/api/devices/${deviceId}`, {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + localStorage.getItem('token')\n },\n body: JSON.stringify(data)\n })\n .then(response => response.json())\n .then(result => {\n showToast('Device settings saved', 'success');\n hideDeviceSettingsModal();\n loadDevices();\n })\n .catch(error => {\n showToast('Failed to save settings', 'error');\n });\n }\n\n function handleRevokeDevice() {\n const deviceId = document.getElementById('settings-device-id').value;\n if (!confirm('Are you sure you want to revoke this device? It will no longer be able to sync.')) return;\n\n fetch(`/api/devices/${deviceId}`, {\n method: 'DELETE',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => {\n if (response.ok) {\n showToast('Device revoked successfully', 'success');\n hideDeviceSettingsModal();\n loadDevices();\n } else {\n showToast('Failed to revoke device', 'error');\n }\n })\n .catch(error => {\n showToast('Failed to revoke device', 'error');\n });\n }\n\n function approveDevice(registrationId) {\n fetch(`/api/devices/approve/${registrationId}`, {\n method: 'GET',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(result => {\n showToast('Device approved successfully', 'success');\n loadPendingRegistrations();\n })\n .catch(error => {\n showToast('Failed to approve device', 'error');\n });\n }\n\n function rejectDevice(registrationId) {\n fetch(`/api/devices/reject/${registrationId}`, {\n method: 'POST',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(result => {\n showToast('Device registration rejected', 'info');\n loadPendingRegistrations();\n })\n .catch(error => {\n showToast('Failed to reject device', 'error');\n });\n }\n\n function clearSyncQueue() {\n if (!confirm('Are you sure you want to clear all sync queue items?')) return;\n\n fetch('/api/queue/clear', {\n method: 'DELETE',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => {\n if (response.ok) {\n showToast('Sync queue cleared', 'success');\n } else {\n showToast('Failed to clear queue', 'error');\n }\n });\n }\n\n function logout() {\n localStorage.removeItem('token');\n window.location.href = '/login';\n }\n\n // Load devices on page load\n loadDevices();\n </script></body></html>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
return nil
|
||
})
|
||
}
|
||
|
||
var _ = templruntime.GeneratedTemplate
|