Spaces:
Running
Running
| // API Keys management | |
| let apiKeys = []; | |
| // Initialize API Keys page | |
| document.addEventListener('DOMContentLoaded', function() { | |
| loadSampleApiKeys(); | |
| renderApiKeysTable(); | |
| }); | |
| // Load sample API keys | |
| function loadSampleApiKeys() { | |
| apiKeys = [ | |
| { | |
| id: 1, | |
| name: 'Production Key', | |
| key: 'sk_live_51HvK2rKRtimDvy4kF8h4gF2j3k5l6m7n8o9p0q1r2s3t4u5v6w7x8y9z0', | |
| createdAt: new Date('2024-01-15'), | |
| lastUsed: new Date('2024-01-20'), | |
| status: 'active' | |
| }, | |
| { | |
| id: 2, | |
| name: 'Development Key', | |
| key: 'sk_test_1HvK2rKRtimDvy4kF8h4gF2j3k5l6m7n8o9p0q1r2s3t4u5v6w7x8y9z0', | |
| createdAt: new Date('2024-01-10'), | |
| lastUsed: new Date('2024-01-18'), | |
| status: 'active' | |
| }, | |
| { | |
| id: 3, | |
| name: 'CI/CD Pipeline', | |
| key: 'sk_live_2HvK2rKRtimDvy4kF8h4gF2j3k5l6m7n8o9p0q1r2s3t4u5v6w7x8y9z0', | |
| createdAt: new Date('2024-01-05'), | |
| lastUsed: new Date('2024-01-12'), | |
| status: 'revoked' | |
| } | |
| ]; | |
| } | |
| // Render API keys table | |
| function renderApiKeysTable() { | |
| const tableBody = document.getElementById('api-keys-table'); | |
| tableBody.innerHTML = apiKeys.map(key => { | |
| const maskedKey = maskApiKey(key.key); | |
| const timeAgo = getTimeAgo(key.lastUsed); | |
| const createdAgo = getTimeAgo(key.createdAt); | |
| const statusClass = key.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'; | |
| return ` | |
| <tr class="hover:bg-gray-50"> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="text-sm font-medium text-gray-900">${key.name}</div> | |
| <div class="text-sm text-gray-500">${key.status === 'active' ? 'Active' : 'Revoked'}</div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex items-center"> | |
| <code class="text-sm font-mono text-gray-900 bg-gray-100 px-2 py-1 rounded">${maskedKey}</code> | |
| <button onclick="copyApiKey('${key.key}')" class="ml-2 text-gray-400 hover:text-gray-600"> | |
| <i data-feather="copy" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
| ${createdAgo} | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
| ${timeAgo} | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> | |
| <div class="flex space-x-2"> | |
| <button onclick="showKeyDetails('${key.id}')" class="text-primary-600 hover:text-primary-700">View</button> | |
| ${key.status === 'active' ? ` | |
| <button onclick="revokeKey('${key.id}')" class="text-red-600 hover:text-red-700">Revoke</button> | |
| ` : ` | |
| <button onclick="deleteKey('${key.id}')" class="text-red-600 hover:text-red-700">Delete</button> | |
| `} | |
| </div> | |
| </td> | |
| </tr> | |
| `; | |
| }).join(''); | |
| feather.replace(); | |
| } | |
| // Mask API key for display | |
| function maskApiKey(key) { | |
| const prefix = key.substring(0, 12); | |
| const suffix = key.substring(key.length - 8); | |
| return `${prefix}...${suffix}`; | |
| } | |
| // Copy API key to clipboard | |
| function copyApiKey(key) { | |
| navigator.clipboard.writeText(key).then(() => { | |
| Wash.showNotification('API key copied to clipboard', 'success'); | |
| }); | |
| } | |
| // Create new API key | |
| function createNewKey() { | |
| const name = prompt('Enter a name for the new API key:'); | |
| if (!name) return; | |
| const newKey = { | |
| id: Date.now(), | |
| name: name, | |
| key: generateApiKey(), | |
| createdAt: new Date(), | |
| lastUsed: null, | |
| status: 'active' | |
| }; | |
| apiKeys.unshift(newKey); | |
| renderApiKeysTable(); | |
| Wash.showNotification('New API key created successfully', 'success'); | |
| } | |
| // Generate a random API key | |
| function generateApiKey() { | |
| const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | |
| let result = 'sk_live_'; | |
| for (let i = 0; i < 48; i++) { | |
| result += chars.charAt(Math.floor(Math.random() * chars.length)); | |
| } | |
| return result; | |
| } | |
| // Revoke API key | |
| function revokeKey(keyId) { | |
| if (confirm('Are you sure you want to revoke this API key? This action cannot be undone.')) { | |
| const key = apiKeys.find(k => k.id == keyId); | |
| if (key) { | |
| key.status = 'revoked'; | |
| renderApiKeysTable(); | |
| Wash.showNotification('API key revoked successfully', 'success'); | |
| } | |
| } | |
| } | |
| // Delete API key | |
| function deleteKey(keyId) { | |
| if (confirm('Are you sure you want to delete this API key? This action cannot be undone.')) { | |
| apiKeys = apiKeys.filter(k => k.id != keyId); | |
| renderApiKeysTable(); | |
| Wash.showNotification('API key deleted successfully', 'success'); | |
| } | |
| } | |
| // Show key details | |
| function showKeyDetails(keyId) { | |
| const key = apiKeys.find(k => k.id == keyId); | |
| if (!key) return; | |
| const details = ` | |
| Key Details: | |
| Name: ${key.name} | |
| Key: ${key.key} | |
| Status: ${key.status} | |
| Created: ${key.createdAt.toLocaleDateString()} | |
| Last Used: ${key.lastUsed ? key.lastUsed.toLocaleDateString() : 'Never'} | |
| Usage Example: | |
| curl -X POST https://api.wash.dev/v1/clean \\ | |
| -H "Authorization: Bearer ${key.key}" \\ | |
| -F "file=@document.pdf" | |
| `; | |
| alert(details); | |
| } | |
| // Get time ago string | |
| function getTimeAgo(date) { | |
| if (!date) return 'Never'; | |
| const now = new Date(); | |
| const diff = now - date; | |
| const days = Math.floor(diff / (1000 * 60 * 60 * 24)); | |
| const hours = Math.floor(diff / (1000 * 60 * 60)); | |
| const minutes = Math.floor(diff / (1000 * 60)); | |
| if (days > 0) { | |
| return `${days} day${days > 1 ? 's' : ''} ago`; | |
| } else if (hours > 0) { | |
| return `${hours} hour${hours > 1 ? 's' : ''} ago`; | |
| } else if (minutes > 0) { | |
| return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; | |
| } else { | |
| return 'Just now'; | |
| } | |
| } |