Feature: KI-Nutzung im Admin mit 30-Tage-Sparkline + Top-Nutzer-Tabelle — SW by-v486, APP_VER 463
This commit is contained in:
parent
392359df45
commit
dc737d0c48
4 changed files with 109 additions and 32 deletions
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '462'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '463'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
|
||||
const App = (() => {
|
||||
|
||||
|
|
|
|||
|
|
@ -311,9 +311,10 @@ window.Page_admin = (() => {
|
|||
// TAB: ÜBERSICHT
|
||||
// ------------------------------------------------------------------
|
||||
async function _renderStats(el) {
|
||||
const [s, ki] = await Promise.all([
|
||||
const [s, ki, kiH] = await Promise.all([
|
||||
API.get('/admin/stats'),
|
||||
API.get('/admin/ki/status').catch(() => null),
|
||||
API.get('/admin/ki/history').catch(() => null),
|
||||
]);
|
||||
|
||||
const _kiStatusBadge = () => {
|
||||
|
|
@ -364,37 +365,79 @@ window.Page_admin = (() => {
|
|||
</div>
|
||||
|
||||
<div class="card" style="padding:var(--space-4)">
|
||||
<p style="font-size:var(--text-sm);font-weight:600;margin:0 0 var(--space-3)">KI-Nutzung</p>
|
||||
${_kiStatusBadge()}
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
${[
|
||||
['☁️ Claude (7 Tage)', s.ki_cloud_week, 'var(--c-primary)'],
|
||||
['🖥️ LM Studio (7 Tage)', s.ki_local_week, 'var(--c-success)'],
|
||||
['🌙 Luna (7 Tage)', s.ki_luna_week, 'var(--c-warning)'],
|
||||
['Gesamt heute', s.ki_today, 'var(--c-text-secondary)'],
|
||||
['Gesamt 7 Tage', s.ki_week, 'var(--c-text-secondary)'],
|
||||
['Gesamt Monat', s.ki_month, 'var(--c-text-secondary)'],
|
||||
['Aktive User heute', s.ki_users_today, 'var(--c-text-secondary)'],
|
||||
].map(([label, val, color]) => `
|
||||
<div style="display:flex;justify-content:space-between;font-size:var(--text-sm)">
|
||||
<span style="color:var(--c-text-secondary)">${label}</span>
|
||||
<span style="font-weight:600;color:${color}">${val ?? 0}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div style="margin-top:var(--space-3);padding-top:var(--space-3);border-top:1px solid var(--c-border)">
|
||||
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:0 0 var(--space-2)">
|
||||
User-Limit: <strong>${s.ki_cloud_weekly_limit ?? 20} Cloud-Anfragen / Woche</strong>
|
||||
<!-- Header -->
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-3)">
|
||||
<p style="font-size:var(--text-sm);font-weight:600;margin:0;display:flex;align-items:center;gap:var(--space-2)">
|
||||
<svg class="ph-icon" style="width:16px;height:16px;color:var(--c-primary)" aria-hidden="true"><use href="/icons/phosphor.svg#robot"></use></svg>
|
||||
KI-Nutzung
|
||||
</p>
|
||||
${(s.ki_top_users || []).length ? `
|
||||
<p style="font-size:var(--text-xs);font-weight:600;color:var(--c-text-secondary);margin:var(--space-2) 0 var(--space-1)">Top Cloud-User (7 Tage)</p>
|
||||
${s.ki_top_users.map((u, i) => `
|
||||
<div style="display:flex;justify-content:space-between;font-size:var(--text-xs)">
|
||||
<span style="color:var(--c-text-secondary)">${i+1}. ${_esc(u.name)}</span>
|
||||
<span style="font-weight:600;color:${u.cloud_calls >= (s.ki_cloud_weekly_limit ?? 20) ? 'var(--c-danger)' : 'var(--c-primary)'}">${u.cloud_calls}</span>
|
||||
</div>
|
||||
`).join('')}` : ''}
|
||||
<span style="font-size:var(--text-xs);font-weight:700;padding:2px 10px;border-radius:100px;
|
||||
background:var(--c-primary-subtle);color:var(--c-primary)">
|
||||
${s.ki_today ?? 0} heute · ${s.ki_week ?? 0} (7 Tage)
|
||||
</span>
|
||||
</div>
|
||||
<!-- KI-Status Badge -->
|
||||
${_kiStatusBadge()}
|
||||
<!-- Quellen-Zeile -->
|
||||
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-3)">
|
||||
${[
|
||||
['☁️ Claude', s.ki_cloud_week, 'var(--c-primary)'],
|
||||
['🖥️ Lokal', s.ki_local_week, 'var(--c-success)'],
|
||||
['🌙 Luna', s.ki_luna_week, 'var(--c-warning)'],
|
||||
['📅 Monat', s.ki_month, 'var(--c-text-secondary)'],
|
||||
['👥 User heute',s.ki_users_today, 'var(--c-text-secondary)'],
|
||||
].map(([label, val, color]) => `
|
||||
<span style="font-size:var(--text-xs);padding:2px 8px;border-radius:100px;
|
||||
background:var(--c-surface-2);border:1px solid var(--c-border);white-space:nowrap">
|
||||
<span style="color:var(--c-text-muted)">${label}</span>
|
||||
<strong style="color:${color};margin-left:3px">${val ?? 0}</strong>
|
||||
</span>`).join('')}
|
||||
</div>
|
||||
<!-- Sparkline (30 Tage) -->
|
||||
${(() => {
|
||||
const hist = kiH?.daily_history || [];
|
||||
if (!hist.length) return '<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:0 0 var(--space-3)">Noch keine Verlaufsdaten</p>';
|
||||
const max = Math.max(...hist.map(d => d.total), 1);
|
||||
const W = 400, H = 60, n = hist.length;
|
||||
const pts = hist.map((d, i) => {
|
||||
const x = n === 1 ? W/2 : (i / (n - 1)) * W;
|
||||
const y = H - 5 - (d.total / max) * (H - 10);
|
||||
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
||||
}).join(' ');
|
||||
const first = hist[0]?.date?.slice(5) || '';
|
||||
const last = hist[hist.length-1]?.date?.slice(5) || '';
|
||||
return `<div style="margin-bottom:var(--space-3)">
|
||||
<svg viewBox="0 0 ${W} ${H}" style="width:100%;height:60px;display:block" preserveAspectRatio="none">
|
||||
<polyline points="${pts}" fill="none" stroke="var(--c-primary)" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div style="display:flex;justify-content:space-between;font-size:10px;color:var(--c-text-muted);margin-top:2px">
|
||||
<span>${first}</span><span>30 Tage</span><span>${last}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
})()}
|
||||
<!-- Limit-Hinweis -->
|
||||
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:0 0 var(--space-3)">
|
||||
Cloud-Limit: <strong>${s.ki_cloud_weekly_limit ?? 20} Anfragen / Woche</strong> pro User
|
||||
</p>
|
||||
<!-- Top-Nutzer -->
|
||||
${(kiH?.top_users || []).length ? `
|
||||
<p style="font-size:var(--text-xs);font-weight:600;color:var(--c-text-secondary);margin:0 0 var(--space-2);text-transform:uppercase;letter-spacing:.04em">Aktivste Nutzer</p>
|
||||
<div class="adm-table-scroll">
|
||||
<table class="adm-table">
|
||||
<thead><tr>
|
||||
<th>Name</th><th>E-Mail</th><th>☁️ Cloud</th><th>Gesamt</th><th>Zuletzt</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
${(kiH.top_users).map(u => `<tr>
|
||||
<td style="font-weight:600">${_esc(u.name)}</td>
|
||||
<td style="color:var(--c-text-muted)">${_esc(u.email.length > 22 ? u.email.split('@')[1] : u.email)}</td>
|
||||
<td style="color:var(--c-primary);font-weight:600">${u.cloud}</td>
|
||||
<td>${u.total}</td>
|
||||
<td style="color:var(--c-text-muted)">${u.last_date?.slice(5) || '—'}</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:var(--space-4)">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Offline-Cache + Push Notifications + Tile-Cache
|
||||
============================================================ */
|
||||
|
||||
const CACHE_VERSION = 'by-v485';
|
||||
const CACHE_VERSION = 'by-v486';
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue