141 lines
4.1 KiB
JavaScript
141 lines
4.1 KiB
JavaScript
import { apiAction, toast } from './api.js';
|
||
import { initUserPanel } from './ui-user.js';
|
||
import { mountLogoutButton, ensureFloatingLogout } from './ui-auth.js';
|
||
|
||
const state = {
|
||
counts: { templates: 0, sections: 0, blocks: 0, snippets: 0, renders_total: 0 },
|
||
usage: [],
|
||
};
|
||
|
||
async function ensureAuthenticated() {
|
||
try {
|
||
const me = await apiAction('auth.me', { method: 'GET' });
|
||
if (!me?.ok || !me?.user) {
|
||
window.location.href = '/login.php';
|
||
return false;
|
||
}
|
||
window.__currentUser = me.user;
|
||
document.documentElement.classList.remove('auth-pending');
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function ensureAccess() {
|
||
const role = (window.__currentUser?.role || '').toLowerCase();
|
||
if (role !== 'owner' && role !== 'admin') {
|
||
toast('Kein Zugriff auf das Dashboard', false);
|
||
window.location.href = '/admin/profile.php';
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function renderCounts(counts) {
|
||
const mapping = {
|
||
templates: 'count-templates',
|
||
sections: 'count-sections',
|
||
blocks: 'count-blocks',
|
||
snippets: 'count-snippets',
|
||
renders_total: 'count-usage',
|
||
};
|
||
Object.entries(mapping).forEach(([key, id]) => {
|
||
const el = document.getElementById(id);
|
||
if (!el) return;
|
||
const value = counts[key] ?? 0;
|
||
el.textContent = typeof value === 'number' ? value.toLocaleString('de-DE') : value;
|
||
});
|
||
}
|
||
|
||
function formatDate(value) {
|
||
if (!value) return '–';
|
||
try {
|
||
const date = new Date(value);
|
||
if (Number.isNaN(date.getTime())) return value;
|
||
return date.toLocaleString('de-DE');
|
||
} catch {
|
||
return value;
|
||
}
|
||
}
|
||
|
||
function renderUsage(list) {
|
||
const table = document.getElementById('usageTable');
|
||
if (!table) return;
|
||
const tbody = table.querySelector('tbody');
|
||
if (!tbody) return;
|
||
if (!list.length) {
|
||
tbody.innerHTML = '<tr><td colspan="4" class="text-sm text-slate-500">Noch keine Daten vorhanden.</td></tr>';
|
||
return;
|
||
}
|
||
tbody.innerHTML = list.map(item => `
|
||
<tr data-template-id="${item.template_id}">
|
||
<td>${escapeHtml(item.name)}</td>
|
||
<td>${item.render_count.toLocaleString('de-DE')}</td>
|
||
<td>${escapeHtml(formatDate(item.last_rendered_at || item.updated_at))}</td>
|
||
<td class="text-right">
|
||
<button type="button" class="btn" data-reset="${item.template_id}">Zähler zurücksetzen</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
return String(str ?? '')
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
async function loadMetrics() {
|
||
try {
|
||
const res = await apiAction('dashboard.metrics', { method: 'GET' });
|
||
if (!res?.ok) throw new Error(res?.error || 'Dashboard konnte nicht geladen werden');
|
||
state.counts = res.counts || state.counts;
|
||
state.usage = Array.isArray(res.usage) ? res.usage : [];
|
||
renderCounts(state.counts);
|
||
renderUsage(state.usage);
|
||
} catch (err) {
|
||
toast(err.message || 'Fehler beim Laden', false);
|
||
}
|
||
}
|
||
|
||
async function resetUsage(templateId) {
|
||
try {
|
||
await apiAction('dashboard.reset_usage', { method: 'POST', data: { template_id: templateId } });
|
||
toast('Zähler zurückgesetzt', true);
|
||
await loadMetrics();
|
||
} catch (err) {
|
||
toast(err.message || 'Zurücksetzen fehlgeschlagen', false);
|
||
}
|
||
}
|
||
|
||
function bindEvents() {
|
||
const refresh = document.getElementById('btn-refresh-dashboard');
|
||
refresh?.addEventListener('click', () => loadMetrics());
|
||
|
||
const table = document.getElementById('usageTable');
|
||
table?.addEventListener('click', ev => {
|
||
const btn = ev.target.closest('button[data-reset]');
|
||
if (!btn) return;
|
||
const id = Number(btn.getAttribute('data-reset'));
|
||
if (!id) return;
|
||
if (confirm('Zähler für dieses Template wirklich löschen?')) {
|
||
resetUsage(id);
|
||
}
|
||
});
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
const ok = await ensureAuthenticated();
|
||
if (!ok) return;
|
||
if (!ensureAccess()) return;
|
||
initUserPanel();
|
||
bindEvents();
|
||
await loadMetrics();
|
||
mountLogoutButton('#btn-logout', { redirect: '/login.php' });
|
||
ensureFloatingLogout({ redirect: '/login.php' });
|
||
});
|