From 24df7a602cb121b0ac753d4cb92d30de95e3a2ea Mon Sep 17 00:00:00 2001 From: Maaxxs <61059039+MaaxxsDev@users.noreply.github.com> Date: Tue, 7 Jul 2026 01:57:32 +0200 Subject: [PATCH] Add Google Analytics dashboard widget (GA4 Data API integration) New Dashboard section shows users/sessions/pageviews for the last 7 days plus top pages, fetched server-side via the GA4 Data API using a Google service account (JWT-bearer flow, no OAuth consent screen needed) - the same approach WordPress plugins like MonsterInsights use to surface GA stats inline instead of linking out to analytics.google.com. Backend: GoogleAnalyticsReporting support class (signs its own JWT with openssl, exchanges it for an access token, calls runReport - no need for the full Google API PHP client library for one endpoint) and AnalyticsController (settings show/update, report fetch). The service account JSON key is written to a gitignored config file, mirroring the existing db.php/setup.php pattern for environment-specific secrets. Admin: new "Dashboard-Anbindung" section in Website Settings (Property ID + service account JSON paste, independent save action). Dashboard gracefully shows a setup hint when not yet configured instead of an error. Co-Authored-By: Claude Sonnet 5 --- .gitignore | 4 + hifi-src/src/pages/admin/Dashboard.jsx | 90 ++++++++++ .../pages/admin/settings/WebsiteSettings.jsx | 72 ++++++++ .../migration_analytics_dashboard.sql | 7 + hifi/api/database/schema.sql | 1 + hifi/api/public/index.php | 5 + .../src/Controllers/AnalyticsController.php | 75 ++++++++ .../src/Support/GoogleAnalyticsReporting.php | 162 ++++++++++++++++++ hifi/api/src/Support/Schema.php | 2 + 9 files changed, 418 insertions(+) create mode 100644 hifi/api/database/migration_analytics_dashboard.sql create mode 100644 hifi/api/src/Controllers/AnalyticsController.php create mode 100644 hifi/api/src/Support/GoogleAnalyticsReporting.php diff --git a/.gitignore b/.gitignore index b1a16b0..ac20ff6 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,10 @@ # pro Umgebung unterschiedlich und darf nicht öffentlich einsehbar sein /hifi/api/config/setup.php +# Google-Service-Account-Schlüssel für die Analytics-Data-API-Anbindung im Dashboard +# (wird vom Admin-Panel automatisch neu geschrieben, enthält einen privaten Schlüssel) +/hifi/api/config/ga-service-account.json + # Claude-Code-Tooling (Skills/lokale Einstellungen) - gehört nicht zum Website-Projekt /hifi/.claude/ /hifi/.agents/ diff --git a/hifi-src/src/pages/admin/Dashboard.jsx b/hifi-src/src/pages/admin/Dashboard.jsx index 20a06d6..4a10c71 100644 --- a/hifi-src/src/pages/admin/Dashboard.jsx +++ b/hifi-src/src/pages/admin/Dashboard.jsx @@ -16,6 +16,94 @@ const Icon = ({ path, className = 'h-6 w-6' }) => ( ); +function AnalyticsWidget() { + const [report, setReport] = useState(null); + const [error, setError] = useState(''); + + useEffect(() => { + api.get('/analytics/report').then(setReport).catch((e) => setError(e.message)); + }, []); + + if (error) { + return ( +
+

Google Analytics

+

{error}

+
+ ); + } + + if (!report) return null; + + if (!report.configured) { + return ( +
+

Google Analytics

+

+ Noch nicht eingerichtet. Unter{' '} + + Einstellungen → Website + {' '} + kannst du die Dashboard-Anbindung konfigurieren, um hier Besucherzahlen zu sehen. +

+
+ ); + } + + const maxPageviews = Math.max(1, ...report.daily.map((d) => d.pageviews)); + + return ( +
+

Google Analytics

+

Letzte 7 Tage.

+ +
+
+

{report.totals.users}

+

Nutzer

+
+
+

{report.totals.sessions}

+

Sitzungen

+
+
+

{report.totals.pageviews}

+

Seitenaufrufe

+
+
+ + {report.daily.length > 0 && ( +
+ {report.daily.map((d) => ( +
+
+ {d.date.slice(6, 8)}.{d.date.slice(4, 6)}. +
+ ))} +
+ )} + + {report.top_pages.length > 0 && ( +
+

Meistbesuchte Seiten

+
    + {report.top_pages.map((p) => ( +
  • + {p.path} + {p.pageviews} +
  • + ))} +
+
+ )} +
+ ); +} + export default function Dashboard() { const { user, hasPermission } = useAuth(); const [stats, setStats] = useState({}); @@ -70,6 +158,8 @@ export default function Dashboard() { ))} )} + + {hasPermission('settings.manage') && } ); } diff --git a/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx b/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx index d0cb6ea..cf41c9c 100644 --- a/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx +++ b/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx @@ -11,6 +11,13 @@ export default function WebsiteSettings() { const [saved, setSaved] = useState(false); const [error, setError] = useState(''); + const [gaPropertyId, setGaPropertyId] = useState(''); + const [gaServiceAccountJson, setGaServiceAccountJson] = useState(''); + const [gaHasCredentials, setGaHasCredentials] = useState(false); + const [gaBusy, setGaBusy] = useState(false); + const [gaError, setGaError] = useState(''); + const [gaSaved, setGaSaved] = useState(false); + useEffect(() => { api .get('/site-settings') @@ -24,8 +31,31 @@ export default function WebsiteSettings() { }) ) .finally(() => setLoading(false)); + api.get('/settings/analytics').then((res) => { + setGaPropertyId(res.ga_property_id || ''); + setGaHasCredentials(res.has_credentials); + }); }, []); + const handleGaDashboardSave = async () => { + setGaBusy(true); + setGaError(''); + setGaSaved(false); + try { + const res = await api.post('/settings/analytics', { + ga_property_id: gaPropertyId, + service_account_json: gaServiceAccountJson, + }); + setGaHasCredentials(res.has_credentials); + setGaServiceAccountJson(''); + setGaSaved(true); + } catch (err) { + setGaError(err.message); + } finally { + setGaBusy(false); + } + }; + const update = (field, value) => { setForm((f) => ({ ...f, [field]: value })); setSaved(false); @@ -119,6 +149,48 @@ export default function WebsiteSettings() { className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900" /> + +
+

Dashboard-Anbindung (optional)

+

+ Zeigt Besucherzahlen direkt in deinem Admin-Dashboard an. Braucht ein Google-Cloud-Service-Account mit + Lesezugriff auf diese GA4-Property. +

+
+
+ + { setGaPropertyId(e.target.value); setGaSaved(false); }} + placeholder="z. B. 123456789" + className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900" + /> +

Nicht die Measurement-ID (G-...) - zu finden unter GA4 → Verwaltung → Property-Details.

+
+
+ +