Website-Analyse für webklar.com: Sie-Fassung des Banners, fester Messdienst-Eintrag, Auswertung fürs WOMS

- Messskript: data-variant="eigen" zeigt eine eigene Banner-Fassung (Sie-Form, WEBklar misst selbst,
  BANNER_VERSION_EIGEN 2026-10-17); Kunden-Websites bleiben bei der Fassung 2026-10-02.
- config.analyse.eigeneWebsite: fester Schlüssel und Domains von webklar.com; ensureEigeneWebsite zieht den
  Eintrag im Messdienst täglich nach (aktiv: false schaltet ab). Kein Kunde, kein Abo, keine Tickets.
- /api/admin/analyse-eigen (jeder Mitarbeiter): Kennzahlen 7/30/90 Tage und Heatmap-Link auf webklar.com.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-17 18:34:06 +00:00
1 parent f3f8ac3777
commit 9ed295946c
10 files changed
+294 -20

No files matched your search

+10
View File
@@ -1058,6 +1058,16 @@ export const config = {
adminToken: process.env.ANALYTICS_ADMIN_TOKEN || '',
heatSecret: process.env.ANALYTICS_HEAT_SECRET || '',
avvVersion: '2026-10-02',
// WEBklars eigene Website (kein Kundenprojekt, kein Abo): fester Eintrag im Messdienst, Auswertung im WOMS
// (server/services/analyseEigen.js). Der Schluessel ist nicht geheim, er steht im Skript-Tag von webklar.com.
// aktiv: false schaltet die Messung ab (Messdienst liefert dann das leere Skript und verwirft Ereignisse).
eigeneWebsite: {
aktiv: true,
siteKey: 'yiy992rvq7vchfd7bqtqivah',
projectId: 'webklar-eigene-website',
domains: ['webklar.com', 'www.webklar.com'],
host: 'webklar.com',
},
},
// Terminplaner (Funktionsschluessel 'termine'). Wird bei jedem Aufruf gelesen (Tests aendern die Werte zur
+7
View File
@@ -33,6 +33,7 @@ import { purgeViewLog } from './services/customerViewLog.js'
import { purgeUnclaimedNewsImages } from './services/newsImages.js'
import { runActivation } from './services/siteFeatures.js'
import { runAboCheck, runAnalyseReconcile } from './services/analyse.js'
import { ensureEigeneWebsite } from './services/analyseEigen.js'
import { aboFunktionenTagesJob } from './services/aboFunktionenAbgleich.js'
import { fuenfMinutenJob, minutenJob, tagesbericht, taeglicherJob } from './services/termine/jobs.js'
import { minutenJob as unterkunftMinutenJob, viertelstundenJob as unterkunftViertelstundenJob, taeglicherJob as unterkunftTaeglicherJob } from './services/unterkunft/jobs.js'
@@ -54,6 +55,7 @@ import publicShopRoutes from './routes/publicShop.js'
import publicUnterkunftRoutes from './routes/publicUnterkunft.js'
import publicZeitenRoutes from './routes/publicZeiten.js'
import adminAnalyseRoutes from './routes/admin/analyse.js'
import adminAnalyseEigenRoutes from './routes/admin/analyseEigen.js'
import { legalRouter } from './services/legalDocs.js'
import adminFinanzenRoutes from './routes/admin/finanzen.js'
import adminProjektkontenRoutes from './routes/admin/projektkonten.js'
@@ -121,6 +123,8 @@ app.use('/api/admin/customers', adminCustomersRoutes)
app.use('/api/admin/employees', adminEmployeesRoutes)
app.use('/api/admin/invoice-clients', adminInvoiceClientsRoutes)
app.use('/api/admin/analyse', adminAnalyseRoutes)
// Website-Analyse der eigenen Website webklar.com (WOMS-Seite Website-Statistik)
app.use('/api/admin/analyse-eigen', adminAnalyseEigenRoutes)
// Projektkonten und Auftraege (Admin): vor dem Finanzen-Router, dessen requireAppwriteStaff sonst jede Anfrage darunter zuerst prueft
app.use('/api/admin/finanzen/projektkonten', adminProjektkontenRoutes)
app.use('/api/admin/finanzen/auftraege', adminAuftraegeRoutes)
@@ -241,6 +245,9 @@ const server = app.listen(config.port, '0.0.0.0', () => {
.then(() => runAnalyseReconcile({ now: new Date() }))
.then((r) => r.ended.length && console.log(`[analyse] Abgleich: ${r.ended.length} Analyse(n) beendet`))
.catch((err) => console.warn('[analyse] Abgleich fehlgeschlagen:', err.message))
// eigene Website webklar.com: Eintrag im Messdienst nachziehen (ohne Kunde und Abo, config.analyse.eigeneWebsite)
.then(() => ensureEigeneWebsite())
.catch((err) => console.warn('[analyse] Eintrag für webklar.com fehlgeschlagen:', err.message))
setTimeout(analyseAboCheck, 2 * 60 * 1000).unref()
setInterval(analyseAboCheck, 24 * 60 * 60 * 1000).unref()
+34
View File
@@ -0,0 +1,34 @@
// Admin-API (WOMS): Auswertung der Website-Analyse fuer WEBklars eigene Website (webklar.com). Jeder Mitarbeiter darf lesen.
import { Router } from 'express'
import { requireAppwriteTeam } from '../../middleware/appwriteStaff.js'
import { eigeneAuswertung, eigenerHeatmapLink, gueltigerPfad } from '../../services/analyseEigen.js'
const router = Router()
router.use(requireAppwriteTeam)
router.get('/', async (req, res) => {
const range = String(req.query.range || '30')
if (!['7', '30', '90'].includes(range)) return res.status(400).json({ error: 'Ungültiger Zeitraum (7, 30 oder 90 Tage)' })
try {
res.json(await eigeneAuswertung(Number(range)))
} catch (err) {
// 404: Messdienst kennt die Website noch nicht (Eintrag entsteht beim Start bzw. im Tagesjob)
if (err.status === 404) return res.status(409).json({ error: 'Die Messung für webklar.com ist noch nicht eingerichtet.' })
console.error('[admin-analyse-eigen] Auswertung:', err.status || '', err.message)
res.status(503).json({ error: 'Auswertung gerade nicht erreichbar' })
}
})
router.post('/heatmap-link', (req, res) => {
const { path, vp } = req.body || {}
if (!gueltigerPfad(path)) return res.status(400).json({ error: 'Ungültiger Pfad' })
if (!['m', 't', 'd'].includes(vp)) return res.status(400).json({ error: 'Ungültige Ansicht' })
try {
res.json({ url: eigenerHeatmapLink({ path, vp }) })
} catch (err) {
console.error('[admin-analyse-eigen] Heatmap-Link:', err.status || '', err.message)
res.status(503).json({ error: 'Der Heatmap-Link konnte gerade nicht erstellt werden.' })
}
})
export default router
+43
View File
@@ -0,0 +1,43 @@
// Website-Analyse fuer WEBklars eigene Website (webklar.com): ohne Kunde, Abo, Funktionsdokument und Tickets.
// Der Messdienst kennt die Website ueber den festen Eintrag config.analyse.eigeneWebsite; das WOMS liest die
// Auswertung ueber /api/admin/analyse-eigen. Rechtlich ist WEBklar hier selbst Verantwortlicher (keine AVV).
import { config } from '../config.js'
import { analyticsClient } from './analyticsClient.js'
// Tests ersetzen den Client
export const eigenDeps = { client: analyticsClient }
const website = () => config.analyse.eigeneWebsite
/** Gleiche Pruefung wie beim Heatmap-Link der Kunden (routes/funktionen.js): Pfad ohne Abfrage, Anker und Steuerzeichen. */
export function gueltigerPfad(path) {
return typeof path === 'string' && path.startsWith('/') && path.length <= 300 && !/[?#\\\s\u0000-\u001f\u007f]/.test(path)
}
/**
* Eintrag im Messdienst anlegen bzw. nachziehen (idempotent), bei aktiv: false abschalten.
* Laeuft taeglich und beim Start; ein Fehler wird geworfen und vom Aufrufer geloggt.
*/
export async function ensureEigeneWebsite() {
const w = website()
if (!w.aktiv) {
await eigenDeps.client.stopSite(w.siteKey)
return { aktiv: false }
}
await eigenDeps.client.putSite(w.siteKey, { projectId: w.projectId, domains: w.domains, active: true })
return { aktiv: true }
}
/** Kennzahlen fuer 7, 30 oder 90 Tage (Form wie analytics/src/stats.js), dazu Adresse und Zustand fuer die Anzeige. */
export async function eigeneAuswertung(range) {
const w = website()
const stats = await eigenDeps.client.getStats(w.siteKey, range)
return { website: { host: w.host, aktiv: w.aktiv }, stats }
}
/** Heatmap-Link fuer eine Seite von webklar.com (neuer Tab; webklar.com laesst sich nicht in fremde Seiten einbetten). */
export function eigenerHeatmapLink({ path, vp }) {
const w = website()
const token = eigenDeps.client.heatToken({ siteKey: w.siteKey, path, vp })
return `https://${w.host}${path}?__wk_heat=${encodeURIComponent(token)}`
}