Spaces:
Running
Running
Download static/js/admin.js from P01yH3dr0n/client: direct link, hf CLI and curl.
- Browser
- Download file 6.26 kB
-
https://huggingface.co/spaces/P01yH3dr0n/client/resolve/main/static/js/admin.js
- Command line
-
hf download hf://spaces/P01yH3dr0n/client/static/js/admin.js
-
curl -L -o admin.js https://huggingface.co/spaces/P01yH3dr0n/client/resolve/main/static/js/admin.js
6.26 kB
| (() => { | |
| 'use strict'; | |
| const $ = id => document.getElementById(id); | |
| let snapshot = {users: []}; | |
| function node(tag, text) { const n = document.createElement(tag); n.textContent = text; return n; } | |
| async function api(url, options = {}) { | |
| const response = await fetch(url, { ...options, headers: {'Content-Type': 'application/json'}, cache: 'no-store' }); | |
| if (response.status === 401) { location.href = '/login'; throw new Error('请重新登录'); } | |
| const data = await response.json(); | |
| if (!response.ok) throw new Error(data.detail || '请求失败'); | |
| return data; | |
| } | |
| function reset() { HTMLFormElement.prototype.reset.call($('user-form')); $('user-id').value = ''; $('api-key').value = ''; } | |
| function renderUsers() { | |
| $('user-list').replaceChildren(); | |
| const selected = $('filter-user').value; | |
| $('filter-user').replaceChildren(new Option('全部用户', '')); | |
| snapshot.users.forEach(user => { | |
| $('filter-user').add(new Option(user.username, user.id)); | |
| const tr = document.createElement('tr'); | |
| [user.username, $('reveal').checked ? user.password : '••••••••', $('reveal').checked ? user.api_key : '••••••••'].forEach(v => tr.append(node('td', v))); | |
| const actions = document.createElement('td'); | |
| const edit = node('button', '编辑'); edit.onclick = () => { | |
| $('user-id').value = user.id; $('username').value = user.username; | |
| $('password').value = user.password; $('api-key').value = user.api_key; $('username').focus(); | |
| }; | |
| const remove = node('button', '删除'); remove.onclick = async () => { | |
| if (!confirm('删除用户“' + user.username + '”?统计也将删除,所有登录将失效。')) return; | |
| remove.disabled = true; | |
| try { await api('/api/admin/users/' + user.id, {method: 'DELETE'}); await refresh(); reset(); } | |
| catch(e) { $('status').textContent = e.message; } finally { remove.disabled = false; } | |
| }; | |
| actions.append(edit, remove); tr.append(actions); $('user-list').append(tr); | |
| }); | |
| $('filter-user').value = selected; | |
| } | |
| function bars(id, entries) { | |
| const el = $(id); el.replaceChildren(); | |
| const max = Math.max(1, ...entries.map(e => e[1])); | |
| entries.forEach(([label, value]) => { | |
| const row = node('div', ''); row.className = 'bar'; | |
| const track = node('div', ''); const bar = node('i', ''); | |
| bar.style.display = 'block'; bar.style.width = (value / max * 100) + '%'; track.append(bar); | |
| row.append(node('span', label), track, node('span', value)); el.append(row); | |
| }); | |
| if (!entries.length) el.append(node('p', '所选时间范围内暂无数据')); | |
| } | |
| function renderStats() { | |
| const start = $('start').value, end = $('end').value; | |
| if (start && end && start > end) { $('status').textContent = '开始日期不能晚于结束日期'; return; } | |
| const daily = {}, models = {}, totals = Array(6).fill(0); | |
| $('statistics').replaceChildren(); | |
| snapshot.users.filter(u => !$('filter-user').value || u.id === $('filter-user').value).forEach(user => { | |
| const values = Array(6).fill(0); | |
| Object.entries(user.stats).forEach(([day, metrics]) => { | |
| if ((start && day < start) || (end && day > end)) return; | |
| Object.entries(metrics).forEach(([metric, count]) => { | |
| const kind = metric.split(':')[0]; | |
| const index = ['generate','upload','tagger','augment','upscale','vibe_encode'].indexOf(kind); | |
| if (index >= 0) values[index] += count; | |
| if (kind === 'generate') { | |
| const model = metric.slice('generate:'.length); | |
| models[model] = (models[model] || 0) + count; daily[day] = (daily[day] || 0) + count; | |
| } | |
| }); | |
| }); | |
| values.forEach((v,i) => totals[i] += v); | |
| const row = node('tr',''); [user.username,...values].forEach(v => row.append(node('td',v))); $('statistics').append(row); | |
| }); | |
| $('totals').replaceChildren(); | |
| ['生成图片','云端上传','Tagger','修图','放大','Vibe 编码'].forEach((label,i) => { | |
| const card = node('div',label); card.className = 'card'; card.append(node('strong',totals[i])); $('totals').append(card); | |
| }); | |
| bars('daily', Object.entries(daily).sort((a,b) => a[0].localeCompare(b[0]))); | |
| bars('models', Object.entries(models).sort((a,b) => b[1]-a[1])); | |
| } | |
| async function refresh() { | |
| snapshot = await api('/api/admin/users'); renderUsers(); renderStats(); | |
| $('status').textContent = snapshot.sync_error || (snapshot.cloud_enabled ? '已刷新;云端同步每 60 秒合并执行。' : '尚未配置云存储,数据仅保存在本机。'); | |
| } | |
| $('user-form').onsubmit = async event => { | |
| event.preventDefault(); const button = event.submitter; button.disabled = true; | |
| const id = $('user-id').value; | |
| try { | |
| await api('/api/admin/users' + (id ? '/' + id : ''), {method: id ? 'PUT' : 'POST', body: JSON.stringify({username: $('username').value, password: $('password').value, api_key: $('api-key').value})}); | |
| await refresh(); reset(); | |
| } catch(e) { $('status').textContent = e.message; } finally { button.disabled = false; } | |
| }; | |
| $('reset-user-form').onclick = reset; | |
| $('reveal').onchange = () => { renderUsers(); ['password','api-key'].forEach(id => $(id).type = $('reveal').checked ? 'text' : 'password'); }; | |
| $('refresh').onclick = () => refresh().catch(e => $('status').textContent = e.message); | |
| ['filter-user','start','end'].forEach(id => $(id).onchange = renderStats); | |
| const end = new Date(), start = new Date(end); start.setUTCDate(start.getUTCDate()-29); | |
| $('start').value = start.toISOString().slice(0,10); $('end').value = end.toISOString().slice(0,10); | |
| refresh().then(reset).catch(e => $('status').textContent = e.message); | |
| })(); | |