Spaces:
Running
Running
stouchinglhh
Claude
sync processor from GitLab c697b6e: 版本 1.19.4 / 前端 1.12.4 —— 服务端 i18n(首屏不再闪英文)、推理模块并入模型页、前端抽出 shared/、缓存键改用前端号
bb59211 Download processor/web/static/js/users.js from Stouching-Robotics/DataFlowing: direct link, hf CLI and curl.
- Browser
- Download file 9.95 kB
-
https://huggingface.co/spaces/Stouching-Robotics/DataFlowing/resolve/main/processor/web/static/js/users.js
- Command line
-
hf download hf://spaces/Stouching-Robotics/DataFlowing/processor/web/static/js/users.js
-
curl -L -o users.js https://huggingface.co/spaces/Stouching-Robotics/DataFlowing/resolve/main/processor/web/static/js/users.js
9.95 kB
| /* Administrator account management. */ | |
| let managedUsers = []; | |
| // 显示时翻译;查不到就回落原始值(后端加了新角色也不会显示成空白) | |
| const ROLE_LABEL_KEYS = { admin: 'role_admin', engineer: 'role_engineer', reviewer: 'role_reviewer' }; | |
| const STATUS_LABEL_KEYS = { active: 'status_active', disabled: 'status_disabled', expired: 'status_expired' }; | |
| const ROLE_CLASSES = { | |
| admin: 'text-blue-400 bg-blue-900/30 border-blue-800', | |
| engineer: 'text-purple-400 bg-purple-900/30 border-purple-800', | |
| reviewer: 'text-green-400 bg-green-900/30 border-green-800', | |
| }; | |
| function escUser(value) { | |
| return String(value ?? '').replace(/[&<>"']/g, c => ({ | |
| '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' | |
| }[c])); | |
| } | |
| function showUserAlert(message, error = false) { | |
| const el = document.getElementById('user-alert'); | |
| if (!el) return; | |
| el.textContent = message; | |
| el.className = `mb-4 rounded-lg border px-4 py-3 text-sm ${error | |
| ? 'text-red-300 bg-red-900/20 border-red-800' | |
| : 'text-green-300 bg-green-900/20 border-green-800'}`; | |
| window.clearTimeout(showUserAlert.timer); | |
| showUserAlert.timer = window.setTimeout(() => el.classList.add('hidden'), 4000); | |
| } | |
| async function apiUsers(url, options = {}) { | |
| const response = await fetch(url, { headers: { 'Content-Type': 'application/json' }, ...options }); | |
| let data = {}; | |
| try { data = await response.json(); } catch (_) {} | |
| if (!response.ok) throw new Error(data.detail || `Request failed (${response.status})`); | |
| return data; | |
| } | |
| async function loadUsers() { | |
| const role = document.getElementById('user-role-filter')?.value || ''; | |
| const status = document.getElementById('user-status-filter')?.value || ''; | |
| const params = new URLSearchParams(); | |
| if (role) params.set('role', role); | |
| if (status) params.set('status', status); | |
| try { | |
| const data = await apiUsers(`/api/v1/users?${params}`); | |
| managedUsers = data.users || []; | |
| updateUserCounts(); | |
| renderUsers(); | |
| } catch (error) { | |
| showUserAlert(error.message, true); | |
| document.getElementById('users-body').innerHTML = `<tr><td colspan="6" class="px-4 py-10 text-center text-red-400">${escUser(error.message)}</td></tr>`; | |
| } | |
| } | |
| function updateUserCounts() { | |
| const active = managedUsers.filter(user => user.status === 'active').length; | |
| document.getElementById('count-total').textContent = managedUsers.length; | |
| document.getElementById('count-active').textContent = active; | |
| document.getElementById('count-inactive').textContent = managedUsers.length - active; | |
| } | |
| function formatDate(value) { | |
| if (!value) return 'Never'; | |
| const date = new Date(value); | |
| return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString(); | |
| } | |
| function renderUsers() { | |
| const query = (document.getElementById('user-search')?.value || '').trim().toLowerCase(); | |
| const users = managedUsers.filter(user => !query | |
| || user.username.toLowerCase().includes(query) | |
| || (user.email || '').toLowerCase().includes(query)); | |
| document.getElementById('user-list-count').textContent = t('users_count').replace('%s', users.length); | |
| const body = document.getElementById('users-body'); | |
| if (!users.length) { | |
| body.innerHTML = '<tr><td colspan="6" class="px-4 py-10 text-center text-gray-600">' + t('users_no_found') + '</td></tr>'; | |
| return; | |
| } | |
| body.innerHTML = users.map(user => { | |
| const statusClass = user.status === 'active' | |
| ? 'text-green-400 bg-green-900/30 border-green-800' | |
| : user.status === 'expired' | |
| ? 'text-yellow-400 bg-yellow-900/30 border-yellow-800' | |
| : 'text-gray-400 bg-gray-800 border-gray-700'; | |
| let action = ''; | |
| if (user.status === 'expired') { | |
| action = `<button onclick="openUserModal('${escUser(user.id)}')" title="${t('users_extend_hint')}" class="border border-yellow-800 text-yellow-400 hover:bg-yellow-900/30 px-2 py-1 rounded">${t('users_extend')}</button>`; | |
| } else if (user.account_status === 'active') { | |
| action = `<button onclick="toggleUser('${escUser(user.id)}', 'disabled')" title="${t('users_disable_hint')}" class="border border-yellow-900 text-yellow-400 hover:bg-yellow-900/30 px-2 py-1 rounded">${t('users_disable')}</button>`; | |
| } else { | |
| action = `<button onclick="toggleUser('${escUser(user.id)}', 'active')" title="${t('users_enable_hint')}" class="border border-green-900 text-green-400 hover:bg-green-900/30 px-2 py-1 rounded">${t('users_enable')}</button>`; | |
| } | |
| return `<tr class="border-t border-gray-800 hover:bg-gray-800/30"> | |
| <td class="px-4 py-3"><div class="text-gray-200">${escUser(user.username)}</div><div class="text-xs text-gray-600">${escUser(user.email || t('users_no_email'))}</div></td> | |
| <td class="px-4 py-3"><span class="inline-flex px-2 py-0.5 rounded border text-xs ${ROLE_CLASSES[user.role] || 'text-gray-400 bg-gray-800 border-gray-700'}">${escUser(ROLE_LABEL_KEYS[user.role] ? t(ROLE_LABEL_KEYS[user.role]) : user.role)}</span></td> | |
| <td class="px-4 py-3"><span class="inline-flex px-2 py-0.5 rounded border text-xs ${statusClass}">${escUser(STATUS_LABEL_KEYS[user.status] ? t(STATUS_LABEL_KEYS[user.status]) : user.status)}</span></td> | |
| <td class="px-4 py-3 text-xs text-gray-500">${escUser(formatDate(user.last_login_at))}</td> | |
| <td class="px-4 py-3 text-xs ${user.status === 'expired' ? 'text-yellow-400' : 'text-gray-500'}">${escUser(user.expires_at ? formatDate(user.expires_at) : t('users_never'))}</td> | |
| <td class="px-4 py-3"><div class="flex justify-end items-center gap-2 whitespace-nowrap text-xs"><button onclick="openUserModal('${escUser(user.id)}')" title="${t('users_edit_hint')}" class="border border-blue-900 text-blue-400 hover:bg-blue-900/30 px-2 py-1 rounded">${t('users_edit')}</button>${action}<button onclick="deleteUser('${escUser(user.id)}')" title="${t('users_delete_hint')}" class="border border-red-900 text-red-400 hover:bg-red-900/30 px-2 py-1 rounded">${t('users_delete')}</button></div></td> | |
| </tr>`; | |
| }).join(''); | |
| } | |
| function toLocalInput(value) { | |
| if (!value) return ''; | |
| const d = new Date(value); | |
| if (Number.isNaN(d.getTime())) return ''; | |
| const pad = n => String(n).padStart(2, '0'); | |
| return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; | |
| } | |
| function openUserModal(userId = '') { | |
| const user = managedUsers.find(item => item.id === userId); | |
| document.getElementById('user-id').value = user?.id || ''; | |
| document.getElementById('user-modal-title').textContent = user ? t('users_edit_user') : t('users_new'); | |
| document.getElementById('user-save-btn').textContent = user ? t('users_save_changes') : t('users_create'); | |
| document.getElementById('user-username').value = user?.username || ''; | |
| document.getElementById('user-username').disabled = Boolean(user); | |
| document.getElementById('user-role').value = user?.role || 'engineer'; | |
| document.getElementById('user-email').value = user?.email || ''; | |
| document.getElementById('user-password').value = ''; | |
| document.getElementById('user-password').required = !user; | |
| document.getElementById('password-hint').textContent = user ? t('users_pw_keep') : t('users_pw_hint'); | |
| document.getElementById('user-status').value = user?.account_status || 'active'; | |
| document.getElementById('user-expires').value = toLocalInput(user?.expires_at); | |
| document.getElementById('user-modal').classList.remove('hidden'); | |
| setTimeout(() => document.getElementById(user ? 'user-email' : 'user-username').focus(), 0); | |
| } | |
| function closeUserModal() { document.getElementById('user-modal').classList.add('hidden'); } | |
| async function saveUser(event) { | |
| event.preventDefault(); | |
| const id = document.getElementById('user-id').value; | |
| const expires = document.getElementById('user-expires').value; | |
| const payload = { | |
| email: document.getElementById('user-email').value.trim() || null, | |
| role: document.getElementById('user-role').value, | |
| expires_at: expires ? new Date(expires).toISOString() : null, | |
| }; | |
| const password = document.getElementById('user-password').value; | |
| if (password) payload.password = password; | |
| if (id) { | |
| payload.status = document.getElementById('user-status').value; | |
| } else { | |
| payload.username = document.getElementById('user-username').value.trim(); | |
| payload.password = password; | |
| } | |
| const button = document.getElementById('user-save-btn'); | |
| button.disabled = true; | |
| try { | |
| await apiUsers(id ? `/api/v1/users/${encodeURIComponent(id)}` : '/api/v1/users', { | |
| method: id ? 'PATCH' : 'POST', body: JSON.stringify(payload) | |
| }); | |
| closeUserModal(); | |
| showUserAlert(id ? 'User updated' : 'User created'); | |
| await loadUsers(); | |
| } catch (error) { | |
| showUserAlert(error.message, true); | |
| } finally { button.disabled = false; } | |
| } | |
| async function toggleUser(id, status) { | |
| try { | |
| await apiUsers(`/api/v1/users/${encodeURIComponent(id)}`, { method: 'PATCH', body: JSON.stringify({ status }) }); | |
| showUserAlert(status === 'active' ? 'User enabled' : 'User disabled'); | |
| await loadUsers(); | |
| } catch (error) { showUserAlert(error.message, true); } | |
| } | |
| async function deleteUser(id) { | |
| const user = managedUsers.find(item => item.id === id); | |
| if (!user || !window.confirm(t('users_confirm_delete').replace('%s', user.username))) return; | |
| try { | |
| await apiUsers(`/api/v1/users/${encodeURIComponent(id)}`, { method: 'DELETE' }); | |
| showUserAlert('User deleted'); | |
| await loadUsers(); | |
| } catch (error) { showUserAlert(error.message, true); } | |
| } | |
| document.addEventListener('DOMContentLoaded', loadUsers); | |